- Ben Johnson ผู้สร้าง BoltDB (ฐานข้อมูลคีย์-แวลูแบบ embedded) ตอนนี้กำลังพัฒนา Litestream ที่ FlyIO
- โครงสร้างที่สมเหตุสมผลของแอปพลิเคชันแบบฟูลสแตกคือ n-Tier : แอปเซิร์ฟเวอร์ + DB เซิร์ฟเวอร์
→ ในสถาปัตยกรรมนี้ SQLite เคยถูกใช้แค่สำหรับ unit test แต่ตอนนี้สามารถใช้งานเป็นชั้นข้อมูลและ persistence layer ได้อย่างเพียงพอแล้ว - Litestream เป็นโอเพนซอร์สที่ทำให้ SQLite ใช้งานกับแอปพลิเคชันฟูลสแตกได้ผ่านการ replication
ประวัติย่อของฐานข้อมูลแอปพลิเคชัน
-
แม้ 50 ปีจะไม่ใช่เวลาที่ยาวนานมาก แต่วิธีที่ซอฟต์แวร์จัดการข้อมูลเปลี่ยนไปอย่างมหาศาล
→ ในยุค 70 มี "กฎของ Codd" ที่นิยามฐานข้อมูลเชิงสัมพันธ์
→ ข้อมูลทั้งหมดอยู่ในตาราง มี CRUD, schema, ภาษา SQL เป็นต้น
→ ในยุค 80 และ 90 ฐานข้อมูล SQL อย่าง Oracle/DB2/Postgres/MySQL แพร่หลายอย่างระเบิดระเบ้อ
→ ฐานข้อมูล XML ในยุค 2000 ไม่ค่อยเวิร์ก และในช่วงเวลาเดียวกันก็มีฐานข้อมูลแบบคอลัมน์ที่ยอดเยี่ยมเกิดขึ้น
→ ในยุค 2010 มีโปรเจกต์ฐานข้อมูลแบบ distributed โอเพนซอร์สขนาดใหญ่เปิดตัว และตอนนี้ใคร ๆ ก็สร้างคลัสเตอร์และ query ข้อมูลระดับเทราไบต์ได้ -
เมื่อฐานข้อมูลพัฒนา กลยุทธ์ในการเชื่อมต่อ DB เข้ากับแอปพลิเคชันก็พัฒนาไปด้วย
→ หลังยุค Codd ก็เริ่มแยกเป็นหลาย tier
→ ตอนแรกมี database tier
→ ต่อมามี caching tier อย่าง memcached และ Redis
→ มี background job tier (Sidekiq), routing tier (PgBouncer), distribution tier เป็นต้น
→ แม้หลายบทเรียนจะพูดเหมือนมีแค่ 3-Tier แต่เพราะไม่รู้ว่าจะมีอีกกี่ tier จึงเรียกกันว่า "n-Tier" -
ตลอด 50 ปีที่ผ่านมา เราได้เห็น CPU, หน่วยความจำ และดิสก์เร็วขึ้นและถูกลงหลายร้อยเท่า
→ คำที่นิยามนวัตกรรมฐานข้อมูลในยุค 2010 จริง ๆ คือ "big data"
→ แต่ด้วยการพัฒนาของฮาร์ดแวร์ ทำให้เมื่อถึงปี 2020 แนวคิดนั้นก็รักษาไว้ได้ยากขึ้น
→ ในปี 1996 การจัดการฐานข้อมูล 1GB เป็นเรื่องใหญ่มาก แต่ในปี 2022 แม้บนโน้ตบุ๊กหรือ t3.micro ก็ยังรันงานได้สบาย -
เวลาเราคิดถึงสถาปัตยกรรมฐานข้อมูลแบบใหม่ เรามักถูกสะกดด้วยข้อจำกัดด้าน scalability
→ ถ้าจัดการข้อมูลระดับเพตะไบต์หรืออย่างน้อยเทราไบต์ไม่ได้ ก็เหมือนหมดสิทธิ์อยู่ในวงสนทนา
→ แต่แอปพลิเคชันส่วนใหญ่ ต่อให้ประสบความสำเร็จ ก็ยังยากจะมีข้อมูลถึงระดับเทราไบต์
→ เรากำลังใช้ jackhammer เพื่อตอกตะปู
การเปิดตัวอันหอมหวานของ SQLite
- มีฐานข้อมูลตัวหนึ่งที่สะท้อนแนวโน้มนี้ได้ดีมาก
- มันเป็นหนึ่งในฐานข้อมูล SQL ที่มีชื่อเสียงที่สุดในโลก เป็นรูปแบบจัดเก็บอย่างเป็นทางการของหอสมุดรัฐสภาสหรัฐฯ มีชื่อเสียงด้านความน่าเชื่อถือและ test suite ขนาดมหาศาลจนประเมินแทบไม่ได้ และประสิทธิภาพก็ยอดเยี่ยมมาก
- ระดับนี้คงไม่ต้องเอ่ยชื่อแล้ว แต่สำหรับคนที่ยกมืออยู่แถวหลัง.. ใช่แล้ว นี่คือเรื่องของ SQLite
- SQLite เป็นฐานข้อมูลแบบ embedded ในสถาปัตยกรรมทั่วไปมันไม่ได้อยู่ใน tier แยกต่างหาก แต่เป็นเพียงไลบรารีที่ลิงก์เข้าไปใน process ของแอปเซิร์ฟเวอร์
→ เป็น "แอปพลิเคชันแบบ single process" ที่รันได้ด้วยตัวเองโดยไม่พึ่งพาเซิร์ฟเวอร์อื่น
- เพราะผมเป็นคนสร้างฐานข้อมูล ผมเลยสนใจแอปพลิเคชันประเภทนี้
- ผมสร้าง BoltDB ซึ่งเป็น embedded key/value DB ที่มีชื่อเสียงใน ecosystem ของ Go
- BoltDB มีความเสถียร และให้ประสิทธิภาพแบบรถของเล่นติดไนโตรอย่างที่คาดหวังจากฐานข้อมูล in-process
- แต่ BoltDB ก็มีข้อจำกัด
→ เนื่องจาก schema ถูกกำหนดด้วยโค้ด Go การทำ DB migration จึงยาก คุณต้องสร้างเครื่องมือเอง แถมยังไม่มี REPL อีกด้วย
- ถ้าคุณระวังดีพอ ฐานข้อมูลประเภทนี้จะให้ประสิทธิภาพมหาศาล
- แต่สำหรับงานทั่วไป คุณคงไม่อยากเอาฐานข้อมูลแบบนี้ไปใช้ใน production
- ผมคิดอยู่ว่าต้องทำอะไรเพื่อให้ BoltDB ใช้ได้กับแอปมากขึ้น และข้อสรุปที่ผมได้คือ "SQLite ถูกสร้างมาเพื่อสิ่งนั้นโดยตรง"
- แน่นอนว่า SQLite ก็มีข้อจำกัดเช่นกัน ข้อใหญ่ที่สุดคือแอปแบบ single process มี SPOF (Single Point of Failure): ถ้าเซิร์ฟเวอร์หายไป ฐานข้อมูลก็หายไปด้วย นี่ไม่ใช่ข้อบกพร่องของ SQLite แต่เป็นสิ่งที่มันถูกออกแบบมาแบบนั้นตั้งแต่ต้น
Enter Litestream
- มีเหตุผลใหญ่สองข้อที่ทำให้หลายคนยังไม่ใช้ SQLite เป็นค่าเริ่มต้น
→ อย่างแรกคือความทนทานต่อความผิดพลาดของสตอเรจ (Resilience)
→ อย่างที่สองคือ concurrency เมื่อสเกลใหญ่ขึ้น - Litestream มีสิ่งจะพูดเกี่ยวกับปัญหาทั้งสองข้อนี้
-
Litestream ทำงานโดยควบคุม journaling ของโหมด WAL (Write Ahead Log) ของ SQLite
-
ในโหมด WAL การเขียนจะถูกเพิ่มเข้าไปในไฟล์ log แยกต่างหากจากไฟล์ฐานข้อมูลหลักของ SQLite
-
ตัว reader จะตรวจทั้งไฟล์ WAL และ DB หลักเพื่อให้ตอบ query ได้ครบ
-
โดยปกติ SQLite จะรัน automatic checkpoint จาก WAL กลับไปยัง DB หลักเอง
-
Litestream จะเข้าไปแทรกในช่วงนี้ โดยเปิด read transaction แบบไม่สิ้นสุดเพื่อป้องกัน automatic checkpoint จากนั้นจับและ replicate การอัปเดตของ WAL ด้วยตัวเอง แล้วค่อย trigger checkpoint เอง
สิ่งสำคัญที่สุดที่ต้องเข้าใจเกี่ยวกับ Litestream คือ มันก็แค่ SQLite แอปพลิเคชันยังคงใช้ SQLite มาตรฐาน ไม่ได้เพิ่ม dependency ไม่ได้วิเคราะห์ query และไม่ได้ทำตัวเป็น proxy มันแค่ใช้ความสามารถด้าน journaling และ concurrency ที่ SQLite มีอยู่แล้ว ในหลายกรณี โค้ดของคุณอาจไม่รับรู้ด้วยซ้ำว่า Litestream มีอยู่
- ฟังดูซับซ้อน แต่จริง ๆ แล้วง่ายมาก พอลองใช้จะพบว่ามัน "just works"
$ litestream replicate fruits.db s3://my-bukkit:9000/fruits.db
$ litestream restore -o fruits-replica.db s3://my-bukkit:9000/fruits.db
- โดยทั่วไปผู้คนใช้มันเพื่อ replicate ฐานข้อมูล SQLite ไปเก็บไว้ใน S3
- มันให้ข้อดีด้านการปฏิบัติการอย่างมาก ฐานข้อมูลของคุณจะมีความยืดหยุ่นและสามารถย้ายหรือ migrate ได้ง่าย
- แต่ยังทำอะไรได้มากกว่านั้นด้วย Litestream
- ในเวอร์ชันถัดไป จะสามารถทำ real-time replication ระหว่างฐานข้อมูล SQLite ได้ ทำให้สามารถตั้งค่า distributed read replica และ write-leader DB ได้
→ read replica สามารถดัก write แล้ว redirect ไปยัง leader ได้
→ แอปจำนวนมากเป็นแบบ read-heavy ดังนั้นการตั้งค่านี้จะทำให้แอปมีฐานข้อมูลที่สเกลได้ทั่วโลก
คุณควรมองตัวเลือกนี้อย่างจริงจังมากขึ้น (การใช้ SQLite เป็นฐานข้อมูลของแอปพลิเคชัน)
- งานสายไอทีช่วงแรก ๆ ของผมอย่างหนึ่งคือเป็น Oracle DBA ในช่วงต้นยุค 2000
- ผมใช้เวลาอ่านหนังสือและเอกสารจำนวนมากเพื่อเรียนรู้ Oracle
- คู่มือผู้ดูแลระบบยาวเกือบพันหน้า และนั่นเป็นเพียงหนึ่งในเอกสารอีกหลายร้อยฉบับ
- การเรียนรู้ว่าต้องทำอะไรเพื่อ optimize query หรือปรับปรุงการเขียนข้อมูล สมัยนั้นสร้างความต่างได้มากจริง ๆ
- ตอนนั้นฮาร์ดดิสก์อ่านข้อมูลได้เพียงไม่กี่สิบเมกะไบต์ต่อวินาที ดังนั้นการใช้อินเด็กซ์ที่ดีขึ้นสามารถเปลี่ยน query ที่ใช้เวลา 5 นาที ให้เหลือ 30 วินาทีได้
- แต่การ optimize ฐานข้อมูลค่อย ๆ มีความสำคัญน้อยลงสำหรับแอปพลิเคชันทั่วไป
- ถ้าคุณมีฐานข้อมูล 1GB ดิสก์ NVMe สามารถโหลดทุกอย่างเข้า memory ได้ภายในไม่ถึง 1 วินาที
- ผมชอบการ optimize SQL query แต่สำหรับนักพัฒนาแอปหลายคน นี่กำลังกลายเป็นทักษะที่กำลังเลือนหาย
- แม้แต่ query ที่ไม่ได้ปรับจูนอย่างดี ในหลายฐานข้อมูลก็ยังรันเสร็จได้ภายในไม่ถึง 1 วินาที
- Postgres สมัยใหม่ถือเป็นปาฏิหาริย์ ผมได้เรียนรู้อะไรมากมายจากการอ่านโค้ดนั้นมาหลายปี
- ทั้ง query optimizer, นโยบาย row-level security, อินเด็กซ์ 6 ประเภท เป็นต้น
- ถ้าคุณต้องการฟีเจอร์เหล่านี้ก็จำเป็น แต่คนส่วนใหญ่ไม่ต้องการ
- และถ้าคุณไม่ได้ต้องการฟีเจอร์ของ Postgres แบบนั้น มันก็ยังมีภาระตามมา
- ต่อให้ไม่ได้ใช้หลายบัญชี คุณก็ยังต้องตั้งค่า host-based authentication และเปิดไฟร์วอลล์
- ฟีเจอร์ยิ่งมากก็ยิ่งหมายถึงเอกสารยิ่งมาก ทำให้ยากขึ้นที่จะเข้าใจซอฟต์แวร์ที่คุณกำลังรันจริง ๆ
- เอกสารของ Postgre14 ยาวเกือบ 3,000 หน้า
- SQLite มีฟีเจอร์เป็น subset ของ Postgres แต่สำหรับสิ่งที่ผมต้องการโดยทั่วไป มันมีครบ 99.9%
- รองรับ SQL อย่างยอดเยี่ยม, window functions, CTE, full-text search, JSON support เป็นต้น
- ต่อให้มีฟีเจอร์ขาดไปบ้าง เพราะข้อมูลอยู่ติดกับแอปพลิเคชันของผมอยู่แล้ว ค่าใช้จ่ายในการดึงออกมาประมวลผลจึงแทบไม่มี
- ในทางกลับกัน ปัญหาซับซ้อนที่ควรแก้จริง ๆ กลับไม่ได้ถูกแก้ด้วยฟีเจอร์หลักของฐานข้อมูล
- สิ่งที่ผมอยาก optimize จริง ๆ มีแค่ latency กับประสบการณ์นักพัฒนา
- ดังนั้น เหตุผลหนึ่งที่ควรพิจารณา SQLite อย่างจริงจังคือ มันดูแลง่ายมาก
- คุณสามารถเลิกเสียเวลาออกแบบชั้นฐานข้อมูล แล้วหันไปเขียนโค้ดแอปพลิเคชันได้เลย
- แต่ก็ยังมีปัญหาอีกข้อ
แสงช้าเกินไป : The Light is Too Damn Slow
- เรากำลังเริ่มชนขีดจำกัดเชิงทฤษฎี แสงเดินทางได้ 186 ไมล์ต่อ 1 มิลลิวินาทีในสุญญากาศ (ระยะไปกลับประมาณฟิลาเดลเฟียถึงนิวยอร์ก)
- พอเพิ่ม network switch, firewall และชั้นของ application protocol เข้าไป ก็ยิ่งช้าลงอีก
- ภายใน AWS region เดียว latency overhead ของ query ไปยัง Postgres อาจสูงได้ถึง 1 มิลลิวินาที
- นี่ไม่ได้หมายความว่า Postgres ช้า แต่หมายความว่าเราเริ่มชนข้อจำกัดของความเร็วในการเคลื่อนย้ายข้อมูลแล้ว
- แอปสมัยใหม่ต้องรับ HTTP request และก่อนจะทำ business logic หรือ render ก็อาจใช้เวลาไปแล้ว 10ms กับหลายฐานข้อมูล query
- มีตัวเลขมหัศจรรย์สำหรับ latency ของแอปพลิเคชันอยู่ตัวหนึ่ง: การตอบสนองต่ำกว่า 100ms จะให้ความรู้สึกเกือบทันที
- แอปที่ตอบสนองฉับไวสร้างผู้ใช้ที่มีความสุข
- 100ms ดูเหมือนเยอะ แต่หมดไปได้ง่ายมากโดยไม่รู้ตัว
- เพราะ threshold 100ms สำคัญมาก ผู้คนจึง prerender หน้าเว็บและเอาไปวางบน CDN เพื่อลด latency
- เราควรย้ายข้อมูลเข้ามาใกล้แอปพลิเคชันมากขึ้น แค่ไหนน่ะหรือ? ใกล้มาก ๆ
- SQLite ไม่ได้แค่อยู่บนเครื่องเดียวกับแอปพลิเคชันของคุณเท่านั้น แต่มันอยู่ภายใน process ของแอปพลิเคชันด้วย
- เมื่อเก็บข้อมูลไว้ข้างแอป คุณจะเห็น latency ต่อ query ลดลงเหลือ 10~20 ไมโครวินาที (μ)
- หรือก็คือเร็วกว่า query ของ Postgres ใน region เดียวกันราว 50~100 เท่า
- แต่มันยังมีมากกว่านั้น เราแทบกำจัด latency ต่อ query ได้อย่างมีประสิทธิภาพ ทำให้แอปของเราเร็วขึ้นและเรียบง่ายขึ้นพร้อมกัน
- เราสามารถแยก query ขนาดใหญ่ออกเป็น query เล็ก ๆ ที่จัดการง่ายกว่า และใช้เวลากับการสร้างฟีเจอร์ใหม่ ๆ ผ่าน pattern แบบ N+1 query ได้มากขึ้น
- การลด latency ให้ต่ำที่สุดไม่ได้มีประโยชน์แค่ใน production การทำ integration test กับฐานข้อมูล client/server แบบเดิมมักจะยืดเยื้อจนใช้เวลาหลายนาทีบนเครื่อง local และแม้ push ไป CI แล้วความเจ็บปวดนั้นก็ยังอยู่
- หากลด feedback loop ตั้งแต่เปลี่ยนโค้ดจนทดสอบเสร็จได้ ก็จะประหยัดเวลาและช่วยให้มีสมาธิระหว่างพัฒนา
- สำหรับ SQLite การเปลี่ยนแค่บรรทัดเดียวสามารถรัน integration test ในหน่วยความจำและจบได้ภายในไม่กี่วินาที
เล็ก, เร็ว, เชื่อถือได้, กระจายทั่วโลก : เลือกได้ 4 อย่างจากสิ่งเหล่านี้
- Litestream เป็นระบบแบบ distributed, replicated และที่สำคัญที่สุดคือ เข้าใจง่าย
- เอาจริง ๆ คือ "ลองใช้ดูสักครั้ง" สิ่งที่ต้องรู้มีไม่มาก
- ข้อเสนอของผมคือ:
- ถ้าเราสร้าง replication ที่เสถียรและใช้งานง่ายสำหรับ SQLite ได้ มันจะทำให้แอปพลิเคชันฟูลสแตกที่รันบน SQLite เพียงอย่างเดียวกลายเป็นตัวเลือกที่น่าสนใจมาก
- ตอนที่มีการเขียนบทเรียน "ทำบล็อกด้วย Rails" กันในสมัยก่อน ตัวเลือกนี้ถูกมองข้ามไป แต่ SQLite ทุกวันนี้รับภาระงานเขียนของแอปส่วนใหญ่ได้ และยังสามารถ load balance การอ่านผ่าน replica ไปยังหลายอินสแตนซ์ได้
- Litestream เองก็มีข้อจำกัด
- เพราะมันถูกสร้างมาสำหรับแอปแบบ single-node จึงทำงานได้ไม่ดีนักบนแพลตฟอร์ม serverless หรือการ deploy แบบ rolling
- เนื่องจากต้อง restore การเปลี่ยนแปลงทั้งหมดตามลำดับ การกู้ฐานข้อมูลจึงอาจใช้เวลาหลายนาที
- เรากำลังเตรียมฟีเจอร์ real-time replication อยู่ แต่โมเดลแบบแยก process ก็มีข้อจำกัดด้านการควบคุมรายละเอียดเกี่ยวกับการรับประกันการ replication
- เราทำได้ดีกว่านี้
- ตลอด 1 ปีที่ผ่านมา งานที่ผมทำคือกำหนดแกนหลักของ Litestream และโฟกัสที่ความถูกต้อง
- ผมพอใจกับจุดที่เรามาถึงตอนนี้
- มันเริ่มจากเครื่องมือสำรองข้อมูลแบบสตรีมมิงง่าย ๆ แต่กำลังพัฒนาไปเป็นฐานข้อมูลที่เสถียรและกระจายตัว
- งานของผมที่ Fly.io คือทำให้สิ่งนี้เร็วขึ้นและไร้รอยต่อยิ่งขึ้น
- และไม่ว่า Fly.io จะเกี่ยวข้องหรือไม่ การปรับปรุงเพิ่มเติมอีกมากก็จะถูกใส่เข้ามาใน Litestream
- Litestream มีบ้านใหม่อยู่ที่ Fly.io แต่จะยังคงเป็นโปรเจกต์โอเพนซอร์สต่อไป
- แผนของผมในอีกหลายปีข้างหน้าคือทำให้มันมีประโยชน์มากขึ้น ไม่ว่าแอปพลิเคชันจะรันอยู่ที่ไหน และดูว่าโมเดลของ SQLite จะไปได้ไกลแค่ไหน
6 ความคิดเห็น
ทำให้อยากกลับไปอ่านอย่างจริงจังอีกครั้งครับ
ผมก็เคยมีความคิดคล้าย ๆ กันอยู่บ้าง แต่นี่จริงจังและลงลึกกว่ามาก อ่านไปแล้วทึ่งเลยครับ อยากลองใช้ Litestream ดูเหมือนกัน
ถ้ารันคิวรีจากรีโมตได้ก็คงจะดีขึ้นอีกหน่อย... ฮือๆ
เป็นช่วงที่นึกถึง elixir ขึ้นมาเลยครับ เป็นเครื่องมือที่มีทั้ง embedded distributed db และ orchestration ให้ในระดับภาษา แต่ก็ยังไม่แน่ใจนักว่านี่คืออนาคตหรือเปล่า
อ่านได้เพลินมาก!
ตอนแรกตั้งใจว่าจะอ่านแบบคร่าว ๆ แล้วสรุปสั้น ๆ แต่พอทำไปทำมากลับสนุก เลยยาวเลยครับ
Litestream - เครื่องมือทำ streaming replication สำหรับ SQLite
ถ้าอ่านควบคู่กับคำถาม มีใครเคยใช้ SQLite เป็น Primary DB ไหม? ก็น่าจะดีครับ
น่าจะมีจุดเชื่อมโยงกับ Cloudflare เปิดตัว D1 ฐานข้อมูล SQL สำหรับ Workers ที่เพิ่งเปิดเผยเมื่อไม่กี่วันก่อนด้วยนะครับ
ลองดูคอมเมนต์ใน HN ด้วยครับ https://news.ycombinator.com/item?id=31318708