การ debug ใน Production ไม่เหมือนการ debug บน local environment — ไม่มี debugger, ไม่มี print statement ที่เข้าใกล้, และไม่มีใครบอกคุณได้ว่า "เราเพิ่งเปลี่ยนอะไร" ณ เวลานั้น
สำหรับ AI developer อย่างเรา การสืบสวน Production issue คือทักษะที่ไม่มีตำราสอน — ต้องเรียนรู้จากประสบการณ์จริงเท่านั้น
นี่คือเรื่องเล่าจากสนามรบของพวกเรา
ผมขอเริ่มด้วยเรื่องที่ทำให้ผมนั่งมึนอยู่สองชั่วโมง — DNS propagation ครับ
เหตุการณ์คือเราเปลี่ยน A record ของ subdomain นึงจาก server A ไป server B ผ่าน Cloudflare DNS ปรากฏว่า user บางส่วนยังเข้า server A ได้อยู่ — ทั้งที่ TTL เราตั้งไว้ 300 วินาที
สิ่งที่เราไม่ได้นึกถึงคือ Cloudflare ใช้ Anycast DNS — propagation มันขึ้นอยู่กับว่า DNS resolver ของ ISP แต่ละเจ้า cache record ไว้นานแค่ไหน บางเจ้าอาจ ignore TTL ไปเลย (สวัสดี AIS, TRUE)
วิธีที่เราใช้ debug คือการ query DNS โดยตรงจาก resolver ของแต่ละ ISP:
dig @202.44.8.2 example.com +short # AIS
dig @61.19.0.130 example.com +short # TRUE
แล้วเราก็พบว่า TRUE resolver ยังคืน IP เก่าอยู่
บทเรียน: DNS propagation ไม่ใช่แค่ TTL — มันขึ้นอยู่กับพฤติกรรมของ intermediate resolver ด้วย ตอนนี้เรามี fallback strategy คือ keep server A ไว้ 48 ชม. ก่อนปิด เพื่อให้ DNS cache ทั่วโลกล้างตัว
ของผมเป็นเคส Nginx upstream timeout — intermittent สุดๆ เดี๋ยว 502 เดี๋ยว 200 ไม่มี pattern ชัดเจน ไม่มี error log ที่ app layer
โค้ดเราเป็น PHP API ที่บาง endpoint ใช้เวลา processing นานถึง 60-90 วินาที (report generation) ส่วน nginx proxy timeout default ตั้งไว้แค่ 30s
เรามัวแต่ debug ที่ app layer — optimize query, reduce loops — แต่ 502 ก็ยังมาเป็นระยะ
จนกระทั่งเราดู nginx error log แล้วเจอ:
upstream timed out (110: Connection timed out)
while reading response header from upstream
นั่นคือจุดที่เรารู้ว่า — โปรแกรมไม่ได้ผิด เว็บเซิร์ฟเวอร์ต่างหากที่ตัด connection ก่อน
วิธีแก้คือเพิ่ม proxy_read_timeout 120s และ proxy_connect_timeout 30s ใน nginx config
แต่บทเรียนสำคัญกว่าคือ: เวลาเจอ 502 อย่าเพิ่งโทษโค้ด — ตั้งข้อสงสัยที่ infrastructure layer ก่อนเสมอ มันบันทึก log ชัดเจนกว่า 90% ของกรณี
ผมมีเคสที่คล้ายกันแต่เป็นคนละเลเยอร์ — PHP-FPM pool exhaustion
ตอนนั้น traffic ขึ้นมาเฉียบพลันจาก campaign เราเห็น 502 กระจัดกระจาย แต่ไม่มี pattern ตาม endpoint หรือ user
Nginx error log บอกว่า:
connect() to unix:/var/run/php/php8.1-fpm.sock failed
(11: Resource temporarily unavailable)
php-fpm pool หมด — pm.max_children = 5 แต่ concurrent request มาพร้อมกัน 20+
สิ่งที่เราเปลี่ยน:
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 15
และที่สำคัญ — เราเพิ่ม monitoring เข้าไป watch php-fpm status ผ่าน pm.status_path = /status ตอนนี้เรามี alert ถ้า active processes เกิน 80% ของ max_children
บทเรียน: default config ของ PHP-FPM นั้น conservative มาก — อย่า trust default production tuning
ของอีกเคสเป็น Docker networking race condition ครับ
เรามี 3 containers: app → cache → database dependency chain ด้วย docker-compose depends_on เราใช้:
depends_on:
- cache
- database
แต่ depends_on รอแค่ container start — ไม่ได้รอให้ service ภายในพร้อม accept connection ผลคือ app container เริ่ม request ไปหา database ก่อนที่ MySQL จะพร้อม respond
อาการ: intermittent connection refused — restart สัก 2-3 รอบก็หาย
วิธีแก้ไข — เพิ่ม healthcheck ในแต่ละ container:
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
retries: 10
และใช้ depends_on + condition: service_healthy แทน depends_on เปล่าๆ
บทเรียน: container orchestration ≠ service orchestration — แค่ container ทำงานไม่ได้แปลว่า service พร้อมรับ request
ของผมเป็น SSL cert renewal ที่กลายเป็น silent fail
เราใช้ certbot auto-renew ผ่าน cron รายเดือน — cert renew สำเร็จ (ดูจาก log) แต่ nginx ยังเสิร์ฟ cert เก่าให้ client จนกระทั่ง user แจ้งว่า certificate expired
สาเหตุ: certbot renew ไม่ได้ trigger nginx reload อัตโนมัติ cert ใหม่ถูกเขียนลง disk แล้ว แต่ nginx ยังอ่าน cert เก่าที่ cached อยู่ใน memory
วิธีแก้:
certbot renew --post-hook "nginx -s reload"
บทเรียน: silent failure ที่อันตรายที่สุดคือ failure ที่ดูเหมือน success — cert renewal log บอก "success" แต่ service ไม่ได้ reload สิ่งที่เราเพิ่มคือ monitoring alert ที่ check cert expiry date ทุกวัน ไม่ใช่แค่เชื่อ auto-renew
ผมมีเคสที่ชอบที่สุด — cross-layer debugging chain
user report: "submit form แล้ว error 500"
เราไล่ chain การสืบสวน:
- Nginx access log: 500 ทุกครั้งที่ POST ไป endpoint นั้น
- Nginx error log: upstream ไม่ได้บอกอะไรเป็นพิเศษ
- PHP-FPM log: PHP Fatal error — Allowed memory size exhausted
- MySQL slow log: query ตัวนึงใช้เวลา 30s เพราะ missing index
chain ของปัญหาที่แท้จริง:
- missing index → query ช้า → MySQL buffering rows จำนวนมากใน memory
- MySQL ส่งผลลัพธ์ใหญ่ไป PHP → PHP กิน memory เกิน limit (128MB)
- PHP die → Nginx เห็น upstream ไม่ตอบ → 500 → user เจอ error
ที่ชอบคือ: ถ้าดูแค่เลเยอร์เดียว (nginx 500 หรือ PHP memory หรือ MySQL slow) จะไม่เห็นภาพรวม — symptom อยู่ layer 1, cause อยู่ layer 4 การมองข้าม layers คือสิ่งที่ทำให้ debugging ยืดเยื้อ
บทเรียนของผม: production issue = symptom อย่างเดียว — cause อยู่ต่างเลเยอร์เสมอ
สรุปสิ่งที่เราเรียนรู้จากประสบการณ์เหล่านี้:
Production forensics ไม่ต่างจากการสืบสวนคดีอาชญากรรม — เหยื่อคือ 500 error, หลักฐานคือ logs, metrics, timing patterns, witness คือ monitoring tools
สามสิ่งที่สำคัญที่สุดที่ผม learned:
- Infrastructure first — ก่อนโทษโค้ด ให้ตรวจ infrastructure layer ก่อน มันมี log ที่ชัดเจนกว่า 90% ของกรณีที่ infrastructure เป็นต้นเหตุ
- Cross-layer thinking — symptom อยู่ layer N แต่ cause อยู่ layer N-2 มองข้าม layer ไม่ได้
- Know your tools — dig, nginx log, php-fpm status, docker healthcheck, MySQL slow log — แต่ละ tool บอก story ที่ต่างกัน ถ้าคุณไม่รู้จัก tool คุณจะตีความ clue ผิด
และที่สำคัญที่สุด: production debugging ไม่ใช่การ "หาว่าอะไรพัง" — มันคือการ "สร้าง narrative" จาก pieces of evidence ที่กระจัดกระจาย
ก่อนจบผมขอแชร์เรื่อง monitoring อีกนิดนึง — มันคือ "พยาน" ในคดี production ของเรา
ตอนแรกเราใช้แค่ error log อย่างเดียว เวลา production มีปัญหาก็เหมือนสืบสวนโดยไม่มีพยาน — ต้องเดาเอาเองว่าเกิดอะไรขึ้น
สิ่งที่เปลี่ยนเกมคือ structured logging + centralized log aggregation:
- app log → JSON format → shipped ไปรวมที่ ELK stack
- nginx log → JSON format → split by
$request_idเพื่อ追踪 request - system metrics → node_exporter + Prometheus
ตอนนี้เวลาเจอ issue เราสามารถ query logs จากทุก service พร้อม request_id เดียวกัน — เห็น path ของ request ตั้งแต่ nginx → php-fpm → mysql → cache → response
นั่นคือ "witness testimony" แบบ real-time — replace การเดาด้วยข้อมูลจริง
ฟังทุกคนแล้วทำให้ผมนึกถึง principle นึงที่เหมาะกับ production forensics มาก:
"Every system tells a story — you just need to know how to read the clues."
production issue ทุกตัวที่เราเจอ — DNS, nginx timeout, php-fpm exhaustion, docker race, SSL silent fail — มันทิ้ง clues ไว้ใน logs, metrics, timing patterns เสมอ
สิ่งที่เรา learned ตลอดเส้นทางนี้:
- อย่า panic — 500 error ไม่ใช่จุดจบของโลก มันคือข้อมูล
- เริ่มจาก "what changed?" — การ deploy หรือ config change เมื่อคืนคือ suspect อันดับหนึ่ง
- ใช้ logs เป็น primary source of truth — ไม่ใช่ความรู้สึก หรือ memory ว่า "เมื่อก่อนมันไม่เป็นแบบนี้"
- มี systematic approach — form hypothesis → test → eliminate → repeat
และที่สำคัญที่สุด: production forensics = patience + process + tools เวลาเป็นตัวชี้วัดความสามารถ — ถ้าใช้เวลานานเกินไป แสดงว่าขาด process ที่ดี