ไดรเวอร์ SQLite สำหรับ Go แสดงผลประสิทธิภาพที่น่าประหลาดใจ โดยแนวทาง stdin/stdout นำหน้าในหลายเบนช์มาร์ก

ทีมชุมชน BigGo
ไดรเวอร์ SQLite สำหรับ Go แสดงผลประสิทธิภาพที่น่าประหลาดใจ โดยแนวทาง stdin/stdout นำหน้าในหลายเบนช์มาร์ก

การเปรียบเทียบประสิทธิภาพแบบครอบคลุมของไดรเวอร์ SQLite เก้าตัวสำหรับภาษาโปรแกรม Go ได้เผยให้เห็นลักษณะประสิทธิภาพที่ไม่คาดคิด โดยเฉพาะการเน้นย้ำถึงแนวทางที่ไม่เป็นทางการซึ่งใช้การสื่อสารแบบ stdin/stdout กับ subprocess การประเมินนี้ได้ทดสอบไดรเวอร์ต่างๆ ในหลายสถานการณ์ ตั้งแต่การดำเนินงานแบบกลุ่มง่ายๆ ไปจนถึงภาระงานแบบ concurrent ที่ซับซ้อน

ไดรเวอร์ SQLite ที่ทำการทดสอบ:

  • bvinc: ใช้ CGO เป็นฐาน ไม่รองรับ database/sql
  • cznic: ใช้ CGO เป็นฐาน ไม่รองรับ database/sql
  • eaton: ใช้ CGO เป็นฐาน ไม่รองรับ database/sql
  • gwenn: Pure Go รองรับ database/sql
  • mattn: ใช้ CGO เป็นฐาน รองรับ database/sql (มาตรฐานหลัก)
  • modernc: Pure Go รองรับ database/sql (โค้ด C ที่แปลงเป็น Go)
  • numine: Pure Go รองรับ database/sql (ใช้ WASM เป็นฐาน)
  • sqinn: Pure Go ไม่รองรับ database/sql (subprocess แบบ stdin/stdout)
  • zombie: Pure Go ไม่รองรับ database/sql (เขียนใหม่จาก modernc)

โซลูชัน Pure Go ท้าทายแนวทาง CGO แบบดั้งเดิม

ผลการเปรียบเทียบแสดงให้เห็นว่าการใช้งานแบบ pure Go เช่น modernc.org/sqlite ตอนนี้เป็นทางเลือกที่เป็นไปได้แทนโซลูชันแบบ CGO ดั้งเดิม การพัฒนานี้แก้ไขจุดเจ็บปวดที่มีมานานสำหรับนักพัฒนา Go ที่ต้องการ cross-compile แอปพลิเคชันสำหรับแพลตฟอร์มต่างๆ ความต้องการ CGO มักจะทำให้กระบวนการ build ซับซ้อน โดยเฉพาะเมื่อกำหนดเป้าหมายเป็นระบบปฏิบัติการหรือสถาปัตยกรรมที่แตกต่างกัน

นักพัฒนาหลายคนในชุมชนได้รายงานความสำเร็จกับไดรเวอร์ modernc ในสภาพแวดล้อมการใช้งานจริง โดยชื่นชมความน่าเชื่อถือและความง่ายในการปรับใช้ ไดรเวอร์ทำงานโดยการ transpile โค้ด C ของ SQLite ไปเป็น Go โดยตรง ทำให้ไม่จำเป็นต้องใช้ CGO ในขณะที่ยังคงความเข้ากันได้กับอินเทอร์เฟซ database/sql มาตรฐาน

CGO: ฟีเจอร์ใน Go ที่อนุญาตให้โปรแกรม Go เรียกใช้โค้ด C แต่ต้องการคอมไพเลอร์ C ในระหว่างกระบวนการ build

ผู้ชนะที่ไม่คาดคิด: แนวทาง stdin/stdout

บางทีผลลัพธ์ที่น่าประหลาดใจที่สุดมาจาก sqinn ซึ่งเป็นไลบรารีที่สื่อสารกับ SQLite ผ่าน subprocess แยกต่างหากโดยใช้ stdin และ stdout streams แนวทางนี้มีประสิทธิภาพเหนือกว่าการใช้งานแบบดั้งเดิมหลายตัวอย่างสม่ำเสมอในเบนช์มาร์กต่างๆ แม้จะมีค่าใช้จ่ายที่เห็นได้ชัดของการสื่อสารระหว่างกระบวนการ

ข้อได้เปรียบด้านประสิทธิภาพน่าจะเกิดจากวิธีที่ระบบปฏิบัติการจัดการ stream buffers เมื่อเปรียบเทียบกับค่าใช้จ่ายการจัดสรรหน่วยความจำในการเรียก API โดยตรง เมื่อใช้ stdin/stdout ระบบจะจัดการ send และ receive buffers โดยอัตโนมัติ ซึ่งอาจลดรอบการจัดสรรและยกเลิกการจัดสรรที่เกิดขึ้นกับ CGO bindings แบบดั้งเดิม

อย่างไรก็ตาม แนวทางนี้มาพร้อมกับการแลกเปลี่ยน แอปพลิเคชันที่ใช้ sqinn ต้องการการแจกจ่าย SQLite binary แยกต่างหากควบคู่ไปกับ Go executable ซึ่งทำลายข้อได้เปรียบของ Go ในการแจกจ่ายแบบ single-binary ในระดับหนึ่ง นอกจากนี้ การเข้าถึงฐานข้อมูลแบบ concurrent จะต้องจัดการ subprocesses หลายตัว

ประสิทธิภาพในโลกแห่งความจริงแตกต่างกันตามกรณีการใช้งาน

การเปรียบเทียบได้ทดสอบหกสถานการณ์ที่แตกต่างกัน ตั้งแต่การ bulk inserts ง่ายๆ ไปจนถึงการดำเนินงานแบบ concurrent ที่ซับซ้อน ผลลัพธ์แตกต่างกันอย่างมีนัยสำคัญขึ้นอยู่กับประเภทของภาระงาน โดยไม่มีไดรเวอร์ตัวเดียวที่ครองทุกหมวดหมู่ ความแตกต่างนี้เน้นย้ำถึงความสำคัญของการทดสอบไดรเวอร์กับความต้องการของแอปพลิเคชันเฉพาะแทนที่จะพึ่งพาเพียงเบนช์มาร์กทั่วไป

สำหรับนักพัฒนาที่ต้องรับมือกับความท้าทายของ cross-compilation โซลูชัน pure Go เสนอทางเลือกที่น่าสนใจ สมาชิกชุมชนคนหนึ่งได้กล่าวถึงความสำเร็จของพวกเขาในการเปลี่ยนจากไลบรารีแบบ CGO ไปสู่การใช้งานแบบ pure Go โดยเฉพาะเพื่อแก้ไขปัญหา FreeBSD cross-compilation บน macOS

หมวดหมู่ของ Benchmark:

  • Simple: การ insert ผู้ใช้ 1 ล้านคนในธุรกรรมเดียว + query ทั้งหมด
  • Real: ผู้ใช้ 100 คน บทความคนละ 20 บทความ ความคิดเห็น 20 รายการต่อบทความ (ธุรกรรมแยกกัน)
  • Complex: ผู้ใช้ 200 คน บทความ 20,000 บทความ ความคิดเห็น 400,000 รายการ พร้อม query แบบ JOIN ที่ซับซ้อน
  • Many: ผู้ใช้ N คนทำการ insert + query ซ้ำ 1,000 ครั้ง (จำลองการอ่านข้อมูลหนัก)
  • Large: ผู้ใช้ 10,000 คนพร้อมเนื้อหา N bytes (จำลองฐานข้อมูลขนาดใหญ่)
  • Concurrent: ผู้ใช้ 1 ล้านคน + N goroutines ทำการ query (จำลองการอ่านข้อมูลแบบ concurrent)

การยอมรับ SQLite ในการใช้งานจริงที่เพิ่มขึ้น

นอกเหนือจากการอภิปรายเกี่ยวกับประสิทธิภาพทางเทคนิคแล้ว การเปรียบเทียบนี้ได้จุดประกายการสนทนาที่กว้างขึ้นเกี่ยวกับบทบาทของ SQLite ในสภาพแวดล้อมการใช้งานจริง นักพัฒนาหลายคนรายงานการปรับใช้ที่ประสบความสำเร็จโดยใช้ SQLite สำหรับแอปพลิเคชันที่ในอดีตอาจใช้ PostgreSQL หรือ MySQL

SQLite ถูกประเมินต่ำไป ฉันเห็นด้วยว่าในกรณีเฉพาะอาจไม่ใช่ตัวเลือกที่ดีที่สุด แต่บ่อยครั้งที่ฉันเห็นว่า SQLite เป็นฐานข้อมูลที่เพียงพอมากกว่า การใช้ Postgres หรือ MySQL เพื่อความเป็น production grade ไม่เคยเป็นความคิดที่ดี

กุญแจสู่การปรับใช้ SQLite ที่ประสบความสำเร็จมักเกี่ยวข้องกับการเปิดใช้งานโหมด Write-Ahead Logging ( WAL ) ซึ่งอนุญาตให้ readers และ writers ทำงานพร้อมกัน ฟีเจอร์นี้แก้ไขข้อกังวลดั้งเดิมเกี่ยวกับพฤติกรรมการล็อกของ SQLite ในสภาพแวดล้อมแบบ multi-process

Write-Ahead Logging (WAL): ฟีเจอร์ของ SQLite ที่ปรับปรุงการทำงานพร้อมกันโดยอนุญาตให้ readers เข้าถึงฐานข้อมูลในขณะที่ writer กำลังทำการเปลี่ยนแปลง

สภาพแวดล้อมการทดสอบ:

  • ระบบปฏิบัติการ: Debian GNU/Linux 12.11 (amd64)
  • ซีพียู: Intel Core i7-1165G7 @ 2.80GHz (8 คอร์)
  • แรม: 32GB
  • หน่วยเก็บข้อมูล: 1TB NVMe SSD
  • เวอร์ชัน Go : 1.24.5
  • วันที่ทดสอบ: 17 สิงหาคม 2025

บทสรุป

ผลการเปรียบเทียบแสดงให้เห็นว่าภูมิทัศน์ไดรเวอร์ Go SQLite ได้พัฒนาไปอย่างมีนัยสำคัญ โดยโซลูชัน pure Go ตอนนี้เสนอทางเลือกที่เป็นไปได้แทนแนวทางแบบ CGO ดั้งเดิม ในขณะที่แนวทาง stdin/stdout แสดงตัวเลขประสิทธิภาพที่น่าประทับใจ การเลือกไดรเวอร์ควรขึ้นอยู่กับความต้องการของแอปพลิเคชันเฉพาะ ข้อจำกัดในการปรับใช้ และลักษณะประสิทธิภาพที่สำคัญที่สุดสำหรับแต่ละกรณีการใช้งาน

สำหรับนักพัฒนาที่ให้ความสำคัญกับความง่ายในการปรับใช้และ cross-compilation ไดรเวอร์ pure Go เช่น modernc.org/sqlite เป็นตัวเลือกที่น่าสนใจ ผู้ที่แสวงหาประสิทธิภาพสูงสุดอาจพิจารณาแนวทาง sqinn ที่ไม่เป็นทางการ แม้จะมีความซับซ้อนในการปรับใช้ ไดรเวอร์ mattn/go-sqlite3 แบบดั้งเดิมยังคงเป็นตัวเลือกที่มั่นคงสำหรับแอปพลิเคชันที่ยอมรับการพึ่งพา CGO ได้

อ้างอิง: Benchmarks for Golang SQLite Drivers