สรุปสั้นก่อนเริ่ม
ในโลกของ software engineering มีความเชื่อที่อันตรายอยู่หนึ่งข้อ: "ระบบเก่ามันแย่มาก สร้างใหม่หมดดีกว่า"
ความเชื่อนี้ทำให้โปรเจกต์ล้มเหลวมาแล้วนับไม่ถ้วน Netscape เคยเขียน browser ใหม่จากศูนย์แล้วเสียตำแหน่งผู้นำตลาดไปตลอดกาล บริษัทระดับ Fortune 500 หลายแห่งทุ่มเงินเป็นร้อยล้านไปกับการ rewrite ที่ไม่เคยเสร็จ
แต่ในธรรมชาติมีต้นไม้ชนิดหนึ่งที่สอนเราถึงวิธีที่ดีกว่า — Strangler Fig หรือต้นไทรบางสายพันธุ์ ที่ค่อยๆ เติบโตพันรอบต้นไม้เดิม ดูดซึมสารอาหาร จนวันหนึ่งต้นเดิมหายไปโดยที่ป่าทั้งป่าไม่เคยสะดุด
บทความนี้จะพาคุณเข้าใจ Strangler Fig Pattern ตั้งแต่หลักการ ไปจนถึง case study จริงจากบริษัทระดับโลกอย่าง Discord, Cloudflare, Dropbox, Hugging Face และ npm ที่ใช้แนวคิดนี้ migrate จาก Python (และภาษาอื่น) ไป Rust — ได้ผลลัพธ์ที่ลด latency 10 เท่า, ลด CPU 70%, และประหยัดต้นทุนปีละหลายล้านดอลลาร์
บทนำ: ทำไมถึงไม่ควร rewrite ระบบทั้งหมดในคราวเดียว
ผมอยากเริ่มด้วยเรื่องที่ทุกคนที่เคยทำ software มานานจะเข้าใจ
ระบบเก่ามันน่ารำคาญ โค้ดที่เขียนมาเมื่อ 5 ปีที่แล้ว ไม่มี test ไม่มี documentation คนที่เขียนลาออกไปแล้ว library ที่ใช้ไม่ support แล้ว bug ก็แปลกๆ ทุกคนอยากทิ้ง อยากเขียนใหม่ อยากเริ่มต้นสด
แต่ "เริ่มต้นสด" มีราคาที่สูงมาก
ปัญหาของ Big-Bang Rewrite:
ใช้เวลานานกว่าที่คิดเสมอ — ระบบเก่าที่ดูเหมือน "แค่ CRUD ธรรมดา" มักจะมี business logic ที่ซ่อนอยู่เป็นร้อยๆ กรณี edge case ที่ไม่มีใครจำได้ว่าทำไมถึงต้องทำแบบนั้น
ระบบเดิมไม่หยุดเปลี่ยนแปลง — ระหว่างที่ทีมหนึ่งกำลังเขียนระบบใหม่ ทีมที่ดูแลระบบเก่าก็ต้อง fix bug เพิ่ม feature ตาม requirement ใหม่ เป้าหมายเลยขยับไปเรื่อยๆ
ไม่มีค่าส่งมอบระหว่างทาง — Big-Bang Rewrite ไม่มี incremental value ลูกค้าไม่ได้อะไรใหม่จนกว่าจะเสร็จทั้งหมด ซึ่งบางทีใช้เวลา 2-3 ปี ถ้าเสร็จ
ความเสี่ยงสูงมากตอน switch — วันที่ต้องเปลี่ยนจากระบบเก่าไประบบใหม่ เป็นวันที่ทุกอย่างอาจพัง ข้อมูลอาจหาย ลูกค้าอาจใช้ไม่ได้
สูญเสีย domain logic ที่ไม่มีเอกสาร — ระบบเก่าที่ทำงานมาหลายปีมี "ความรู้" ฝังอยู่ในโค้ด if-else ที่ดูไม่สำคัญอาจจะเป็นการ handle edge case ที่ลูกค้ารายใหญ่เคย complain แล้วต้องแก้ พอเขียนใหม่ ความรู้พวกนี้หายไป
Joel Spolsky เคยเขียนไว้ว่า "The single worst strategic mistake that any software company can make: rewrite from scratch" — นี่คือบทเรียนที่ทุกคนรู้ แต่หลายคนก็ยังทำซ้ำ
แล้วทางออกคืออะไร?
Strangler Fig คืออะไร — ต้นไม้ที่สอนเรื่อง migration
ในป่าดิบชื้นของเอเชียตะวันออกเฉียงใต้ ออสเตรเลีย และอเมริกากลาง มีต้นไม้สกุล Ficus ที่มีพฤติกรรมน่าทึ่ง
มันเริ่มต้นจากเมล็ดเล็กๆ ที่นกทิ้งไว้บนกิ่งของต้นไม้อื่น จากนั้นมันค่อยๆ หยั่งรากลงมาตามลำต้นของต้นไม้เจ้าบ้าน พันรอบ เติบโต แผ่กิ่งก้านออกไปรับแสง ดูดน้ำและสารอาหาร จนในที่สุดต้นเดิมก็ค่อยๆ ตายลง เหลือแต่ Strangler Fig ที่ยืนอยู่แทน — โดยที่ระบบนิเวศของป่ารอบข้างแทบไม่ได้รู้สึกอะไร
Martin Fowler นำแนวคิดนี้มาเปรียบเทียบกับการ migrate ระบบ software และ Microsoft ได้อธิบายไว้ใน Azure Architecture Center อย่างละเอียด (ภาพประกอบจาก Microsoft Azure Architecture Center)
หลักการง่ายมาก:
- ไม่ต้องทิ้งระบบเก่าทันที — ปล่อยให้มันทำงานต่อไป
- สร้างระบบใหม่ค่อยๆ เติบโตรอบระบบเดิม — ทีละ module ทีละ feature
- เปลี่ยนเส้นทาง traffic ทีละน้อย — จากระบบเก่าไประบบใหม่
- เมื่อทุกอย่างย้ายไปหมดแล้ว — ปลดระบบเก่าออก
ฟังดูเรียบง่าย แต่ devil อยู่ใน detail เสมอ มาดูขั้นตอนกัน
4 ขั้นตอนของ Strangler Fig Pattern
ตาม Microsoft Azure Architecture Center มี 4 เฟสหลัก:
Phase 1: Introduce Facade (Proxy)
ขั้นตอนแรกคือวาง facade หรือ proxy layer ระหว่าง client กับ legacy system
ทุก request ที่เข้ามาจะผ่าน facade ก่อน ตอนแรก facade แค่ forward ทุกอย่างไป legacy system เหมือนเดิม — ยังไม่มีอะไรเปลี่ยนแปลง
แต่ facade นี้แหละที่เป็นกุญแจ มันทำให้เราสามารถ "สลับ" ได้ว่า request ไหนจะไประบบเก่า request ไหนจะไประบบใหม่ โดยที่ client ไม่รู้ตัว
ลองนึกภาพ reverse proxy อย่าง NGINX หรือ API Gateway ที่สามารถ route /api/products ไประบบใหม่ แต่ route /api/orders ไประบบเก่า — ลูกค้าที่ call API เข้ามาไม่รู้ด้วยซ้ำว่าหลังบ้านมีสองระบบทำงานอยู่
Phase 2: Incremental Shift
เมื่อ facade พร้อมแล้ว เราเริ่ม migrate ทีละ module
หลักในการเลือกว่าจะ migrate อะไรก่อน:
- module ที่เป็นอิสระ — ไม่พึ่งพา module อื่นมาก (low coupling)
- module ที่มีปัญหาชัดเจน — ช้า กิน CPU เยอะ มี bug บ่อย
- module ที่ให้คุณค่าทางธุรกิจสูง — แก้แล้วเห็นผลชัด
เขียน module ใหม่ → test → route traffic บางส่วนไปที่ใหม่ (canary) → ถ้า OK ก็ route ทั้งหมด → ไป module ถัดไป
Phase 3: Decommission Legacy
เมื่อ functionality ทั้งหมดย้ายไประบบใหม่แล้ว ระบบเก่าก็ไม่มี traffic เข้ามาอีก ตอนนี้สามารถ decommission ได้อย่างสบายใจ
แต่อย่าลืม — ก่อน decommission ต้องมั่นใจว่า:
- ข้อมูลทั้งหมด migrate เรียบร้อยแล้ว
- ไม่มี process ไหนที่ยังแอบเรียกระบบเก่าอยู่
- มี rollback plan ในกรณีฉุกเฉิน
Phase 4: Remove Facade
ขั้นตอนสุดท้าย — เมื่อระบบเก่าถูกปลดออกแล้ว facade ก็ไม่จำเป็นอีกต่อไป client สามารถเชื่อมต่อกับระบบใหม่โดยตรง
ขั้นตอนนี้เป็น optional สำหรับหลายทีม บาง facade (เช่น API Gateway) อาจจะเก็บไว้ถาวรเพราะมันมีประโยชน์ในตัวของมันเอง เช่น rate limiting, logging, authentication
ข้อควรระวังที่สำคัญ
Microsoft Azure Architecture Center เน้นเรื่องที่ต้องระวัง:
- Shared data stores — ระบบเก่าและใหม่อาจใช้ database ร่วมกัน ต้อง handle ให้ดีเรื่อง data consistency
- Facade ต้องไม่กลายเป็น bottleneck — ถ้า facade ช้าหรือล่ม ทั้งระบบเก่าและใหม่จะพังไปด้วย ต้องออกแบบให้ fault-tolerant
- ออกแบบระบบใหม่ให้ intercept ได้ง่าย — structure ของ app ใหม่ต้องเอื้อให้สามารถเพิ่ม/เปลี่ยน module ได้สะดวก
เมื่อไหร่ควรใช้ เมื่อไหร่ไม่ควร
ใช้ Strangler Fig เมื่อ:
- ระบบใหญ่เกินกว่าจะ rewrite ทั้งหมดในครั้งเดียว — ถ้ามีโค้ดเป็นหลักแสนบรรทัด การเขียนใหม่ทั้งหมดคือการพนัน
- ต้องการ gradual migration — ค่อยๆ ย้าย ค่อยๆ เรียนรู้ ค่อยๆ ปรับ
- ห้าม downtime — ระบบที่ลูกค้าใช้ตลอด 24/7 ไม่สามารถปิดเพื่อ "เปลี่ยนระบบ" ได้
- ต้องการพิสูจน์ ROI ระหว่างทาง — ผู้บริหารอยากเห็นผลก่อนที่จะลงทุนเพิ่ม
ไม่ควรใช้ Strangler Fig เมื่อ:
- ระบบเล็กพอที่จะ replace ทั้งหมดได้ — ถ้ามีแค่ไม่กี่พัน lines ก็เขียนใหม่เลย
- ไม่สามารถ intercept request ได้ — ถ้าระบบเก่าไม่มี API ไม่มี HTTP ไม่มีทาง route traffic ได้ pattern นี้อาจไม่เหมาะ
- ต้อง decommission ระบบเก่าเร็วมาก — ถ้ามี deadline ว่าระบบเก่าต้องปิดภายใน 3 เดือน Strangler Fig อาจช้าเกินไป
- ระบบเก่าไม่มีปัญหาอะไรจริงๆ — ถ้ามันทำงานได้ดี ลูกค้า happy ต้นทุนไม่สูง อย่า fix สิ่งที่ไม่ broken
Case Study 1: Java Monolith สู่ Spring Boot Microservices
ก่อนที่เราจะเข้าเรื่อง Python กับ Rust ผมอยากยกตัวอย่าง case study แรกที่แสดงให้เห็นว่า Strangler Fig Pattern ทำงานกับ technology stack ไหนก็ได้
จาก case study ที่ตีพิมพ์ใน Medium โดย Raghavender Badam ทีมงานต้อง migrate ระบบ Java monolith ที่ใช้ Spring MVC + MySQL database ตัวเดียว ไปเป็น Spring Boot microservices
ระบบเก่า: Spring MVC monolith, Single MySQL, tightly coupled — deploy ทั้งก้อน ถ้าอะไรพัง ทุกอย่างพัง
ระบบใหม่: Spring Boot microservices, Spring Cloud Gateway เป็น facade, PostgreSQL แยกตาม service, RabbitMQ + Kafka
ลำดับการ migrate:
- Fraud detection module — self-contained มากที่สุด ไม่พึ่งพา module อื่น ถ้า improve ได้จะเห็นผลทันที
- User authentication → 3. Transaction processing → 4. Notification system (ผลกระทบต่อ business น้อยที่สุด)
เครื่องมือ: Feature flags (LaunchDarkly), blue-green deployments, Kubernetes, Prometheus + Grafana
ผลลัพธ์:
- 99.9% uptime ตลอดระยะเวลา migration 2 ปี — ลูกค้าแทบไม่รู้ว่ามีการเปลี่ยนระบบ
- 40% faster feature releases — ทีมส่ง feature ได้เร็วขึ้นเพราะ deploy แยกกัน
- 50% cost reduction — ผ่าน containerization ใช้ resource ได้ efficient กว่า
Credit: Strangler Fig Pattern to migrate from a monolithic Java application to Spring Boot microservices
Case Study 2: PHP Laminas Migration
อีกตัวอย่างที่ดีมาจาก Laminas Project (เดิมคือ Zend Framework) ที่ publish แนวทาง 6 ขั้นตอนสำหรับการ migrate ระบบ PHP แบบ Strangler Fig
6 ขั้นตอน:
- Isolate functionality — แยก function ที่ต้องการ migrate ออกมา ให้ชัดเจนว่าขอบเขตอยู่ตรงไหน
- Build replacement — เขียน module ใหม่ที่ทำสิ่งเดียวกัน
- Route interception — เปลี่ยน route ให้ชี้ไป module ใหม่
- Test & Monitor — ทดสอบอย่างละเอียด monitor ทุก metric
- Repeat — ทำซ้ำกับ module ถัดไป
- Retire legacy — ปลด module เก่าออก
ตัวอย่าง e-commerce:
ทีมเลือกลำดับ migrate endpoint ตามความเสี่ยงที่ต่ำไปสูง:
/products → ย้ายก่อน (อ่านอย่างเดียว ความเสี่ยงต่ำ)
/cart → ย้ายถัดมา (มี state แต่กระทบน้อย)
/checkout → ย้ายเมื่อมั่นใจแล้ว (เกี่ยวกับเงิน ความเสี่ยงสูง)
/account → ย้ายสุดท้าย (user data, privacy, security)
ทำไมถึงไม่ full rewrite?
บทความจาก Laminas ให้เหตุผลชัด:
- สูญเสีย domain logic ที่ไม่มี document — โค้ดเก่ามี "ความรู้" ที่ไม่มีใครจำได้
- ต้นทุนสูงเกินไป — full rewrite ต้องใช้ทีมใหญ่ เวลานาน
- อัตราล้มเหลวสูง — โปรเจกต์ rewrite ส่วนมากไม่เสร็จตาม plan
- stakeholder ไม่ยอม — ผู้บริหารไม่อยากเสี่ยงกับ "ทำใหม่ทั้งหมด"
Credit: Strangler Fig Pattern — Laminas Blog
Case Study 3: Python สู่ Rust — The Main Event
ถ้า case study ก่อนหน้าคือ warm-up ตรงนี้คือ main event
ในช่วง 3-4 ปีที่ผ่านมา เกิดกระแสที่น่าสนใจในวงการ tech: บริษัท tech ระดับโลกหลายแห่ง migrate ระบบจาก Python (และภาษา high-level อื่นๆ) ไปเป็น Rust — ไม่ใช่เพราะ hype แต่เพราะตัวเลขที่ปฏิเสธไม่ได้
ทำไมต้อง Rust? (ไม่ใช่ hype)
ก่อนจะเข้า case study ต้องเข้าใจก่อนว่า Rust ไม่ใช่ภาษาวิเศษที่จะมาแทน Python ทุกที่ มันมี trade-off ที่ชัดเจน:
ทำไม Rust เร็ว:
- ไม่มี Garbage Collector — Python มี GC ที่ต้องหยุดโปรแกรมเป็นพักๆ เพื่อจัดการ memory Rust ใช้ระบบ ownership ที่จัดการ memory ตั้งแต่ compile time ไม่มี pause
- Compiled เป็น machine code — Python เป็น interpreted language ต้องแปลโค้ดทีละบรรทัดตอนรัน Rust compile เป็น native code ที่ CPU เข้าใจโดยตรง
- Zero-cost abstractions — syntax ที่ดูสวยของ Rust ไม่มี overhead ตอนรัน
- Memory safety โดยไม่ต้องจ่ายค่า GC — ได้ความปลอดภัยเท่า Java/Go แต่ไม่ต้องแลกกับ performance
ตัวเลขจริงในปี 2026:
- Rust เร็วกว่า Python ประมาณ 60 เท่า ในงานที่ใช้ CPU หนักๆ เช่น JSON parsing, binary tree operations
- ในงาน AI agent frameworks, Rust ใช้ memory น้อยกว่าประมาณ 5 เท่า (1,046 MB เทียบกับ 5,146 MB)
แต่ — Rust มีราคาที่ต้องจ่าย:
- Learning curve สูง — developer ใช้เวลา 3-6 เดือนกว่าจะรู้สึก productive (เทียบกับ Python ที่ 2-4 สัปดาห์)
- เขียนโค้ดช้ากว่า — Rust บังคับให้คิดเรื่อง memory, lifetime, borrowing ตั้งแต่ต้น
- ไม่เหมาะกับทุกงาน — prototype, script, orchestration logic ใช้ Python ดีกว่า
ดังนั้นคำถามไม่ใช่ "จะ rewrite ทั้งหมดเป็น Rust ไหม?" แต่คือ "ส่วนไหนที่ Rust จะให้ ROI คุ้มที่สุด?" — และนี่แหละคือที่ที่ Strangler Fig Pattern เข้ามา
Dropbox: The Perfect Strangler Fig
ถ้าจะยกตัวอย่าง Strangler Fig กับ Python-to-Rust ที่สมบูรณ์แบบที่สุด ต้องยกให้ Dropbox
ปัญหา:
Dropbox มี codebase ขนาดใหญ่ที่เขียนด้วย Python ระบบ file sync ที่ต้องจัดการกับไฟล์หลายพันล้านไฟล์ทุกวัน ทีม engineer ค้นพบว่า:
- 20% ของโค้ดกิน 80% ของ CPU — ตาม Pareto principle คลาสสิก
- Hot paths ที่เป็นปัญหาคือ: file hashing, compression, deduplication — งาน CPU-intensive ที่ต้องทำทุก sync
- Python ทำงานพวกนี้ช้าเกินไป ส่งผลให้ sync ช้า แบตเตอรี่ไหลเร็ว ลูกค้าไม่ happy
วิธีแก้ — Strangler Fig แบบ PyO3:
แทนที่จะ rewrite ทั้ง Dropbox client ทีม Dropbox ใช้วิธีที่ฉลาดกว่า:
- Profile เพื่อหา hot paths ที่กิน CPU มากที่สุด
- เขียนเฉพาะ functions ที่เป็นปัญหาด้วย Rust — ไม่ใช่ทั้ง module ไม่ใช่ทั้งระบบ แค่ functions ที่ profile บอกว่าเป็น bottleneck
- ใช้ PyO3 bindings เพื่อให้ Python เรียก Rust functions ได้เหมือนเรียก Python functions ปกติ — Python code ที่เหลือไม่ต้องเปลี่ยนอะไร
- เก็บ Python ไว้สำหรับ orchestration และ business logic — ส่วนที่ไม่ต้องการ performance สูง ยังเป็น Python เหมือนเดิม
ผลลัพธ์:
- CPU ลดลง 75% — จาก hot paths ที่เคยกิน CPU เยอะ ตอนนี้เกือบไม่เห็นบน profiler
- ประหยัดค่า infrastructure มากกว่า $1M ต่อปี — ใช้เครื่องน้อยลง
- Sync เร็วขึ้นอย่างเห็นได้ชัด — ลูกค้ารู้สึกได้
- แบตเตอรี่อึดขึ้น — laptop ไม่ร้อน ไม่กินแบต
สิ่งที่สำคัญที่สุดจาก Dropbox:
"You don't need a full rewrite. Targeting hot paths delivers most benefits with 20% of the effort and risk."
นี่คือ essence ของ Strangler Fig — ไม่ต้อง rewrite ทั้งหมด แค่เลือกจุดที่ให้ผลมากที่สุด ทำตรงนั้น แล้วให้ส่วนที่เหลือทำงานต่อไปเหมือนเดิม
Discord: 10x Performance, 60% Fewer Alerts
Discord มีเรื่องเล่าที่น่าสนใจไม่แพ้กัน
ปัญหา:
Read States service — ระบบที่ track ว่าผู้ใช้อ่านข้อความไหนแล้ว ไม่ได้อ่านข้อความไหน ฟังดูเรียบง่าย แต่เมื่อผู้ใช้มีเป็นร้อยล้านคน ข้อความมีเป็นพันล้านข้อความ service นี้ต้อง handle ปริมาณ read/write ที่มหาศาล
ระบบเดิมเขียนด้วยภาษาที่มี Garbage Collector ปัญหาใหญ่คือ GC pauses — ทุกๆ ไม่กี่นาที ระบบจะหยุดชั่วขณะเพื่อ clean up memory ทำให้ latency กระโดด ส่ง alert ออกมาเรื่อยๆ
การ migrate:
- ทีม 2-3 คน ใช้เวลาประมาณ 6 เดือน
- Rewrite เฉพาะ Read States service ไม่ใช่ทั้งระบบ Discord
ผลลัพธ์:
- Performance เพิ่มขึ้น 10 เท่า
- P99 latency ลดจาก 400ms เหลือ 40ms — จาก "ช้าจนรำคาญ" เป็น "ไม่รู้สึกอะไร"
- Memory ลดลง 30%
- PagerDuty alerts ลดลง 60% — ทีม on-call นอนหลับได้สบายขึ้นเยอะ
- GC pauses หายไปหมด — เพราะ Rust ไม่มี GC
ลองคิดดู — ทีม 2-3 คน 6 เดือน แก้ปัญหาที่ทำให้ on-call engineer เสีย sleep มาหลายปี นี่คือ ROI ที่ชัดเจนมาก
Cloudflare: 1 Trillion Requests, 70% Less CPU
Cloudflare ทำเรื่องที่ใหญ่กว่า — rewrite NGINX (reverse proxy ที่ใช้ทั่วโลก) ด้วย Rust ในชื่อ Pingora
Scale:
- Handle มากกว่า 1 ล้านล้าน (trillion) requests ต่อวัน
- Development ใช้เวลา 18 เดือน
- เป็นหนึ่งใน largest Rust deployments ในโลก
ผลลัพธ์:
- CPU ลดลง 70% — ทำ request เดียวกันใช้ CPU น้อยกว่า NGINX ถึง 70%
- Memory ลดลง 67% — จากการจัดการ memory ที่ efficient กว่ามาก
- P95 latency เร็วขึ้น 80ms — ลูกค้าของ Cloudflare ได้ประโยชน์โดยตรง
- "434 ปีของเวลา TLS handshake ที่ประหยัดได้ต่อวัน" — ตัวเลขที่ฟังดูบ้าแต่เป็นจริง เมื่อคูณจำนวน request ต่อวัน
- ประหยัดค่า server หลายสิบล้านดอลลาร์ต่อปี — CPU น้อยลง = server น้อยลง = ค่าไฟน้อยลง
สิ่งที่น่าสังเกตคือ Cloudflare ไม่ได้ rewrite ทุกอย่างในวันเดียว Pingora ค่อยๆ รับ traffic มากขึ้นเรื่อยๆ ระบบเดิมยังทำงานคู่กันในช่วง transition — Strangler Fig Pattern อีกแล้ว
Hugging Face: 20x Faster Tokenizer
Hugging Face เป็น platform ที่ developer ใช้ AI models ทั่วโลก ปัญหาของเขาคือ text tokenizer — ขั้นตอนแรกของ NLP ที่ต้องแปลงข้อความเป็นตัวเลขก่อนจะ feed เข้า model
Tokenizer เดิมเขียนด้วย Python ทำงานช้า เมื่อ dataset ใหญ่ขึ้นเรื่อยๆ กลายเป็น bottleneck ที่ทุกคนรู้สึกได้
วิธีแก้:
- Rewrite tokenizer ด้วย Rust
- ใช้ PyO3 bindings เพื่อให้เรียกจาก Python ได้เหมือนเดิม
- API ฝั่ง Python ไม่เปลี่ยน — developer ที่ใช้ Hugging Face library เรียก function เดิม parameter เดิม แค่ข้างในเป็น Rust
ผลลัพธ์:
- เร็วขึ้น 20 เท่า เทียบกับ pure Python version
- Developer ไม่ต้องเปลี่ยนโค้ดฝั่ง Python แม้แต่บรรทัดเดียว
นี่คือ Strangler Fig ที่ elegant ที่สุดรูปแบบหนึ่ง — เปลี่ยนข้างใน แต่ข้างนอกเหมือนเดิมทุกอย่าง
npm: 10x Auth Checks, 70% Fewer Servers
npm — package manager ของ JavaScript ที่ developer ทั่วโลกใช้ — มีปัญหากับระบบ authorization
ปัญหา:
- Auth check ทุกครั้งที่ install package ใช้เวลา 5-10 milliseconds
- ฟังดูน้อย แต่เมื่อคูณจำนวน request ต่อวัน มันกลายเป็น bottleneck ที่ต้องใช้ server จำนวนมาก
วิธีแก้:
- Rewrite authorization logic ด้วย Rust
ผลลัพธ์:
- เร็วขึ้น 10 เท่า — auth check ลดลงเหลือ sub-millisecond
- Server ลดลง 70% — ใช้เครื่องน้อยลงมาก
- Linear scaling บน multi-core — Rust ใช้ประโยชน์จาก CPU หลาย core ได้ดีกว่า
1Password: Memory Safety สำหรับ Crypto
1Password ใช้ Rust ด้วยเหตุผลที่ต่างออกไป — ไม่ใช่แค่ speed แต่เป็น memory safety
เมื่อ app จัดการ master password, encryption keys, secrets ของผู้ใช้ — memory bug ไม่ใช่แค่ crash แต่อาจเป็น security vulnerability ร้ายแรง
ผลลัพธ์:
- 63% code sharing ข้าม platform ทั้งหมด (Windows, macOS, Linux, iOS, Android) — จากเดิมที่แทบ 0%
- Memory safety สำหรับ crypto operations — compiler บังคับให้ handle memory อย่างถูกต้อง
- Crash reports ลดลงทันที — memory bug ที่เคยทำให้ app crash หายไป
วิธีทำ Python สู่ Rust แบบ Strangler Fig (Practical Guide)
จาก case study ทั้งหมดข้างบน สรุปเป็น step-by-step ได้ดังนี้:
Step 1: Profile ก่อน — หา Hot Paths
ก่อนจะเขียน Rust แม้แต่บรรทัดเดียว ต้อง profile ระบบเดิมให้รู้ว่าอะไรช้า อะไรกิน CPU อะไรกิน memory
เครื่องมือที่ใช้กันบ่อย:
- py-spy — sampling profiler สำหรับ Python ที่ไม่ต้องแก้โค้ด
- cProfile — built-in profiler ของ Python
- Prometheus + Grafana — สำหรับ monitor service-level metrics
สิ่งสำคัญ: อย่าเดา ว่าอะไรช้า ให้ข้อมูลบอก ทีม Dropbox ค้นพบว่า 20% ของโค้ดกิน 80% ของ CPU — ถ้าไม่ profile อาจจะไป optimize ส่วนที่ไม่ใช่ปัญหา
Step 2: เลือก 20% ที่กิน 80% CPU
จาก profiling data เลือก functions ที่:
- กิน CPU มากที่สุด
- เรียกบ่อยที่สุด (high frequency)
- มี input/output ที่ชัดเจน (ง่ายต่อการ rewrite)
- ไม่มี complex dependencies กับส่วนอื่น
ตัวอย่างงานที่ Rust ทำได้ดีกว่า Python มาก:
- Hashing / Cryptography — งาน byte-level
- Compression / Decompression — CPU intensive
- Data parsing — JSON, CSV, binary formats
- Image / Video processing — pixel manipulation
- Text tokenization — character-level processing
Step 3: เขียน Rust Module + Expose ผ่าน PyO3
PyO3 คือ library ที่ให้ Rust code ถูกเรียกจาก Python ได้เหมือน native Python module
flow คือ:
- เขียน Rust function ที่ทำสิ่งเดียวกับ Python function เดิม
- ใช้ PyO3 เพื่อ wrap เป็น Python module
pip install Rust module เข้ามาใน Python project
- เปลี่ยน import จาก Python module เดิมเป็น Rust module ใหม่
จากมุมมองของ Python code ที่เรียกใช้ — ไม่รู้ด้วยซ้ำว่าข้างในเป็น Rust ฟังดูเหมือน Hugging Face tokenizer ที่เล่าไปก่อนหน้า
Step 4: Shadow Test — รันทั้งคู่ เทียบผลลัพธ์
ก่อนจะ route traffic จริง ให้รันทั้ง Python และ Rust ขนานกัน:
- ส่ง input เดียวกันเข้าทั้งสองระบบ
- เทียบ output ว่าตรงกันไหม
- เทียบ performance ว่าดีขึ้นจริงไหม
- ทำแบบนี้อย่างน้อย 1-2 สัปดาห์กับ production traffic จริง
ถ้า output ไม่ตรงกัน — ต้องหาว่าทำไม อาจเป็น edge case ที่ Rust code ยังไม่ handle
Step 5: Route Traffic ด้วย Feature Flag
ใช้ feature flag เพื่อค่อยๆ route traffic:
- เริ่ม 1% → monitor → ถ้า OK → 5% → 10% → 25% → 50% → 100%
- ถ้าพบปัญหา — ปิด flag กลับไปใช้ Python ทันที
- ใช้เวลากี่สัปดาห์ก็ได้ ไม่ต้องรีบ
Step 6: Monitor, Iterate, Expand
หลังจาก module แรก stable แล้ว:
- บันทึก metrics ก่อน/หลัง (latency, CPU, memory, error rate)
- คำนวณ ROI — ประหยัดค่า infra เท่าไหร่ ลด alert เท่าไหร่
- นำ metrics ไปเสนอทีมและผู้บริหาร
- เลือก module ถัดไปที่จะ migrate
Step 7: Python กลายเป็น Orchestration Layer
เมื่อ migrate hot paths ไปเรื่อยๆ สิ่งที่เหลืออยู่ใน Python คือ:
- Business logic ที่ไม่ต้องการ performance สูง
- Orchestration — เรียก Rust modules ตามลำดับ
- Configuration และ glue code
- Prototype/experimentation code
Python กับ Rust อยู่ร่วมกันได้ดี ไม่ต้องเลือกข้างใดข้างหนึ่ง
5 ข้อผิดพลาดที่ต้องระวัง
จาก case study และประสบการณ์ทั้งหมด นี่คือ 5 mistakes ที่เจอบ่อยที่สุด:
1. Rewrite ทุกอย่างในคราวเดียว (Big-Bang Fallacy)
นี่คือ mistake อันดับ 1 ที่เห็นซ้ำแล้วซ้ำเล่า ทีมตื่นเต้นกับ technology ใหม่ อยาก rewrite ทั้งระบบ แล้วก็ติดอยู่ในโปรเจกต์ที่ไม่มีวัน deliver
ทำแทน: เลือก 1-2 modules ที่ให้ ROI สูงสุด ทำให้เสร็จ เห็นผล แล้วค่อยขยาย
2. ไม่คิดเรื่อง Learning Curve
Rust ไม่ใช่ Python developer ต้องเรียนรู้ concepts ใหม่ เช่น ownership, borrowing, lifetimes ที่ไม่มีในภาษาอื่น ใช้เวลา 3-6 เดือนกว่าจะรู้สึก productive
ทำแทน: ลงทุนเวลาเรียน Rust อย่างจริงจัง เริ่มจาก side project เล็กๆ ก่อน ไม่ใช่เริ่มด้วย production system
3. ไม่วัดก่อน Migrate (No Baseline)
ถ้าไม่มี baseline metrics ก่อน migrate จะพิสูจน์ให้ใครเห็นว่า "ดีขึ้น" ได้ยังไง?
ทำแทน: วัดทุกอย่างก่อนเริ่ม — latency, CPU, memory, error rate, cost per request แล้วเทียบหลัง migrate
4. Migrate Business Logic ก่อน Hot Paths
บางทีมเริ่ม migrate business logic (ที่ไม่ได้ช้า) แทนที่จะเริ่มจาก hot paths (ที่ช้าจริงๆ) ได้ประโยชน์น้อย แต่เสียเวลาเยอะ
ทำแทน: ให้ profiler ตัดสินใจ ไม่ใช่ gut feeling เริ่มจากจุดที่ทำให้ระบบช้าที่สุด
5. ลืมเรื่อง Shared State / Data Stores
ระบบเก่าและใหม่มักจะใช้ database ร่วมกัน ถ้าไม่ plan ดีๆ เรื่อง data consistency จะเจอ bug ที่ debug ยากมาก
ทำแทน: วาง data migration plan ตั้งแต่ต้น ตัดสินใจให้ชัดว่า database จะ share หรือ migrate ด้วย
สำหรับทีม Enersys — เราใช้แนวคิดนี้ยังไง
ในฐานะ Software House ที่ทำงานกับลูกค้าหลายราย เราเห็นปัญหา "ระบบเก่าใช้ไม่ไหวแล้ว แต่ทิ้งก็ไม่ได้" เป็นประจำ
Strangler Fig กับ Odoo ERP:
หลายองค์กรมีระบบเก่าหลายตัวที่ทำงานแยกกัน — ระบบบัญชีตัวหนึ่ง, ระบบ HR อีกตัว, ระบบ inventory อีกตัว, Excel อีกกองหนึ่ง ข้อมูลกระจัดกระจาย ไม่ sync กัน
วิธีที่เราทำคือใช้หลักการเดียวกับ Strangler Fig: ไม่ได้ยกเลิกทุกระบบพร้อมกัน แต่ค่อยๆ ย้ายทีละ module ไปที่ Odoo ERP ที่เป็นระบบกลาง
เริ่มจาก module ที่ critical ที่สุด ให้ระบบเก่ายังทำงานคู่กัน ลูกค้ายังใช้ได้ตามปกติ แล้วค่อยๆ route workflow ไปที่ระบบใหม่ เมื่อ module นั้น stable แล้วก็ไป module ถัดไป
AI Agents เป็น Facade:
ในบาง project เราใช้ AI agents เป็น "facade" ที่ route งานระหว่างระบบเก่าและใหม่ agent รับ request เข้ามา ตัดสินใจว่า request นี้ควรไประบบไหน ส่งไป แล้ว return ผลกลับมา — ลูกค้าไม่รู้ด้วยซ้ำว่ามีหลายระบบทำงานอยู่ข้างหลัง
PDPA Considerations:
เรื่องที่หลายคนมองข้ามในการ migrate ระบบคือ data privacy
- ข้อมูลส่วนบุคคลอยู่ที่ไหน? — ระหว่าง migrate ข้อมูลอาจอยู่ทั้งในระบบเก่าและใหม่ ต้องมั่นใจว่า comply กับ PDPA ทั้งสองที่
- Data retention — ระบบเก่าที่จะ decommission อาจมีข้อมูลที่ต้องเก็บตาม compliance ต้อง plan ว่าจะ archive ยังไง
- Consent management — ถ้าระบบใหม่มี purpose ในการใช้ข้อมูลต่างจากระบบเก่า อาจต้องขอ consent ใหม่จากลูกค้า
- Data sovereignty — ข้อมูลต้องอยู่ใน jurisdiction ที่ถูกต้อง ถ้า migrate ไป cloud ต้องดูว่า server อยู่ที่ไหน
Migration ไม่ใช่แค่เรื่อง technical — มันเป็นเรื่อง business, legal, และ people ด้วย
สรุป
Strangler Fig Pattern ไม่ใช่แค่ "วิธี migrate ระบบ" — มันเป็นปรัชญาที่ว่า การเปลี่ยนแปลงที่ดีไม่จำเป็นต้องเป็น revolution มันอาจจะเป็น evolution
สิ่งที่ case study ทั้งหมดมีร่วมกัน:
- เริ่มจากปัญหาจริง ไม่ใช่ hype — ทุกบริษัทมี pain point ที่ชัดเจน (latency, CPU, memory, cost) ก่อนจะตัดสินใจ migrate
- วัดก่อนทำ — ทุกทีมมี baseline metrics แล้วเทียบผลหลัง migrate
- เลือก scope ที่เหมาะสม — ไม่มีใคร rewrite ทั้งระบบ ทุกทีมเลือก module หรือ function ที่ให้ ROI สูงสุด
- Execute แบบ gradual — ค่อยๆ ย้าย ค่อยๆ เรียนรู้ ค่อยๆ ขยาย
- ROI ที่วัดได้ — ผลลัพธ์ไม่ใช่ "ดีขึ้น" แบบลอยๆ แต่เป็นตัวเลข: 10x latency, 70% CPU, $1M savings
สำหรับองค์กรที่กำลังเผชิญกับ "ระบบเก่าที่อยากทิ้ง" — ลองถามตัวเองก่อน:
- อะไรคือ pain point จริงๆ? (latency? cost? scalability? security?)
- วัดได้ไหม? (baseline metrics)
- ส่วนไหนของระบบที่เป็น bottleneck? (profile first)
- สามารถวาง facade/proxy ได้ไหม? (interceptability)
- มี module ที่ self-contained พอจะเริ่มได้ไหม? (first target)
ถ้าตอบคำถามพวกนี้ได้ คุณก็พร้อมที่จะเริ่ม Strangler Fig ของคุณเอง
อย่า rewrite ทุกอย่าง ค่อยๆ เติบโตรอบระบบเดิม แล้ววันหนึ่ง ระบบเก่าจะหายไปเอง — เหมือนต้นไม้ในป่าดิบชื้นที่ถูก Strangler Fig โอบรัด
แหล่งข้อมูล