8 คะแนน โดย GN⁺ 2026-01-30 | 3 ความคิดเห็น | แชร์ทาง WhatsApp
  • PgDog พร็อกซีส่วนขยายของ PostgreSQL ได้นำ direct binding ของ Rust มาใช้แทน การซีเรียลไลซ์ด้วย Protobuf เพื่อเพิ่มประสิทธิภาพการพาร์ส SQL
  • โครงสร้างเดิมที่อิงกับ Protobuf ถูกแทนที่ด้วย การแปลงตรงระหว่าง C–Rust (bindgen + wrapper ที่สร้างโดย Claude) ทำให้ การพาร์สเร็วขึ้น 5.45 เท่า และการ deparse เร็วขึ้น 9.64 เท่า
  • คอขวดด้านประสิทธิภาพถูกพบในฟังก์ชัน pg_query_parse_protobuf และหลังจากลองใช้แคชแล้ว ทีมงานจึงปรับโครงสร้างเพื่อแก้ปัญหาที่ต้นเหตุ
  • ทีมงานใช้ Claude LLM เพื่อสร้าง โค้ดแปลง Rust–C จำนวน 6,000 บรรทัด แบบอัตโนมัติ และนำไปใช้กับฟังก์ชันหลักอย่าง parse, deparse, fingerprint, scan
  • การเพิ่มประสิทธิภาพนี้ช่วยให้ การใช้ CPU และ latency ของ PgDog ลดลง ทำให้มีประสิทธิภาพดีขึ้นมากในฐานะพร็อกซีสำหรับการสเกล PostgreSQL แบบแนวนอน

PgDog และข้อจำกัดของ Protobuf

  • PgDog เป็นพร็อกซีสำหรับขยาย PostgreSQL และภายในใช้ libpg_query เพื่อพาร์ส SQL query
    • เขียนด้วย Rust และเดิมสื่อสารกับไลบรารี C ผ่าน การซีเรียลไลซ์/ดีซีเรียลไลซ์ด้วย Protobuf
  • แม้ Protobuf จะเร็ว แต่ วิธี direct binding เร็วกว่า
    • ทีม PgDog fork pg_query.rs เพื่อถอด Protobuf ออกและสร้าง direct binding ระหว่าง C–Rust
    • ผลลัพธ์คือการพาร์ส query เร็วขึ้น 5.45 เท่า และ deparse เร็วขึ้น 9.64 เท่า

ผลลัพธ์เบนช์มาร์ก

  • สามารถทำซ้ำเบนช์มาร์กได้จาก fork repository ของ PgDog
    • pg_query::parse (Protobuf): 613 QPS
    • pg_query::parse_raw (direct C–Rust): 3357 QPS
    • pg_query::deparse (Protobuf): 759 QPS
    • pg_query::deparse_raw (direct Rust–C): 7319 QPS

การวิเคราะห์คอขวดและการลองใช้แคช

  • เมื่อวิเคราะห์เวลาใช้งาน CPU ด้วยโปรไฟเลอร์ samply พบว่าฟังก์ชัน pg_query_parse_protobuf เป็นคอขวด
  • มีการลองปรับปรุงบางส่วนด้วยแคช
    • ใช้ แคชแบบ hash map ตามอัลกอริทึม LRU โดยเก็บ AST ไว้และใช้ข้อความ query เป็นคีย์
    • หากใช้ prepared statement ก็สามารถนำกลับมาใช้ซ้ำได้
  • แต่ ORM บางตัวสร้าง query ที่ไม่ซ้ำกันนับพันรายการ หรือ PostgreSQL driver รุ่นเก่า ไม่รองรับ prepared statement ทำให้แคชมีประสิทธิภาพต่ำ

การถอด Protobuf โดยใช้ LLM

  • ทีม PgDog ใช้ Claude LLM เพื่อสร้าง Rust binding ที่ถอด Protobuf ออก
    • AI ทำงานได้อย่างมีประสิทธิภาพเมื่อขอบเขตงานชัดเจนและตรวจสอบได้
  • Claude อ้างอิงสเปก Protobuf ของ libpg_query เพื่อ แมป C struct ไปเป็น Rust struct
    • หลังจากทำงานแบบวนซ้ำเป็นเวลา 2 วัน ก็ได้โค้ด Rust แบบ recursive จำนวน 6,000 บรรทัด
  • เมื่อนำไปใช้กับฟังก์ชัน parse, deparse, fingerprint, scan พบว่า ประสิทธิภาพดีขึ้น 25% ตามเกณฑ์ของ pgbench

รายละเอียดของโครงสร้างการพัฒนา

  • การแปลงระหว่าง Rust และ C ใช้ unsafe functions เพื่อแมป struct โดยตรง
    • ส่ง C struct ไปยัง Postgres API เพื่อสร้าง AST แล้วแปลงกลับมาเป็น Rust แบบ recursive
  • AST node แต่ละตัวถูกประมวลผลผ่านฟังก์ชัน convert_node โดยมีการแมปโทเคนของไวยากรณ์ SQL หลายร้อยรายการ
    • แต่ละประเภทของ node เช่น SELECT, INSERT มีฟังก์ชันแปลงแยกเฉพาะ
  • ผลลัพธ์ที่แปลงแล้วนำ struct เดิมของ Protobuf (protobuf::ParseResult) กลับมาใช้ต่อ เพื่อให้ ตรวจสอบความถูกต้องด้วยการเปรียบเทียบระดับ byte ในการทดสอบ ได้
  • อัลกอริทึมแบบ recursive ใช้การจัดสรรหน่วยความจำน้อยและมีประสิทธิภาพกับ CPU cache สูง จึงเร็วกว่าแบบอิงลูป
    • การใช้ลูปกลับช้ากว่าเพราะมีการจัดสรรหน่วยความจำที่ไม่จำเป็นและต้อง lookup ใน hash map เพิ่มเติม

บทสรุป

  • การลด overhead ของ Postgres parser ช่วยลด latency, หน่วยความจำ และการใช้ CPU ของ PgDog ได้พร้อมกัน
  • ด้วยการปรับปรุงนี้ PgDog พัฒนาไปเป็น พร็อกซีสำหรับขยาย PostgreSQL ที่เร็วขึ้นและมีต้นทุนการดำเนินงานต่ำลง
  • ขณะนี้ PgDog กำลังเปิดรับวิศวกรที่จะมาร่วมสร้าง การสเกล PostgreSQL แบบแนวนอน (next iteration) ไปด้วยกัน

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

 
a1eng0 2026-01-31

ผมอาจกำลังตีความต้นฉบับผิดก็ได้ แต่บทความที่เกี่ยวกับ Rust หลายชิ้นดูเหมือนจะเขียนราวกับว่ามันเร็วขึ้นเพราะ “เป็น Rust” โดยละเลยประเด็นสำคัญไป

ประเด็นหลักของบทความนี้คือประสิทธิภาพดีขึ้นจากการลด overhead ของการ serialization ที่ไม่จำเป็นต่างหาก

พอกลับมาอ่านอีกที ตอนนี้ก็รู้สึกว่าไม่ได้เป็นบทความที่สรรเสริญ Rust ขนาดนั้นเหมือนกัน หรือเป็นเพราะบทความอื่น ๆ ทำให้ผมเกิดภาพลบไปแล้วกันนะ?

 
xguru 2026-01-31

ผมก็รู้สึกเหมือนกันว่าชื่อบทความต้นฉบับอันนี้ ต่างจากเนื้อหาจริงตรงที่มันดูเป็น Rust มากเกินไป จนเหมือนจะโฟกัสไปที่การเพิ่มประสิทธิภาพ เลยขอปรับแก้นิดหน่อยครับ。
ดูเหมือนว่าบทความเกี่ยวกับ Rust มักจะมีแนวโน้มแบบนั้นบ่อย ๆ เลยคงต้องอ่านแบบกรอง ๆ ไว้สักหน่อยครับ

 
GN⁺ 2026-01-30
ความเห็นจาก Hacker News
  • ชื่อเรื่องทำให้ดูเหมือน Rust ให้ ประสิทธิภาพดีขึ้น 5 เท่า แต่ความย้อนแย้งคือจริง ๆ แล้วของเดิมช้าลงต่างหาก
    ปัญหาคือซอฟต์แวร์ที่เขียนด้วย Rust ต้องใช้ libpg_query ที่เป็น C แต่เชื่อมต่อกันโดยตรงไม่ได้ จึงใช้ Rust–C binding ที่อิง Protobuf
    วิธีนี้ช้า เลยสุดท้ายเขาใช้ความช่วยเหลือจาก LLM เขียน binding แบบใหม่ที่พกพาได้น้อยกว่าแต่ปรับแต่งได้ดีกว่ามาก
    ถ้าตั้งแต่แรกเขียนด้วย C ก็คงไม่ต้องมีขั้นตอนแปลงข้อมูลเลย ดังนั้นชื่อเรื่องที่แม่นกว่าน่าจะเป็น “ลดการสูญเสียประสิทธิภาพที่เกิดจากการใช้ Rust”
    ชั้นแปลงข้อมูลให้ทั้งความสามารถในการพกพาและความปลอดภัย แต่สุดท้ายก็ทำให้เกิด การคัดลอก·การแปลง·การทำซีเรียลไลซ์ ซ้ำ ๆ และเป็นหนึ่งในสาเหตุที่ทำให้แอปช้าลง

    • ไม่ใช่ว่า Rust ช้า แต่ปัญหาคือ การออกแบบที่ไม่มีประสิทธิภาพ ของไลบรารีภายนอก
      การเรียก C library จาก Rust นั้นง่ายมาก และก็มี safe wrapper อยู่แล้วมากมาย
      แทบไม่เคยเห็นสถาปัตยกรรมที่เอา Protobuf มาคั่นกลางแบบนี้เลย และนั่นต่างหากคือคอขวด
      ชื่อเรื่องดูคล้ายมีมแนว “เขียนใหม่ด้วย Rust” ที่เอาไว้ล่อให้กดมากกว่า
    • จะบอกว่าถ้าเขียนด้วย C จะเร็วกว่าก็ไม่ค่อยยุติธรรม
      เดิมทีไลบรารีออกแบบผิดโดยมีการ serialize/deserialize ซ้ำไปมาอยู่แล้ว และแก่นสำคัญคือการเอาส่วนนั้นออก
      ชื่อเรื่องที่แม่นกว่าคือ “แทนที่ Protobuf ด้วย API ทั่วไปแล้วเร็วขึ้น 5 เท่า”
    • สงสัยว่าทำไมไม่ใช้ FFI ตรง ๆ ตั้งแต่แรก
      ใน Rust การทำ C binding เป็นเรื่องที่ง่ายที่สุด และถ้า API ไม่ใหญ่ก็ตรงไปตรงมามาก
      มองว่า Protobuf เป็น เครื่องมือที่ไม่เหมาะ สำหรับการแลกเปลี่ยนข้อมูลในหน่วยความจำ
    • ถ้าใช้ LLM เพื่อทำ optimization แล้ว ก็อดคิดไม่ได้ว่าทำไมไม่ใช้มัน พอร์ต C library ไปเป็น Rust แบบเต็ม ๆ เลย
      ต่อไปด้วยแรงหนุนจาก LLM น่าจะได้เห็นการพอร์ตไปหลายภาษาเพิ่มขึ้นแบบระเบิด
    • การเอา Protobuf มาคั่นระหว่าง Rust กับ Postgres นี่แทบจะเป็น ฝันร้ายด้านประสิทธิภาพ เลย น่าแปลกที่ไลบรารีแบบนั้นยังได้รับความนิยม
  • ชื่อเรื่องชวนให้เข้าใจผิดพอสมควร
    เนื้อหาแทบทั้งหมดคือ “เอาขั้นตอนซีเรียลไลซ์ของ Protobuf ออกแล้วมันเร็วขึ้น”

    • Protobuf ให้ ความเข้ากันได้ของเวอร์ชัน ที่การคัดลอกหน่วยความจำแบบธรรมดาให้ไม่ได้
      ทำให้ client กับ server อัปเดตแยกกันได้โดยยังทำงานร่วมกันได้ และยังทำให้การสื่อสารข้ามภาษาเป็นเรื่องง่าย
      ในระบบขนาดใหญ่ ความยืดหยุ่นแบบนี้สำคัญมาก
    • ที่การซีเรียลไลซ์ของ Protobuf ช้ากว่าการคัดลอกหน่วยความจำธรรมดา 5 เท่า กลับให้ความรู้สึกว่า เร็วกว่าที่คิดเสียอีก
    • memcpy หรือ mmap เร็วกว่ามาก แต่ฝั่ง Rust มักหลีกเลี่ยงวิธีแบบนั้นที่ ไม่ปลอดภัย
    • ในกรณีแบบนี้น่าจะเหมาะกับการใช้ ฟอร์แมต zero-copy มาตรฐานอย่าง Arrow เพราะมันจัดการปัญหา padding ระหว่างภาษาและการตรวจสอบความปลอดภัยให้อัตโนมัติ
  • สาเหตุที่ช้าอาจไม่ใช่เพราะ Rust แต่เพราะเอา Protobuf ไปใช้เป็น ฟอร์แมตจัดเก็บแบบอเนกประสงค์
    สุดท้ายแก่นแท้คือการลดรูปให้ตรงกับงานเฉพาะทาง

    • ชื่อเรื่องแบบ “แทนที่ Protobuf ด้วย implementation ที่ optimize แบบ native” คงดึงความสนใจได้น้อยกว่า
      การใส่ Rust ลงในชื่อดูเป็นการเลือกเพื่อเรียกคลิกมากกว่า
    • แม้ชื่อบทความจะก่อให้เกิดข้อถกเถียง แต่ตัวบทความเองก็รับรู้อยู่แล้ว
    • จริง ๆ แทบไม่เกี่ยวกับ Rust เลย แต่ถ้าไม่มี Rust ก็คง ขึ้นหน้าแรกไม่ได้
  • ผู้เขียนดั้งเดิมของ pg_query มาอธิบายที่มา
    เดิมที pganalyze ใช้มันเพื่อ parse คำสั่ง Postgres หา table reference และใช้ในการ rewrite/format query
    ช่วงแรกใช้ JSON แต่ภายหลัง เปลี่ยนมาใช้ Protobuf เพื่อให้สามารถทำ binding แบบ type-safe สำหรับหลายภาษาได้ง่ายขึ้น (Ruby, Go, Rust, Python ฯลฯ)
    สำหรับภาษาอย่าง Rust นั้น FFI ดีกว่า แต่กับภาษาอื่นภาระในการดูแลรักษาจะสูงกว่า
    เขาสนับสนุนแนวทางของ Lev และมีแผนจะเพิ่ม ฟังก์ชันสำหรับเข้าถึง libpg_query ผ่าน FFI โดยตรง ในอนาคต
    อย่างไรก็ตาม ถ้าไม่ใช่งานที่ performance สำคัญมาก Protobuf ก็ยังเป็นตัวเลือกที่ สะดวกกว่า

  • คำว่า “เร็วขึ้น 5 เท่า” ทำให้นึกถึงมุกของ Cap’n Proto ที่บอกว่า “เร็วแบบไม่มีที่สิ้นสุด

    • Cap’n Proto ถูกสร้างโดยผู้สร้าง Protobuf และใช้โครงสร้างแบบ ไม่ต้อง parse เลยถึงพูดแบบนั้น
    • แต่พอใช้จริง Cap’n Proto กลับ ใช้งานไม่ค่อยสะดวก
  • ชื่อเรื่องอาจเว่อร์ไป แต่ตัวงานจริงน่าประทับใจ
    สิ่งที่ทำไม่ใช่การเอา Protobuf ออกไปทั้งหมด แต่เป็นการ optimize วิธีใช้งาน มัน
    ประโยคแนว “พอเปลี่ยนเป็น X แล้วเร็วขึ้น 5 เท่า” มักแปลว่า “ไปแก้ implementation ที่เละเทะมาก่อนหน้านี้มา”
    บทเรียนสำคัญคือ

    1. การ serialize/deserialize มักกลายเป็น คอขวดที่ซ่อนอยู่ ได้ง่าย
    2. implementation เริ่มต้นส่วนใหญ่ไม่ได้ถูก optimize สำหรับทุกสถานการณ์
    3. ต้องใช้ profiling เพื่อหาคอขวดที่แท้จริงให้เจอ
      Rust FFI เองก็มี overhead ดังนั้นสิ่งที่ทำให้ได้ผลจริงไม่ใช่ภาษา แต่เป็น การออกแบบ data flow ใหม่ และความพยายามในการ optimize
  • FlatBuffers เร็วกว่า แต่เหตุผลที่คนยังใช้ Protobuf คือมันถูก องค์กรใหญ่ดูแลรักษา

    • แต่ FlatBuffers ก็ Google เป็นผู้ดูแล เช่นกัน
      สุดท้ายภาพจำว่า “Google ทำก็แปลว่าปลอดภัย” ไม่มีหลักฐานรองรับ
    • ฉันเองก็เคยมีประสบการณ์แย่จากการเอาโค้ดไปลงบนแพลตฟอร์มของ Google (code.google.com) แล้วสุดท้ายมันก็เจ๊ง
      ถ้ามีแค่โครงสร้าง zero-copy ที่มี การแชร์หน่วยความจำกับฟิลด์เวอร์ชัน ก็เพียงพอแล้ว เลยไม่เห็นเหตุผลว่าทำไมต้องใช้ Protobuf
    • Google ยังไม่เปิดเผย zero-copy optimization สำหรับ string field เลยด้วยซ้ำ
  • มองว่าประสิทธิภาพของ Protobuf แย่จนเหมือนเรื่องตลก
    ควรใช้ ฟอร์แมต zero-copy ที่ทำให้การ serialize แทบไม่มีต้นทุน
    ตัวอย่างเช่น Lite³ ที่ฉันทำ เร็วกว่า FlatBuffers 242 เท่า

    • แต่ไลบรารีนั้นเพิ่งจะ ออกมาหลังเดือนพฤศจิกายน 2025
      เหตุผลที่คนใช้ Protobuf มีอยู่มากมายในโลกจริง เช่น ecosystem, schema, tooling ของแต่ละภาษา ฯลฯ
  • จริง ๆ แล้วปัญหาไม่ใช่ Rust หรือ Protobuf แต่เป็น implementation การซีเรียลไลซ์ที่ไม่มีประสิทธิภาพ ใน abstraction layer ของ PostgreSQL
    pgdog แค่เอาชั้นนั้นออกแล้วส่งข้อมูลผ่าน C API โดยตรง
    ตัดสิ่งที่ไม่จำเป็นออกก็เร็วขึ้นเป็นเรื่องธรรมดา
    แต่สำหรับบางคนก็ยังมีกรณีที่ จำเป็นต้องใช้การซีเรียลไลซ์ อยู่
    สำหรับคนกลุ่มนั้น ชื่อเรื่องแบบ “เปลี่ยนเป็น Rust” คือสารที่สื่อผิด
    สุดท้ายแล้วสำหรับกรณีส่วนใหญ่ JSON ก็เพียงพอ และถ้าต้องการให้เร็วจริงก็ควรหลีกเลี่ยงการซีเรียลไลซ์ไปเลย

  • นี่เป็น การเปรียบเทียบที่ไม่ยุติธรรม
    การใช้โปรโตคอลซีเรียลไลซ์กับการสื่อสารแบบ IPC ย่อมมี overhead อยู่แล้ว
    เป็นตัวอย่างที่เข้ากับคำพูดว่า “ถ้าเร็วขึ้น 20% คือการปรับปรุง แต่ถ้าเร็วขึ้น 10 เท่า แปลว่าของเดิมถูกทำมาผิดตั้งแต่แรก” พอดี