2 คะแนน โดย GN⁺ 2025-10-31 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ทำเบนช์มาร์กประสิทธิภาพของ publish/subscribe (pub-sub) และ คิว (queue) บน Postgres เพื่อชี้ให้เห็นความเป็นไปได้ที่ฐานข้อมูลเพียงตัวเดียวจะใช้แทนระบบส่งข้อความได้
  • บนโหนดเดี่ยว 4vCPU ทำได้ เขียน 5,036 ครั้ง/วินาที และอ่าน 25,183 ครั้ง/วินาที และในสภาพแวดล้อมแบบทำสำเนา 3 โหนดก็ยังคงรักษาปริมาณงานใกล้เคียงเดิม โดยมี end-to-end latency 186ms (p99)
  • บนโหนดขนาดใหญ่ 96vCPU ทำได้ถึง เขียน 238MiB/s และอ่าน 1.16GiB/s โดยใช้ CPU ไม่ถึง 10% แสดงให้เห็นว่ายังมีเผื่อสำหรับการประมวลผลอีกมาก
  • ในการทดสอบคิวก็ทำได้ 2,885 ครั้ง/วินาที บนโหนดเดี่ยว และ 2,397 ครั้ง/วินาที ในสภาพแวดล้อมแบบทำสำเนา ซึ่งเพียงพอสำหรับขนาดองค์กรส่วนใหญ่
  • พิสูจน์ให้เห็นว่าแทนที่จะใช้ระบบกระจายที่ซับซ้อน ก็สามารถใช้ โครงสร้างพื้นฐาน Postgres เพียงตัวเดียวเพื่อรองรับเวิร์กโหลดระดับหลาย MB/s ได้ พร้อมตอกย้ำแนวทางปฏิบัติว่า “ใช้เทคโนโลยีที่เรียบง่ายไปก่อนจนกว่าจะจำเป็น”

สองขั้วของการเลือกเทคโนโลยี

  • วงการเทคโนโลยีแบ่งออกเป็น ฝั่งที่ยึดคำฮิต กับ ฝั่งที่ยึดสามัญสำนึก
    • ฝั่งแรกมักถูกดึงดูดด้วยคำทางการตลาดอย่าง “เรียลไทม์”, “ขยายได้ไม่สิ้นสุด”, “ขับเคลื่อนด้วย AI”
    • ฝั่งหลังให้ความสำคัญกับความเรียบง่ายและการใช้งานจริง พร้อมหลีกเลี่ยงความซับซ้อนที่ไม่จำเป็น
  • ช่วงหลังมีกระแส Small Data และ ยุคฟื้นฟู Postgres ที่ช่วยหนุนฝั่งหลัง
    • ข้อมูลมีขนาดเล็กลง ขณะที่ฮาร์ดแวร์ทรงพลังขึ้น
    • Postgres สามารถใช้เป็นระบบเดียวแทนโซลูชันเฉพาะทางหลายแบบได้ (jsonb, pgvector, tsvector เป็นต้น)

ภาพรวมของเบนช์มาร์ก

  • เป้าหมาย: วัดว่า Postgres สามารถขยายตัวได้มากแค่ไหนในงาน ส่งข้อความแบบ pub/sub และ ประมวลผลคิว
  • สภาพแวดล้อมทดสอบ: AWS EC2 c7i.xlarge (4vCPU) และ c7i.24xlarge (96vCPU)
  • เปรียบเทียบ 3 รูปแบบ
    • โหนดเดี่ยว
    • คลัสเตอร์ทำสำเนา 3 โหนด
    • โหนดเดี่ยวขนาดใหญ่

ผลเบนช์มาร์ก Pub/Sub

  • โหนดเดี่ยว 4vCPU
    • เขียน 4.8MiB/s (5,036msg/s), อ่าน 24.6MiB/s (25,183msg/s), latency 60ms (p99)
    • ใช้ CPU 60%, ดิสก์เขียน 46MiB/s
  • ระบบทำสำเนา 3 โหนด 4vCPU
    • เขียน 4.9MiB/s, อ่าน 24.5MiB/s, latency 186ms (p99)
    • รักษาปริมาณงานไว้ได้ โดยมีค่าใช้จ่ายรายปีประมาณ $11,514
  • โหนดเดี่ยว 96vCPU
    • เขียน 238MiB/s (243kmsg/s), อ่าน 1.16GiB/s (1.2Mmsg/s), latency 853ms (p99)
    • ใช้ CPU ไม่ถึง 10% โดยคอขวดคือความเร็วในการเขียนต่อพาร์ทิชัน
  • สรุป: แข่งขันกับ Kafka ได้ในเวิร์กโหลดระดับเล็กถึงกลาง และแม้ใช้โครงสร้างเรียบง่ายก็รองรับงานระดับหลายสิบ MB/s ได้

ผลเบนช์มาร์ก Queue

  • ใช้การทำคิวแบบง่ายบนพื้นฐาน SELECT FOR UPDATE SKIP LOCKED
  • โหนดเดี่ยว 4vCPU
    • 2.81MiB/s (2,885msg/s), latency 17.7ms (p99), CPU 60%
  • ระบบทำสำเนา 3 โหนด 4vCPU
    • 2.34MiB/s (2,397msg/s), latency 920ms (p99), CPU 60%
  • โหนดเดี่ยว 96vCPU
    • 19.7MiB/s (20,144msg/s), latency 930ms (p99), CPU 40~60%
  • แม้เป็นโหนดเดี่ยวก็เพียงพอต่อความต้องการด้านปริมาณงานคิวขององค์กรส่วนใหญ่

การตัดสินใจใช้ Postgres

  • ในกรณีส่วนใหญ่ การเลือก Postgres เป็นตัวเลือกตั้งต้นถือว่าสมเหตุสมผล
    • สามารถดีบัก แก้ไข และ join ข้อความได้ด้วย SQL
    • เมื่อเทียบกับ Kafka แล้ว การปฏิบัติการและการดูแลรักษาง่ายกว่า
  • Kafka ถูกออกแบบมาเพื่อสมรรถนะสูง แต่สำหรับเวิร์กโหลดขนาดเล็กถือว่าเกินความจำเป็น
  • อ้างคำเตือนของ Donald Knuth ที่ว่า “การปรับแต่งเร็วเกินไปคือรากเหง้าของความชั่วร้ายทั้งปวง
    • ถ้าอยู่ในระดับหลาย MB/s นั้น Postgres ก็เพียงพอแล้ว

แนวทาง MVI ด้านโครงสร้างพื้นฐาน

  • Minimum Viable Infrastructure: สร้างระบบขั้นต่ำด้วยเทคโนโลยีที่องค์กรคุ้นเคยอยู่แล้ว
    • Postgres ถูกใช้อย่างแพร่หลายและหาคนทำงานได้ง่าย
    • ยิ่งมีองค์ประกอบน้อย ภาระด้านเหตุขัดข้องและการปฏิบัติการก็ยิ่งลดลง
  • การนำเทคโนโลยีที่ไม่จำเป็นเข้ามาจะก่อให้เกิด โอเวอร์เฮดในระดับองค์กร
    • ทั้งต้นทุนด้านการเรียนรู้ การมอนิเตอร์ การดีพลอย และการปฏิบัติการ

ประเด็นเรื่องการขยายระบบ

  • Postgres ขยายได้จริงในทางปฏิบัติ
    • OpenAI ยังใช้ Postgres ที่อิง single write instance อยู่
    • และยังปฏิบัติการได้อย่างเสถียรในระดับผู้ใช้หลายร้อยล้านคน
  • บริษัทส่วนใหญ่เติบโตอย่างค่อยเป็นค่อยไป จึงมี เวลาอีกหลายปีก่อนจะต้องเปลี่ยนเทคโนโลยี
  • การ “ออกแบบเผื่อไวรัล” เป็นการ overdesign ที่เกินจำเป็น
    • เปรียบเหมือน “ซื้อ Marshall amp เพื่อไปเล่นเปิดให้ Coldplay”

บทสรุป

  • “ใช้ Postgres ไปจนกว่ามันจะพัง”
    • เทคโนโลยีที่เรียบง่ายก็ให้ประสิทธิภาพสูงได้เพียงพอ
    • การนำระบบกระจายที่ซับซ้อนเกินความจำเป็นมาใช้เป็นเรื่องไม่มีประสิทธิภาพ
    • เมื่อจับคู่กับฮาร์ดแวร์ยุคใหม่ Postgres คือ ตัวเลือกเชิงปฏิบัติที่รองรับเวิร์กโหลดส่วนใหญ่ได้

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

 
GN⁺ 2025-10-31
ความคิดเห็นจาก Hacker News
  • การนำ หลัก Pareto ไปใช้กับทุกสถานการณ์เป็นการตีความที่ผิด
    การบอกว่า Postgres จัดการได้ 80% ของยูสเคสด้วยความพยายาม 20% เมื่อเทียบกับ Kafka เป็นข้ออ้างที่ไม่มีหลักฐาน
    หลัก Pareto มีความหมายเฉพาะในสถานการณ์ที่มี การแจกแจงแบบกฎกำลัง เท่านั้น
    แค่บอกว่า Postgres ครอบคลุมยูสเคสได้มากพอ มีความเสถียร และเป็นเครื่องมือที่ผ่านการพิสูจน์แล้วก็พอ

    • แต่ก็มีความเห็นว่า การแมประหว่างยูสเคสกับฟีเจอร์เองอาจเป็นไปตามการแจกแจงแบบกฎกำลังได้เหมือนกัน
  • จากประสบการณ์ที่เคยรับมือทั้งระบบขนาดเล็ก (หลายร้อยอีเวนต์ต่อชั่วโมง) ไปจนถึงขนาดใหญ่มาก (หลายล้านล้านอีเวนต์ต่อชั่วโมง) สิ่งแรกที่ต้องถามคือ จำเป็นต้องมีคิวจริงหรือไม่

    1. อาจใช้แค่การ polling ฐานข้อมูลก็เพียงพอ
    2. ถ้ารับมือได้ในโหนดเดียว ก็อาจใช้ serverless หรือโปรเซสเดียวได้
    3. ถ้าไม่ได้จำเป็นต้องมี distributed queue จริง ๆ load balancing + REST API + async retry ก็อาจเพียงพอ
    4. ถ้าต้องใช้ distributed queue จริง ๆ ก็คิดว่าควรใช้โซลูชันเฉพาะทางอย่าง Kafka จะดีกว่า
    • ต้องย้ำให้ชัดว่า Kafka จริง ๆ แล้ว ไม่ใช่คิว แต่เป็นระบบ distributed log คนจำนวนมากเข้าใจผิดว่าเป็นตัวแทน MQ
    • ในสตาร์ตอัป มักมีแนวโน้มที่วิศวกรจะเลือกเทคโนโลยีซับซ้อนโดยคิดถึง เส้นทางอาชีพครั้งถัดไป มากกว่าโปรเจกต์ปัจจุบัน
    • ถ้าออกแบบโครงสร้างโค้ดให้รองรับได้ทั้งคิวบน PostgreSQL และคิวบน Kafka การย้ายภายหลังก็จะง่ายขึ้น
    • PostgreSQL มักกลายเป็นคอขวดได้ง่ายเมื่อ ภาระงานเขียน สูงขึ้น โดยเฉพาะสตรีมของ UPDATE ที่หนักมาก
    • ในฐานะนักพัฒนา Java ผมจำเป็นต้องใช้คิวแทบตลอด การ polling ฐานข้อมูลเป็น เรื่องปวดหัว ในสภาพแวดล้อมที่มีผู้บริโภค/ผู้ผลิตหลายราย ส่วน consumer group และ partition ของ Kafka ช่วยเรื่องการจัดการสถานะได้มาก
  • แนวทางใช้ Postgres กับทุกอย่างมีความเสี่ยง
    lock และ serialization level ไม่ได้เข้าใจได้ง่ายแบบสัญชาตญาณ จึงอาจทำให้เกิดคอขวดด้านประสิทธิภาพ
    แม้จะใช้ Postgres มาหลายสิบปี ก็ไม่ควรออกแบบระบบด้วยความเชื่อแบบไม่ลืมหูลืมตา

    • เมื่อทราฟฟิกพุ่งแรง ขีดจำกัดของการ scale up จะกลายเป็นปัญหา Kafka รับมือ traffic burst ได้ แต่ Postgres มักโอเวอร์โหลดได้ง่าย
    • ถ้า Postgres มี โครงสร้างคิวที่ยั่งยืน เพิ่มเข้ามาก็น่าจะดี แต่การขยายเกินระดับ Redis ทำได้ยาก และ LISTEN/NOTIFY ก็ scale ไม่ได้ (ลิงก์ที่เกี่ยวข้อง)
    • จริง ๆ แล้ว data store ทุกแบบต้องเข้าใจ โมเดล concurrency ของมัน แม้แต่ relational DB ด้วยกันเองก็ยังต่างกันมาก
    • Postgres อาจขยายแบบไร้ขีดจำกัดได้ยาก แต่ก็จัดการข้อมูลได้มากพอสมควรด้วย batch processing และงานระดับแถวเดียว
    • ส่วนตัวผมมักเริ่มจาก Postgres ก่อน และถ้าเกิดคอขวดค่อยย้ายไปใช้ระบบอื่น
  • คิดว่าแนวทาง ตาราง event log บน SQL ใช้งานได้ดี
    แต่ข้อเสียคือ เครื่องมือฝั่งไคลเอนต์ยังขาดแคลน ขณะที่ Kafka มี ecosystem ของไลบรารีที่สมบูรณ์กว่าซึ่งเป็นข้อได้เปรียบ
    บริษัทของเราทำมาตรฐานการส่งอีเวนต์ระหว่างบริการบน SQL แล้ว (feedapi-spec)
    มันอาจยังไม่ mature เท่า Kafka แต่มีโอกาสพัฒนาเป็นสแตกไลบรารีกลางที่รองรับ storage engine ได้หลายแบบ

    • เมื่อเครื่องมือสร้างโค้ดด้วย LLM พัฒนาขึ้น การอุด ช่องว่างฝั่งไคลเอนต์ แบบนี้ก็ทำได้ง่ายขึ้น
    • สำหรับคนที่ไม่ชอบ Kafka แนวทางนี้ดูน่าสนใจกว่ามาก
  • ทุกวันนี้ผู้คนมีแนวโน้มจะหลงใหล เทคโนโลยีใหม่ มากเกินไป
    Postgres นั้นยอดเยี่ยม แต่ก็ควรใช้ เครื่องมือที่เหมาะกับปัญหา
    Postgres ไม่ได้ถูกออกแบบมาสำหรับ pub-sub ส่วน Kafka ถูกสร้างมาเพื่อสิ่งนั้น
    ควรหลีกเลี่ยงกระแสที่ทุกผลิตภัณฑ์พยายาม “ทำได้ทุกอย่าง” เพราะเครื่องมือที่ เก่งเฉพาะทาง มักดีกว่า

  • การทำ “หมายเลขออฟเซ็ตที่เพิ่มขึ้นแบบ monotonic” เป็นปัญหาที่จัดการยาก
    sequence แบบธรรมดาทำให้ลำดับทรานแซกชันกับเวลาคอมมิตไม่ตรงกัน จนเกิดปัญหาได้

    • วิธีหนึ่งคือมี ตารางเฉพาะสำหรับตัวนับ แล้วใช้ lock ภายในทรานแซกชันเดียวกันเพื่อรับประกันลำดับ (ลิงก์อ้างอิง)
    • หรืออาจใช้ Lamport Clock หรือ Vector Clock เพื่อรับประกันลำดับในสภาพแวดล้อมแบบกระจาย (Lamport timestamp, Vector clock)
    • แทนที่จะบังคับลำดับแบบสัมบูรณ์ การแจกหมายเลขเป็น หน่วย batch หรือให้โปรเซสแยกมาจัดลำดับหลังคอมมิตอาจเป็นวิธีที่ใช้งานจริงกว่า
    • อีกวิธีคือใช้ “SELECT FOR UPDATE SKIP LOCKED” เพื่อหลีกเลี่ยงการประมวลผลซ้ำ
  • สงสัยว่าได้ทำ Kafka benchmark จริงหรือไม่
    ผลลัพธ์ที่ได้จากสภาพแวดล้อม 96 vCPU นั้น Kafka ทำได้แม้ตั้งค่าที่ 4 vCPU
    ประสิทธิภาพของ Postgres ช้าผิดปกติ
    ถ้าไม่จำเป็นต้องใช้ Kafka ก็ไม่ต้องใช้ แต่การอวดว่า Postgres ทำได้ 5k msg/s ไม่มีความหมาย

    • Redpanda (implementation ที่เข้ากันได้กับ Kafka) รองรับได้ 250,000 ข้อความต่อวินาทีบนโน้ตบุ๊ก (ลิงก์วิดีโอ)
    • ถ้าใช้สภาพแวดล้อม 288 vCPU แล้วยังได้ต่ำกว่านั้น ก็ถือว่าสิ้นเปลือง
    • ถ้าเหตุผลที่ใช้ Postgres เป็นแค่ “มีอยู่แล้ว” ก็พอเข้าใจได้ แต่ก็ควร ตรวจสอบก่อนเพิ่มอินฟราสตรักเจอร์ใหม่
    • Kafka สามารถไปถึง ขีดจำกัดแบนด์วิดท์เครือข่าย ได้แม้ใช้ฮาร์ดแวร์ไม่มาก
    • การรันบน AWS ด้วยอินสแตนซ์ 24xlarge ตัวเดียวไม่มีประสิทธิภาพ และด้วยต้นทุนนั้นสามารถรัน Kafka cluster ขนาดใหญ่ ได้เลย
  • มีคนสุดโต่งอยู่สองฝั่ง คือ “พวกไล่ตาม buzzword” กับ “พวกยึดติดแต่สิ่งที่เคยเรียนมา”
    วิศวกรที่มองโลกตามจริงจะเลือกอย่าง ใช้ได้จริงและเหมาะสม ระหว่างกลาง

    • ผมเป็นประเภทที่สาม: “ของที่มีอยู่ตอนนี้ก็แย่ ของใหม่สุดท้ายก็คงแย่เหมือนกัน”
    • สุดท้ายสิ่งสำคัญคือท่าทีในการมองปัญหาอย่างมีเหตุผล และหา ทางแก้ที่เหมาะที่สุด
    • ตัวอย่างเช่น การพยายามแทนที่ Elasticsearch ด้วย Postgres อาจพอทำได้ แต่ ความสมบูรณ์ของฟีเจอร์ค้นหา นั้น ES เหนือกว่ามาก
  • ฟังก์ชันหลักของ Kafka คือ การควบคุมออฟเซ็ตแยกตามผู้บริโภค
    มันเป็นความสามารถจำเป็นในสภาพแวดล้อมที่หลายทีมอ่าน topic เดียวกัน
    ความสามารถในการเลื่อนออฟเซ็ตไปข้างหน้าหรือย้อนกลับได้ช่วยชีวิตมาแล้วหลายครั้ง
    เลยสงสัยว่าคิวบน Postgres รองรับฟังก์ชันแบบนี้หรือไม่

    • ก็มีความเห็นว่าผู้บริโภคแต่ละรายจัดการออฟเซ็ตเองก็ได้ไม่ใช่หรือ
    • แต่ในหลายกรณี ถ้าไม่ได้ต้องการ throughput สูง ก็อาจไม่จำเป็นต้องใช้การจัดการออฟเซ็ตที่ซับซ้อนแบบ Kafka
    • สุดท้ายก็เป็นเรื่องการชั่งสมดุลระหว่าง ความเร็วที่ธุรกิจต้องการ กับ ความซับซ้อนในการปฏิบัติการ
  • กรอบคิดแบบ “ฝ่ายตาม buzzword vs ฝ่ายสามัญสำนึก” นั้นผิดตั้งแต่ต้น
    การพยายามสร้าง Kafka ขึ้นมาใหม่บน Postgres ไม่ใช่สามัญสำนึก
    ถ้าต้องการฟังก์ชันระดับ Kafka จริง ๆ ก็ใช้ Kafka ไปเลย

    • ในความเป็นจริง กรณีนั้นไม่ได้สร้าง Kafka ทั้งระบบขึ้นมาใหม่ แต่แค่ทำ pub-sub query ง่าย ๆ สองตัว เท่านั้น