- โครงสร้างผู้เขียนเดี่ยวและคุณสมบัติแบบ embedded ของ SQLite ได้รับการพิสูจน์ผ่านการทดลองว่าเป็นปัจจัยที่ช่วยเพิ่มทั้งความสามารถในการขยายตัวและประสิทธิภาพ
- ภายใต้เงื่อนไขเดียวกัน Postgres ลดลงเหลือ 348 TPS เมื่อมีความหน่วงเครือข่าย ขณะที่ SQLite สามารถทำได้ 44,096 TPS ด้วยการตัดเครือข่ายออก
- ด้วย การประมวลผลแบบ batch และธุรกรรมแบบละเอียดด้วย SAVEPOINT ที่อาศัยโมเดลผู้เขียนเดี่ยว ทำได้สูงสุด 186,157 TPS และในคอนฟิกที่เสถียรทำได้ 102,545 TPS
- กฎของ Amdahl อธิบายคอขวดของฐานข้อมูลแบบเครือข่าย และ SQLite รักษาประสิทธิภาพสูงไว้ได้ด้วยการหลีกเลี่ยงข้อจำกัดนี้
- ผลลัพธ์นี้เน้นย้ำทั้ง ความเป็นไปได้ของการใช้ SQLite ในสภาพแวดล้อมโลคัล และ ความสำคัญของการกำจัดคอขวดจากเครือข่าย
โครงสร้างของ SQLite และสภาพแวดล้อมการทดลอง
- SQLite ไม่มี MVCC และอนุญาตให้มีผู้เขียนได้เพียงรายเดียว แต่โครงสร้างนี้กลับทำให้ขยายตัวได้ดีอย่างน่าประหลาด
- ในฐานะฐานข้อมูลแบบ embedded จึงไม่มี network overhead
- เบนช์มาร์กดำเนินการบน MacBook Pro (2021) พร้อม Apple M1 Pro และหน่วยความจำ 16GB
- การทดลองนี้ไม่ได้มุ่งที่การจูนให้เหมาะสมที่สุดแบบสุดขั้ว แต่มีเป้าหมายเพื่อแสดงว่า แม้ในเงื่อนไขทั่วไปก็สามารถได้ throughput การเขียนที่สูงมาก
ความหมายของ TPS และตัวอย่างธุรกรรม
- TPS ไม่ได้หมายถึงความเร็วในการเขียนอย่างเดียว แต่หมายถึง ธุรกรรมเชิงโต้ตอบ (Interactive Transaction)
- ตัวอย่าง: การโอนเงินระหว่างบัญชี ซึ่งมีหลาย query และโค้ดแอปพลิเคชันทำงานอยู่ภายในธุรกรรมเดียวกัน
- ธุรกรรมสามารถ rollback สถานะได้เมื่อเกิดข้อผิดพลาด จึงมี บทบาทสำคัญต่อการรักษาความสอดคล้องของข้อมูล
การตั้งค่าเบนช์มาร์ก
- ใช้ virtual threads บน Clojure เพื่อจำลองคำขอพร้อมกันจำนวนมาก
- Postgres ตั้งค่าด้วย connection pool บน HikariCP ส่วน SQLite ใช้ ผู้เขียนเดี่ยวและการเชื่อมต่อสำหรับการอ่านตามจำนวนคอร์
- ทั้งสองฐานข้อมูลใช้ตาราง account แบบง่ายที่มีฟิลด์ id, balance และแทรกข้อมูล 1 พันล้านแถว
- กิจกรรมของผู้ใช้เป็นไปตาม การกระจายแบบ power law (0.9995) และมี ผู้ใช้ที่แอ็กทีฟประมาณ 100,000 คน
ประสิทธิภาพของฐานข้อมูลแบบเครือข่าย (Postgres)
- ภายในเซิร์ฟเวอร์เดียวกัน Postgres ทำได้ 13,756 TPS
- เมื่อเพิ่มความหน่วงเครือข่าย 5ms ลดลงเหลือ 1,214 TPS และที่ 10ms ลดลงฮวบเป็น 702 TPS
- หลังใช้ระดับการแยกธุรกรรมแบบ serializable ลดลงเป็น 660 TPS และเมื่อรวม query เพิ่มเติมลดลงเหลือ 348 TPS
- สิ่งนี้แสดงให้เห็นตาม กฎของ Amdahl ว่าคอขวดจากเครือข่ายเป็นตัวจำกัดประสิทธิภาพโดยรวม
- เมื่อความหน่วงเครือข่ายเพิ่มขึ้น การแย่ง lock ระหว่างธุรกรรมจะรุนแรงขึ้นจนไม่สามารถขยายตัวได้
ข้อได้เปรียบของ SQLite แบบ embedded
- หลังตัดเครือข่ายออก SQLite ทำได้ 44,096 TPS
- เมื่อคอขวดจากเครือข่ายหายไป ผลกระทบจากกฎของ Amdahl ก็ลดลงอย่างมาก
- เมื่อใช้ประโยชน์จากโครงสร้างผู้เขียนเดี่ยวและนำ batch processing มาใช้ throughput เพิ่มขึ้นถึง 186,157 TPS
- มีการปรับขนาด batch แบบไดนามิกเพื่อเพิ่มประสิทธิภาพ latency และ throughput โดยอัตโนมัติ
ธุรกรรมแบบละเอียดด้วย SAVEPOINT
- เพื่อป้องกันไม่ให้ความล้มเหลวของธุรกรรมรายตัวใน batch กระทบทั้งชุด จึงใช้ nested transaction ด้วย SAVEPOINT
- หากล้มเหลวจะ rollback เฉพาะธุรกรรมนั้น และคง batch ทั้งหมดไว้
- แม้ใช้วิธีนี้ก็ยังรักษาระดับได้ที่ 121,922 TPS
การทดสอบโหลดแบบผสมอ่าน/เขียน
- กำหนดให้ 75% ของคำขอทั้งหมดเป็นการอ่าน และ 25% เป็นการเขียน
- ใช้ thread pool สำหรับการอ่านแยกต่างหากเพื่อ แยกไม่ให้คำขออ่านไปรบกวนการเขียน
- ผลลัพธ์สุดท้ายทำได้ 102,545 TPS
สรุปการเปรียบเทียบประสิทธิภาพ
| เงื่อนไข |
Postgres |
SQLite |
| ไม่มีเครือข่าย |
13,756 |
44,096 |
| หน่วง 5ms |
1,214 |
n/a |
| หน่วง 10ms |
702 |
n/a |
| 10ms + serializable |
660 |
n/a |
| batch processing |
n/a |
186,157 |
| batch + SAVEPOINT |
n/a |
121,922 |
| batch + SAVEPOINT + การอ่าน |
n/a |
102,545 |
บทสรุป
- SQLite สามารถทำ TPS ได้สูงกว่าฐานข้อมูลแบบเครือข่ายอย่างมาก ด้วย โมเดลผู้เขียนเดี่ยวและโครงสร้างแบบ embedded
- การหลีกเลี่ยง ข้อจำกัดคอขวดจากเครือข่ายตามที่กฎของ Amdahl ชี้ให้เห็น ทำให้รีดประสิทธิภาพได้อย่างเต็มที่
- โค้ดทั้งหมดเปิดเผยไว้บน GitHub และยังมีเอกสารประกอบในหัวข้อที่เกี่ยวข้อง เช่น กฎของ Amdahl, power law และกรณีการขยายระบบด้วย SQLite
- SQLite เป็นตัวเลือกที่มีประสิทธิภาพมากสำหรับ การประมวลผลธุรกรรมประสิทธิภาพสูงในสภาพแวดล้อมโลคัล
2 ความคิดเห็น
ถ้าใช้แค่ในเครื่องภายในสภาพแวดล้อมโลคัลโดยไม่ต้องไปพึ่งเซิร์ฟเวอร์ภายนอก ก็เท่ากับว่าถามว่า จำเป็นต้องเสียภาษีเครือข่ายด้วยเหรอ? (VFS vs Socket)
ความคิดเห็นจาก Hacker News
ฉันกำลังสร้าง เซิร์ฟเวอร์ ORM/CRUD แบบไฮบริด protobuf ที่ใช้ SQLite
โค้ดและคำอธิบายอยู่ที่ GitHub - accretional/collector
ระหว่างแบ็กอัปแบบเรียลไทม์มี downtime 5~15ms, คิวคำขออ่าน/เขียนได้หลายร้อยรายการ, latency ของ CRUD ทั้งหมดอยู่ราว 1ms, และยังทำ streaming backup บน WAL ได้อีกด้วย
เมื่อก่อนฉันใช้แต่ Postgres กับ Spanner แต่ถ้าเพิ่มฟีเจอร์พาร์ทิชันให้ Collector ได้ ฉันคงไม่กลับไปใช้ Postgres อีก
ข้อเสียคือข้อมูลและการประมวลผลทั้งหมดต้องอยู่ใน เครื่องเดียว
ถ้าใช้ AWS instance รุ่น u-24tb1.112xlarge (448 vcore, RAM 24TB, EBS 64TB) ก็ยังเหลือเฟือพอสมควร
บทความเน้นประสิทธิภาพของ SQLite ก็จริง แต่รู้สึกว่า เกณฑ์เปรียบเทียบไม่ชัดเจน
เพราะเดิมทีตั้งสมมติฐานเป็นสถาปัตยกรรมเซิร์ฟเวอร์แยกกัน แล้วมาวัดประสิทธิภาพของ local embedded DB
ถ้าเงื่อนไขเดียวกัน การจูน Postgres แบบรันในเครื่องเดียวกันก็น่าจะให้ประสิทธิภาพใกล้เคียงได้
การจำกัดจำนวนการเชื่อมต่อ Postgres ไว้ที่ 8 อาจเป็น คอขวด
น่าจะเผยการใช้ CPU และเธรดควบคู่กันไป และลองทดสอบใหม่ด้วย connection pool ที่ใหญ่ขึ้น
ถ้าเพิ่มเป็น 64 connections throughput อาจเพิ่มได้ 8 เท่า ควร ขยายการตั้งค่าฝั่งไคลเอนต์ ไปเรื่อย ๆ จนกว่าจะชนขีดจำกัด
ประเด็นสำคัญคือ ต้องตระหนักว่า network latency เป็นคอขวดหรือไม่
ในหลาย workload นั้น local DB ธรรมดา ๆ ก็เร็วกว่าระบบ remote DB ที่ยอดเยี่ยม
สิ่งสำคัญไม่ใช่ “DB ตัวไหนดีที่สุด” แต่คือ “จำเป็นต้องข้ามขอบเขตเครือข่ายหรือเปล่า”
DB แบบเครือข่าย มีข้อดีตรง redeploy แอปได้ง่าย
เปิดอินสแตนซ์ใหม่แล้วปิดตัวเดิม ก็ทำ การ deploy แบบแทบไม่มี downtime ได้
แต่ถ้า SQLite อยู่ในอินสแตนซ์เดียวกัน พอจะสลับเครื่องก็ต้องย้าย DB ขึ้นมาใหม่ ทำให้ซับซ้อนกว่า อยากรู้ว่าเคยเจอปัญหานี้ในการใช้งานจริงไหม
ตอนย้ายระบบอาจมี downtime ได้ แต่ตอนนี้ Litestream ทำให้การทำ replication และ backup ง่ายขึ้นมาก
ผู้เขียนตั้งค่า
PRAGMA synchronous="normal"ซึ่งหมายความว่า ไม่ได้ทำ fsync ทุกครั้งเพื่อความยุติธรรมในการเปรียบเทียบ ควรตั้งเป็น
"full""normal"ก็ใช้ได้ การสูญเสียไฟจะทำให้เสียความทนทาน แต่ยังคง ความสอดคล้องของทรานแซกชัน เอาไว้ได้อยากรู้ว่าการทำ HA (ความพร้อมใช้งานสูง) ของ SQLite ทำอย่างไร
อย่างน้อยควรอยู่ในระดับที่ทำ failover อัตโนมัติได้
ตอนนี้ฉันกำลังชั่งใจระหว่าง Postgres กับ SQLite (รวม litestream)
แอปของฉันยอมรับ downtime ได้เล็กน้อย ดังนั้นการขยายแนวตั้งบนเครื่องเดียวจึงง่ายและถูกกว่า
ที่ Marmot GitHub มีการเพิ่ม กลไก replication แบบ gossip-based เข้ามาใหม่
อยากรู้ว่ามีกรณีไหนบ้างที่ใช้ SQLite ใน โปรดักชันจนดันไปถึงขีดจำกัดจริง ๆ
อยากรู้ว่าถ้าเป็นเว็บแอปทั่วไปหรือระบบคอมเมิร์ซ ขีดจำกัด จำนวนผู้ใช้ ของ SQLite เทียบกับ Postgres อยู่ประมาณไหน
SQLite จากอัปเดตล่าสุดรองรับ การอ่านพร้อมกัน ได้ แต่ยังอนุญาต การเขียนได้ครั้งละหนึ่งเดียว
อยากถามความเห็นว่ากรณีไหนสิ่งนี้จะกลายเป็นปัญหา และถ้าต้องคิดเรื่องการขยายระบบตั้งแต่ต้น ควรเริ่มด้วย Postgres จะดีกว่าไหม