46 คะแนน โดย GN⁺ 2025-07-10 | 18 ความคิดเห็น | แชร์ทาง WhatsApp
  • แชร์ประสบการณ์การสร้างระบบเปรียบเทียบแผนประกันสุขภาพ Medicare ของสหรัฐฯ ขึ้นใหม่ โดยใช้ เทคโนโลยีที่ผ่านการพิสูจน์แล้วเท่านั้น (เช่น Postgres, golang, React) ในโครงสร้างที่เรียบง่าย เพื่อรองรับ คำขอเว็บมากกว่า 1 พันล้านครั้ง ได้อย่างเสถียร
  • ออกแบบสถาปัตยกรรมโดยมี ความเรียบง่ายและเสถียรภาพ เป็นเป้าหมาย จนทำได้ทั้ง ความเร็วตอบสนองเฉลี่ยต่ำกว่า 10ms และ อัตราเหตุขัดข้องที่ต่ำมาก
  • ใช้ innovation token อย่างประหยัดเฉพาะกับการแยกโครงสร้างหลักที่จำเป็น (โมดูลขนาดใหญ่ 3 ตัว, การสื่อสารผ่าน gRPC) และเลือกใช้แนวทางที่น่าเบื่อแต่เชื่อถือได้สำหรับทุกอย่างที่เหลือ
  • สร้างองค์ประกอบด้านปฏิบัติการทั้งหมดตั้งแต่ การจัดการ DB schema, ETL pipeline, testing, logging, documentation, CLI tool ให้ทำซ้ำได้และเรียบง่าย จนกลายเป็น ระบบที่ทั้งทีมเข้าใจและดูแลรักษาได้ง่าย
  • แสดงให้เห็นอย่างชัดเจนว่า การควบคุมคุณภาพอย่างต่อเนื่องและการทำงานเป็นทีมที่แข็งแกร่ง ใช้ได้ผลแม้ในโปรเจกต์ภาครัฐขนาดใหญ่

Serving a billion web requests with boring code

ภาพรวมระดับสูง

  • เป็นหัวหน้านักพัฒนาของเว็บไซต์ภาครัฐสหรัฐฯ สำหรับเปรียบเทียบและซื้อแผน Medicare ตลอดระยะเวลา สองปีครึ่ง
  • รองรับ API request เฉลี่ยวันละ 5 ล้านครั้ง โดยมีความเร็วตอบสนองเฉลี่ยต่ำกว่า 10ms และ 95% ของคำขอใช้เวลาไม่เกิน 100ms
  • อัตราเหตุขัดข้องต่ำมาก จนกรณีที่วิศวกรจริง ๆ ต้องถูกปลุกตอนเช้ามืดมีนับได้ไม่กี่ครั้ง
  • ใช้เฉพาะเทคโนโลยีที่ผ่านการพิสูจน์และใคร ๆ ก็เข้าใจได้อย่าง Postgres, golang, React เพื่อสร้างระบบที่เสถียรได้อย่างต่อเนื่อง

Boring über alles

  • หลักการสำคัญสูงสุดคือให้ความสำคัญกับ "เทคโนโลยีที่น่าเบื่อแต่ผ่านการพิสูจน์แล้ว" ก่อนเสมอ (Choose Boring Technology)
  • ความพยายามเชิงนวัตกรรมจะใช้ innovation token อย่างประหยัดเฉพาะในจุดที่จำเป็นจริง ๆ
  • ชอบ เทคโนโลยีและกระบวนการที่เสถียรและชัดเจน มากกว่าวิธีแก้ปัญหาที่ซับซ้อนและหวือหวา

ส่วนที่น่าเบื่อ

  • Postgres: แกนหลักของการจัดเก็บข้อมูล ตอบโจทย์ทั้งความน่าเชื่อถือและการขยายระบบ แม้แต่การค้นหาที่ซับซ้อน (เช่น faceted search) ก็แก้ด้วย Postgres
  • golang: build และ deploy ได้รวดเร็ว ได้ binary ที่ชัดเจน การจัดการ error ตรงไปตรงมา และสมาชิกใหม่ในทีมก็ปรับตัวได้ง่าย
  • React: เป็นเฟรมเวิร์ก SPA ที่ผ่านการพิสูจน์มากที่สุด และสมาชิกทีมก็คุ้นเคยอยู่แล้ว เรื่อง accessibility และการรองรับอุปกรณ์หลากหลายก็เป็นปัจจัยสำคัญเช่นกัน
    • ในระยะยาวมีปัญหาเรื่อง bundle size และความเร็วที่ลดลง แต่ในบริบทตอนนั้นถือเป็นตัวเลือกที่ดีที่สุดสำหรับการส่งมอบผลลัพธ์ให้ทันเวลา

innovation tokens

  • Modular backend: แบ็กเอนด์ทั้งหมดไม่ได้เป็นทั้ง microservices หรือ monolith แต่จัดเป็น 3 โมดูลขนาดใหญ่ (druginfo, planinfo, beneinfo)
    • แต่ละโมดูลใช้ Postgres DB แยกกัน และแชร์ข้อมูลกันผ่าน gRPC เท่านั้น
    • druginfo: ทำดัชนีข้อมูลราคายาอย่างละเอียดมาก ซึ่งมีจำนวนชุดผสมของร้านยา ประกัน และบรรจุภัณฑ์เพิ่มขึ้นแบบทวีคูณ จึงต้องมีการ preprocess ที่ซับซ้อนและการปรับแต่งประสิทธิภาพ
    • planinfo: รับข้อมูล CMS ชุดใหม่ทุกวัน แล้วสร้าง DB ใหม่ทั้งก้อนเพื่อใช้งาน (รักษาความเป็น immutable)
    • beneinfo: เป็นส่วนเดียวที่เก็บข้อมูลผู้สมัครจริง โดยเก็บ PII ที่อ่อนไหวให้น้อยที่สุด และใส่ใจทั้งด้านการออกแบบและการปฏิบัติการเพื่อลดความเสี่ยงข้อมูลรั่วไหล
  • gRPC: มีข้อดีที่สามารถนิยามอินเทอร์เฟซการสื่อสารระหว่างโมดูลอย่างชัดเจนในรูปแบบโค้ด และเชื่อมกับเครื่องมืออัตโนมัติได้ดีมาก
    • แต่ก็พบข้อเสียเช่น build, tooling และ debugging ที่ซับซ้อน และความตรงไปตรงมาที่น้อยกว่าเมื่อเทียบกับ JSON API
    • ใช้ grpc-gateway เพื่อรองรับเว็บไคลเอนต์และรับทราฟฟิกปริมาณมากได้อย่างไม่มีปัญหา

การคงความเข้ากันได้ย้อนหลังอย่างเข้มงวด

  • รักษา backwards compatibility ของทั้ง API และฐานข้อมูล อย่างเคร่งครัด
    • ฟิลด์ของ public API จะไม่ถูกลบเด็ดขาด และจะคงไว้ตลอดไปตราบใดที่ไม่มีปัญหาด้านความปลอดภัย
    • คอลัมน์ใน DB สามารถเพิ่มได้อิสระ แต่การลบต้องผ่านหลายขั้นตอนในการตรวจสอบ (ลบการอ้างอิง → รอหลายสัปดาห์ → ค่อยลบจริง)
  • วินัยนี้กลายเป็นฐานสำคัญที่ทำให้เปลี่ยนแปลงได้เร็ว พร้อมกับ deploy และปฏิบัติการได้อย่างเสถียร

Faceted search

  • สร้าง faceted search ด้วย Postgres เพียงอย่างเดียวแทน ElasticSearch
    • ใช้ฟังก์ชันยาว 250 บรรทัดเพียงตัวเดียวที่ประกอบเงื่อนไขบน plan table ที่ทำดัชนีไว้อย่างดี เพื่อจัดการ logic การค้นหาทั้งหมด
    • โฟกัสที่ความต้องการทางธุรกิจ และแก้ปัญหาอย่างเรียบง่ายโดยไม่เพิ่มความซับซ้อนที่ไม่จำเป็น

Database

  • creation

    • จัดการ DB schema ด้วยไฟล์ .sql ที่มีหมายเลขกำกับ และโหลดตามลำดับเพื่อรับประกันความน่าเชื่อถือ
    • DB ของ planinfo/beneinfo ถูกสร้างใหม่ทุกวัน จึงไม่ต้องทำ migration และหากมีความผิดพลาดในการตั้งค่า เช่น version ไม่ตรงกัน ก็ออกแบบให้แอปไม่เริ่มทำงานเลย
  • ETL

    • ใช้ shell script แยกตามแต่ละ data source เพื่อโหลดขึ้น S3 → จากนั้น EC2 instance ที่รันด้วย cron จะดึง ETL code/data ล่าสุดมาสร้าง RDS DB ใหม่
    • ใช้ คำสั่ง COPY ของ Postgres อย่างจริงจัง เพื่อจัดการโหลดข้อมูลจำนวนมากได้มีประสิทธิภาพกว่าการใช้ INSERT
    • ใช้เวลาเพียง 2~4 ชั่วโมงต่อวันในการสลับข้อมูลระดับหลายร้อยล้านแถวไปยัง DB ใหม่
  • models

    • ใช้ไลบรารี xo เพื่อสร้าง DB model อัตโนมัติ และใช้ custom template เพื่อให้ได้โค้ดที่เหมาะกับทีม
  • testing

    • ความผิดพลาดที่ใหญ่ที่สุด คือการทำเทสต์ด้วย sqlmock มากเกินไป จนเมื่อข้อมูลเปลี่ยนบ่อย การดูแลรักษากลายเป็นเรื่องยุ่งยากมาก
    • ถ้าเป็น DB จริงที่ immutable การทดสอบกับฐานข้อมูลจริงน่าจะมีประสิทธิภาพมากกว่า
  • Local database for development

    • มีสคริปต์ที่สร้างข้อมูลบางส่วนของแต่ละตารางแบบอัตโนมัติ ทำให้นักพัฒนาแต่ละคนสามารถทดสอบและพัฒนาบน local DB ขนาดเล็กที่อิงจากข้อมูลจริงได้
    • หากเตรียมเครื่องมือนี้ไว้ก่อนที่ DB จะใหญ่เกินไป จะช่วยเพิ่มประสิทธิภาพการพัฒนาของทั้งทีมได้อย่างมาก

Miscellaneous tooling

  • พัฒนา CLI tool เป็น shell script เพื่อทำงานอัตโนมัติด้านปฏิบัติการและการสังเกตการณ์หลายรูปแบบ โดยรวม utility ทั้งหมดไว้จัดการในที่เดียว
  • พัฒนาและใช้งานเครื่องมือที่ยึดหน้างานจริงอย่างจริงจัง เช่น การแปลง splunk log เป็นกราฟได้ทันทีผ่านคำสั่งใน Slack

Logging

  • สร้าง request id ตั้งแต่จุดที่รับคำขอเข้ามา และแนบ id นี้ไว้กับ log context ทั้งหมดเพื่อให้ตามรอยได้จากทุกที่
  • ออกแบบระบบ logging ที่ปลอดภัยและเป็นระเบียบด้วย zerolog
  • บันทึก log ที่จำเป็นไว้ในทุกช่วงเวลาสำคัญ เช่น จุดเข้า-ออกของระบบ และสถานการณ์ผิดปกติ

Documentation

  • แปลงเอกสาร GitHub markdown ด้วย sphinx-book-theme แล้วนำไปใช้งานเป็น wikibook
  • สมาชิกทีมทุกคนมีส่วนร่วมกับการทำเอกสารอย่างจริงจัง เพื่อให้สามารถค้นหาเอกสารของระบบทั้งหมดได้จากที่เดียว
  • วัฒนธรรมการทำเอกสารที่ยอดเยี่ยมช่วยเพิ่มการเติบโตของทีม การดูแลรักษา และประสิทธิภาพในการ onboarding สมาชิกใหม่อย่างมาก

Runtime integrations

  • พยายามอธิบายให้ลูกค้าลดคำขอที่ทำให้ประสิทธิภาพฝั่งไคลเอนต์ลดลง (เช่น การฝัง analytics script) ให้เหลือน้อยที่สุด
  • เปลี่ยนการประมวลผล query จาก browser runtime ไปเป็น build-time เพื่อรักษาประสิทธิภาพของบริการ
  • ในความเป็นจริงไม่สามารถกันคำขอของลูกค้าได้ทั้งหมด และบางส่วนก็ส่งผลให้ประสิทธิภาพลดลง

And more

  • นอกจากเรื่องเทคนิคแล้ว ยังย้ำว่า บรรยากาศทีมที่เป็นบวกและร่วมมือกัน รวมถึงแรงจูงใจที่แข็งแกร่ง คือแรงขับเคลื่อนที่แท้จริงของความสำเร็จของระบบขนาดใหญ่
  • นี่เป็นกรณีศึกษาที่ทำให้เห็นพลังของการตัดสินใจเชิงปฏิบัติเล็ก ๆ แต่สำคัญ และการควบคุมคุณภาพอย่างสม่ำเสมอ

18 ความคิดเห็น

 
bugprone 2025-08-03

เรื่องน่าเบื่อแบบนี้ตั้ง 2.5 ปีเลยเหรอ?!

 
kimjj81 2025-07-14

สงสัยว่า Faceted Search คืออะไรเลยตามไปอ่าน แล้วก็มีเรื่องให้อ่านต่ออีกพอสมควรเลยครับ

https://www.cybertec-postgresql.com/en/faceting-large-result-sets/
https://roaringbitmap.org/about/
https://github.com/cybertec-postgresql/pgfaceting

 
choijaekyu 2025-07-13

ความเห็นเกี่ยวกับคำว่า "น่าเบื่อ" น่าสนใจดีนะครับ 555 ถ้าจะเปลี่ยนเป็นคำอื่น คำไหนจะดีกว่ากัน? แบบเดิม ๆ, ธรรมดา ๆ?

 
aqqnucs 2025-07-13

การแปล boring ว่า "น่าเบื่อ" ทำให้ความหมายดั้งเดิมหายไปมากเกินไปหน่อย boringness เป็นหนึ่งในปรัชญาการออกแบบของ Go ครับ

 
iolothebard 2025-07-11

ทำเป็นน่าเบื่อสุด ๆ…

 
sinbumu 2025-07-11

เกาหลีนี่เป็นประเทศแห่ง Java แบบเปิดเรื่อง-กลางเรื่อง-จบเรื่องด้วย Java เลย เลยรู้สึกแปลกกันนิดหน่อย 555

 
ethanhur 2025-07-10

ผมคิดว่า golang กับ React ต่างก็เป็นภาษาเขียนโค้ดเชิงองค์กรแบบ "boring" ของยุคใหม่ทั้งหมด

คำว่า boring -> แปลเป็น "น่าเบื่อ" ก็ไม่ได้ตรงความหมาย 100% เลย ทำให้ดูเหมือนว่านัยของคำนี้จะสื่อไปถึงผู้อ่านชาวเกาหลีได้ไม่ครบถ้วน

 
white9s 2025-07-10

อยากอยู่ในโลกที่น่าเบื่อของ Postgres, golang และ React

 
helio 2025-07-11

จริงครับ พอเห็นชื่อเรื่องแล้วก็คิดว่าเป็นมุกอะไรสักอย่าง

 
riki3 2025-07-10

ดูเหมือนว่าในต่างประเทศจะมองว่านั่นเป็นสแตกที่น่าเบื่อ
จริง ๆ แล้ว Go ก็เป็นตัวเลือกที่ง่ายที่สุดสำหรับการทำเว็บเซิร์ฟเวอร์อยู่แล้ว..

เหมือนว่าต้องพัฒนาด้วยภาษาอย่าง Rust หรือสาย FP ถึงจะไม่ถูกมองว่าน่าเบื่อ

 
kandk 2025-07-10

เรื่องที่ธรรมดามาก ๆ .. ธรรมดาเสียจนมองข้ามประเด็นสำคัญที่กำลังพลาดไป..

 
vwjdalsgkv 2025-07-10

สแต็กระดับนี้ดูเหมือนจะไม่ได้ชวนเบื่อขนาดนั้นนะครับ ถ้าจะให้น่าเบื่อจริง ๆ อย่างน้อยก็น่าจะต้องเป็น Java เวอร์ชันต่ำกว่า 1.8 หรือไม่ก็ VB ประมาณนั้น... เป็นความคิดที่ค่อนข้างลบหลู่แวบเข้ามาในหัวครับ

 
beoks 2025-07-10

ข้อดีของความธรรมดา (ซึ่งมีข้อจำกัดมาก) คือเรารู้ความสามารถของสิ่งเหล่านี้เป็นอย่างดี แต่ที่สำคัญกว่านั้นคือ เราเข้าใจรูปแบบความล้มเหลวของมันเป็นอย่างดี

ในต้นฉบับมีลิงก์ที่เกี่ยวกับ boring อยู่ แต่เมื่อดูเนื้อหาแล้ว ดูเหมือนว่า boring ในที่นี้จะหมายถึง คุ้นเคยมากเกินไป

 
beoks 2025-07-10

มีคำที่เหมาะสมกว่านี้อย่าง experienced, verified, skillful อยู่แล้ว แต่กลับจงใจใช้คำว่า boring ดูเหมือนมีเจตนาเพื่อเรียกกระแส

 
cocofather 2025-07-10

มันอาจไม่ได้หมายถึงว่าน่าเบื่อตอนเขียน แต่อาจจะหมายถึงว่าเป็นสแต็กแบบเดิมๆ ที่ใช้บ่อยมากจนเริ่มเบื่อหรือเปล่าครับ

 
dongjinahn 2025-07-10

เคอร์เนลลินุกซ์ประมาณ 2.6.29...

 
kandk 2025-07-10

แค่บอกว่าใช้ gRPC ก็แบบ.. 555

 
click 2025-07-10

ผมก็เริ่มจากคิดเหมือนกันว่า golang น่าเบื่อเหรอ?
ถ้าเป็นระดับ classic asp ก็คงพอจะเรียกว่าน่าเบื่อได้อยู่หรอก