HN เปิดตัว: Triplit - ฐานข้อมูลซิงก์แบบโอเพนซอร์สที่ทำงานทั้งบนเซิร์ฟเวอร์และไคลเอนต์
(github.com/aspen-cloud)- Triplit เป็นฐานข้อมูลโอเพนซอร์สที่ซิงก์ข้อมูลแบบเรียลไทม์ระหว่างเซิร์ฟเวอร์กับเบราว์เซอร์ และวางตำแหน่งตัวเองเป็น ฐานข้อมูลฟูลสแตก ที่ใช้งานได้โดยใส่เป็นแพ็กเกจ Typescript ในแอป
- รองรับทั้งการจัดเก็บบนเซิร์ฟเวอร์และการซิงก์คิวรีของไคลเอนต์ พร้อมสนับสนุน incremental updates, การแก้ไขความขัดแย้งระดับพร็อพเพอร์ตี, local caching, โหมดออฟไลน์ และการเชื่อมต่อใหม่อัตโนมัติ
- รองรับ สตอเรจแบบปลั๊กอิน เช่น SQLite, IndexedDB, LevelDB และ Memory พร้อมมี persistent storage ฝั่งเซิร์ฟเวอร์และแดชบอร์ดสำหรับจัดการ
- ใช้งาน API สำหรับคิวรีและการเปลี่ยนแปลงได้ทั้งใน React และ vanilla Javascript และถูกจัดมาในรูปแบบ monorepo ที่ประกอบด้วย React·Svelte bindings, CLI, Console และ Server
- มีทั้ง optimistic updates เพื่อให้ตอบสนองได้รวดเร็ว, การ rollback·retry สำหรับอัปเดตที่ล้มเหลว, สิทธิ์การอ่าน·เขียนที่บังคับใช้จากเซิร์ฟเวอร์ และความสามารถด้านการทำงานร่วมกันบนพื้นฐาน CRDT
สิ่งที่ Triplit มอบให้
- Triplit เป็นฐานข้อมูลโอเพนซอร์สที่ ซิงก์ข้อมูลแบบเรียลไทม์ ระหว่างเซิร์ฟเวอร์กับเบราว์เซอร์
- ให้ ที่เก็บข้อมูลแบบซิงก์ ที่สามารถเพิ่มเข้าไปในแอปได้ในรูปแบบแพ็กเกจ Typescript
- จัดเก็บข้อมูลบนเซิร์ฟเวอร์ และซิงก์คิวรีของไคลเอนต์อย่างชาญฉลาด
- Triplit เรียกรูปแบบนี้ว่า ฐานข้อมูลฟูลสแตก
- มีวิดีโองานนำเสนอในชุมชน Local First ให้ดูด้วย: presentation
ฟีเจอร์หลัก
-
การซิงก์แบบเรียลไทม์
- รองรับ incremental updates
- มีการแก้ไขความขัดแย้งระดับพร็อพเพอร์ตี
-
ประสบการณ์ใช้งานแบบ local-first
- มี local caching บนพื้นฐานฐานข้อมูลฝั่งไคลเอนต์เต็มรูปแบบ
- ทำให้ทุกการโต้ตอบรู้สึกรวดเร็วด้วย optimistic updates
- รองรับโหมดออฟไลน์, การเชื่อมต่อใหม่อัตโนมัติ และการรับประกันความสอดคล้องของข้อมูล
-
การจัดเก็บและการปฏิบัติการบนเซิร์ฟเวอร์
- มี persistent storage ฝั่งเซิร์ฟเวอร์
- รวมแดชบอร์ดสำหรับจัดการมาให้
- รองรับ ผู้ให้บริการสตอเรจแบบปลั๊กอิน เช่น SQLite, IndexedDB, LevelDB และ Memory
-
โมเดลข้อมูลและ API
- รองรับ relational queries สำหรับโมเดลข้อมูลที่ซับซ้อน
- ให้ความปลอดภัยของข้อมูลและการเติมโค้ดอัตโนมัติของ Typescript ผ่าน schema
- มี API แบบเรียบง่ายสำหรับคิวรีและการเปลี่ยนแปลง ทั้งใน vanilla Javascript และ React
-
การทำงานร่วมกันและความปลอดภัย
- บังคับใช้สิทธิ์ทั้งการอ่านและการเขียนจากฝั่งเซิร์ฟเวอร์
- มีฟีเจอร์การทำงานร่วมกัน·มัลติเพลเยอร์บนพื้นฐาน CRDTs
- ใช้ delta patches เพื่อลดทราฟฟิกเครือข่ายและมุ่งสู่ latency ต่ำ
- จัดการการ rollback และ retry สำหรับอัปเดตที่ล้มเหลว
องค์ประกอบของ monorepo
- TriplitDB: DB ที่ออกแบบมาให้ทำงานในสภาพแวดล้อม JS เช่น เบราว์เซอร์, Node, Deno และ React Native โดยให้คิวรีที่รวดเร็วและอัปเดตแบบสด พร้อมรักษาความสอดคล้องในเครือข่ายที่มีผู้เขียนหลายราย
- Client: ไลบรารีเบราว์เซอร์สำหรับโต้ตอบกับ TriplitDB ทั้งแบบโลคัลและระยะไกล
- CLI: เครื่องมือบรรทัดคำสั่งสำหรับ scaffold โปรเจ็กต์, รันสภาพแวดล้อมพัฒนาแบบฟูลสแตก และทำ server migrations
- React: React bindings สำหรับ
@triplit/client - Svelte: Svelte bindings สำหรับ
@triplit/client - Console: แอปสำหรับดูและแก้ไขข้อมูลโปรเจ็กต์ Triplit รวมถึงจัดการ schema
- Server: เซิร์ฟเวอร์ Node ที่ซิงก์ข้อมูลระหว่างไคลเอนต์ Triplit
- Server-core: ไลบรารีที่ไม่ผูกกับโปรโตคอล สำหรับสร้างเซิร์ฟเวอร์ Triplit
- Docs: เอกสารของ Triplit ที่สร้างด้วย Nextra
- Types: ชนิดข้อมูลที่โปรเจ็กต์ Triplit ใช้ร่วมกัน
- UI: คอมโพเนนต์ UI ที่ใช้ร่วมกันบนพื้นฐาน shadcn
ขั้นตอนเริ่มต้นอย่างรวดเร็ว
- โปรเจ็กต์ใหม่เริ่มได้ด้วย
npm create triplit-app@latest my-app - สำหรับโปรเจ็กต์เดิม ให้ติดตั้ง
@triplit/cliเป็น development dependency แล้วรันnpm run triplit init - กำหนด schema ใน
my-app/triplit/schema.ts- ตัวอย่างกำหนดฟิลด์
id,text,completedในคอลเลกชันtodos completedถูกตั้งเป็นฟิลด์ Boolean ที่มีค่าเริ่มต้นเป็นfalse
- ตัวอย่างกำหนดฟิลด์
- เริ่ม เซิร์ฟเวอร์ซิงก์ สำหรับพัฒนาด้วย
npm run triplit dev - เซิร์ฟเวอร์พัฒนาจะแสดง environment variables ที่แอปต้องใช้ในการซิงก์กับเซิร์ฟเวอร์
- ในตัวอย่าง Vite คือ
VITE_TRIPLIT_SERVER_URL=http://localhost:6543 VITE_TRIPLIT_TOKEN=copied-in-from-triplit-dev
- ในตัวอย่าง Vite คือ
ตัวอย่างการใช้งาน React และการตรวจสอบการซิงก์
- ตัวอย่าง React ใช้
TriplitClientและuseQuery - ไคลเอนต์ถูกสร้างจาก schema, server URL และ token
- สมัครรับผลลัพธ์คิวรี
todosด้วยuseQuery(client.query('todos')) - เมื่อเปลี่ยน checkbox จะสลับค่า
completedด้วยclient.update - หลังเริ่มแอปแล้ว หากเปิดแท็บเบราว์เซอร์อีกแท็บ จะเห็นว่าข้อมูลถูก ซิงก์แบบเรียลไทม์
เอกสารและช่องทางติดต่อ
- คู่มือเริ่มต้นทั้งหมด: getting started guide
- ทัวร์เรียนรู้แบบละเอียดเพิ่มเติม: building a real-time todo app with Triplit, Vite, and React
- สามารถถามคำถาม, ขอความช่วยเหลือในการเริ่มต้น, และดูตัวอย่างฟีเจอร์ใหม่ล่วงหน้าได้ที่ Discord
- ติดตามประกาศล่าสุดได้ทาง Twitter/X
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
ได้ลองใช้ Triplit ในโปรเจกต์ https://github.com/thanhnguyen2187/cryptaa แล้ว และมันทำงานได้ตามที่คาดไว้
โมเดลข้อมูลเข้ากับแนวคิดที่ใกล้เคียงแบบกระจายศูนย์/P2P มากกว่าการมี DB ศูนย์กลางเพียงตัวเดียวเป็นแหล่งความจริง แต่ยังรู้สึกว่า การโฮสต์เอง และ ภาษา query ยังน่าเสียดายอยู่
วิธีสร้างโทเค็นยืนยันตัวตนของเซิร์ฟเวอร์ในเอกสารไม่ชัดเจน จึงใช้คำสั่ง
devของ CLI เพื่อสร้างโทเค็น และการที่โทเค็นถูกทิ้งไว้เป็น log แบบ plain text ใน system service นั้นไม่ดีในเชิงความปลอดภัย แต่ผมมองว่าต้องมีปัญหาเรื่องสิทธิ์เข้าถึงที่ใหญ่กว่านั้นเป็นเงื่อนไขอยู่ก่อนDSL สำหรับ query แบบกำหนดเองขาดความสามารถในการแสดงออกอย่าง
UNIQUE,COUNTแบบ SQL ทำให้ต้องทำ aggregation บางส่วนเองช่วงหลังได้ดู Evolu https://www.evolu.dev/docs แล้วดูเหมือนมีขอบเขตและฟีเจอร์คล้ายกัน โดย Triplit มี
.subscribe()แต่ Evolu ไม่มี ส่วน Evolu เป็น typed SQL ที่อิงกับ Kysely ทำให้ query คุ้นเคยและขั้นสูงกว่า และบนเบราว์เซอร์ Evolu ใช้ SQLite บน OPFS ขณะที่ Triplit ดูเหมือนจะใช้ IndexedDBโพสต์ที่ลงใน Reddit: https://www.reddit.com/r/sveltejs/comments/1dndpj8/cryptaa_a...
ฝั่ง query ตอนนี้ยังไม่มี aggregation แต่มีอยู่ใน roadmap และคิดว่าถ้าใช้ประโยชน์จาก incremental query engine จะเปิดทางที่น่าสนใจได้
เช่น data dashboard ที่อัปเดตทุกชั่วโมง ในระบบเดิม ๆ (Postgres, MongoDB ฯลฯ) ต้องรัน query ใหม่ตั้งแต่ต้นทุกครั้ง แต่ถ้าประมวลผลเฉพาะข้อมูลใหม่ในแนวทางที่ใกล้เคียงกับ Materialize ก็จะอัปเดตต่อเนื่องได้อย่างมีประสิทธิภาพกว่ามาก
ยังไม่ได้ลองใช้ Evolu เอง แต่ใน Discord อาจมีคนที่เคยเปรียบเทียบอยู่: https://triplit.dev/discord
useQueryหรือวิธีที่แยกออกมาต่างหากเช่นกันเวลาจะใช้ DB ที่มี โปรโตคอลซิงก์แบบออฟไลน์ ดี ๆ แบบนี้ อยากรู้ว่าจัดการ schema evolution กันอย่างไรในสถานการณ์ที่อัปเกรดไคลเอนต์หลายเวอร์ชันพร้อมกันไม่ได้
เคยมีบริบทที่ต้องปวดหัวกับปัญหานี้ในแอปสุขภาพบนมือถือมาก่อน
ถ้าจำเป็นก็ต้องทำ dual write ไปยังทั้งสองเวอร์ชันพร้อมกัน
คล้ายกับการทำ live migration แบบไม่หยุดระบบสำหรับการเปลี่ยนแปลงที่ทำลายความเข้ากันได้ใน SQL DB แต่จุดเปลี่ยนผ่านขึ้นอยู่กับลูกค้า จึงต้องเก็บตรรกะนั้นไว้นานกว่า
ตารางที่ใช้ประสานเวอร์ชันล่าสุดก็สำคัญ และถ้ามีการเปลี่ยนแปลงที่ทำให้ใช้ร่วมกันไม่ได้ ก็ควรให้ไคลเอนต์ที่ตามหลังแจ้งผู้ใช้ให้อัปเกรด
ยังสามารถกำหนดได้ว่าจะต้องคงการอ่าน/เขียนสองทางไว้นานแค่ไหน โดยผูกกับเวอร์ชันขั้นต่ำที่ไคลเอนต์ทั้งหมดรองรับ
Triplit จะแสดงคำเตือนเมื่อคุณสร้างการเปลี่ยนแปลงที่ไม่เข้ากันได้ย้อนหลัง ดูได้ในเอกสาร: https://www.triplit.dev/docs/schemas/updating#pushing-the-sc...
แต่เมื่อเวลาผ่านไป อาจเกิดนิยาม schema ที่รกและมีชื่อชวนสับสนจำนวนมากขึ้นตามธรรมชาติ
ตอนนี้ยังไม่ได้ปล่อยโซลูชันสำหรับแก้เรื่องนี้ แต่กำลังทำบางอย่างให้เจ็บตัวน้อยลง และสำหรับพื้นหลังของแนวทางต่าง ๆ เอกสาร Cambria ถือว่ายอดเยี่ยม: https://www.inkandswitch.com/cambria/
ผู้ใช้อาจเก็บมือถือไว้ในลิ้นชักเป็นเวลา 2 ปี ดังนั้นแต่ละไคลเอนต์ก็แค่มายเกรตตัวเองให้เร็วที่สุดเท่าที่ทำได้
ทุกคนจะได้ไม่ต้องคิดระบบจัดการ migration ของตัวเองขึ้นมาใหม่
ยังไม่ค่อยเข้าใจว่าแอปแบบไหนที่การให้ไคลเอนต์เขียนลง DB ได้โดยตรงเป็นเรื่องโอเค และจะอยู่รอดได้อย่างไรโดย ไม่มี backend logic
มีข้อสงสัยเดียวกันกับ Supabase และ Firestore ด้วย เลยรู้สึกว่าน่าจะพลาดอะไรไปบางอย่าง
ในสภาพแวดล้อมองค์กร แน่นอนว่าตรงกันข้าม และผมหงุดหงิดเวลาเห็นการคุยที่มองข้ามเรื่องนี้
โดยเฉพาะเวลาเห็นคนใน tech Twitter สนับสนุนสแตกหรือวิธีทำงานบางแบบ จะเห็นชัดมากว่าเขาเคยทำแต่ CRUD โดยไม่เคยสร้างระบบธุรกิจจริง ๆ เลย จึงมักไม่เข้าใจว่าทำไมนักพัฒนาที่มีประสบการณ์ถึงไม่เห็นด้วย
แต่ดูไม่น่าจะเหมาะกับสิ่งที่มี backend logic เยอะ ๆ
ใน Supabase มีฟีเจอร์อย่าง row-level security เป็นต้น
ไคลเอนต์ส่งคำขอไปยัง Supabase ได้ก็จริง แต่ Supabase จะรัน query เพิ่มเติมที่ backend เพื่อตัดสินว่าคำขอที่เข้ามานั้นได้รับอนุญาตหรือไม่
ตัวอย่างง่าย ๆ คืออนุญาตให้อ่าน เขียน และอัปเดตแถวนั้นได้เฉพาะเมื่อค่าในคอลัมน์
UserIDตรงกับผู้ใช้ที่ยืนยันตัวตนในคำขอเท่านั้นเคยเก็บการตั้งค่าผู้ใช้ไว้ใน Triplit และการตั้งค่าเหล่านี้จำเป็นต้องให้ผู้ดูแลจัดการได้
ผู้ใช้ต้องรู้สึกว่าแอปทำงานอยู่ในเครื่องเสมอ และคุณภาพอินเทอร์เน็ตก็มักไม่ดี แต่เพราะต้องใช้งานข้ามอุปกรณ์หลายเครื่อง และผู้ดูแลต้องดูและจัดการการตั้งค่าของผู้ใช้อื่นได้ จึงจำเป็นต้องมี การซิงก์
โดยรวมแล้ว Triplit ยอดเยี่ยมทั้งในด้านประสบการณ์นักพัฒนา frontend และการสนับสนุน เมื่อพบ issue หรือ feature request ทีมก็จัดการให้อย่างรวดเร็วมาก
ถ้ามีคำตอบเรื่อง การ deploy แบบ high availability แล้ว ก็มีแผนจะย้ายข้อมูลที่สำคัญกว่านี้จาก Postgres มาด้วย
สงสัยว่าทำไมถึงเลือกไลเซนส์ AGPL
เหมือนเคยเห็นการนำเสนอบน YouTube https://www.youtube.com/playlist?list=PLTbD2QA-VMnXFsLbuPGz1... ในเซิร์ฟเวอร์ Discord ของ Local First https://localfirstweb.dev/ เลยดีใจที่ได้เห็นใน Show HN
อาจไม่ใช่กลุ่มเป้าหมายหลักเพราะไม่ได้ใช้ TypeScript และโดยมากใช้แนวทาง local-first กับแอปมือถือที่การเชื่อมต่อไม่เสถียร ต่างจากเว็บ โดยใช้ Flutter กับ backend ที่เป็น Rust
โซลูชัน local-first อื่น ๆ อย่าง ElectricSQL และ PowerSync ซิงก์ฐานข้อมูลฝั่ง client กับ server โดยตรง จึงเป็นอิสระจาก client/server มากกว่า
โซลูชันที่ใช้ CRDT ก็ใช้ได้ทั้งฝั่ง client และ server ผ่าน FFI เช่น automerge เป็น Rust จึงใช้กับ Flutter ผ่าน FFI ด้วย
flutter_rust_bridgeหรือบนเว็บผ่าน WASM และบน backend ด้วย Rust ได้Triplit ดูเหมือนเป็นการซิงก์ client-server แบบดั้งเดิมมากกว่า โดยให้ server เป็นแหล่งความจริง แทนที่จะเป็นการแก้ conflict ระหว่าง client ต่าง ๆ แบบไร้การชนกัน
สงสัยว่าทำไมถึงเลือก โซลูชันระดับภาษา แทนแนวทางชั้นฐานข้อมูลที่เป็นอิสระจาก client และ server มากกว่า และดูเหมือนในอนาคตจะรองรับภาษาและ framework ที่ไม่ใช่ฐาน JS ได้ยาก
อีกทั้งดูเหมือนจะพยายามแข่งกับ Supabase แต่ Supabase เองก็กำลังทดลองการซิงก์ระดับฐานข้อมูลของ Postgres และ CRDT อยู่ จึงอาจไล่ทันได้ https://news.ycombinator.com/item?id=33931971
แต่ตอนเริ่มต้นตัดสินใจโฟกัสที่ TypeScript ล้วน เพราะตลาดใหญ่พอ เชื่อในอนาคตของ PWA และมองว่าต้องโฟกัสตรงนั้นจึงจะสร้างประสบการณ์ที่ดีที่สุดได้
สักวันหนึ่งคงจะสร้างอะไรที่เป็นอิสระจากแพลตฟอร์มมากกว่านี้ แต่ยังไม่แน่ชัดว่าเมื่อไร
ทั้งทีม ElectricSQL และ Supabase ล้วนยอดเยี่ยมและรอบคอบ และน่าจะเติบโตต่อไปในสาย SQL ซึ่งนี่คือความแตกต่างพื้นฐานที่สุดของแนวทาง
Triplit มองว่าการหลีกเลี่ยง SQL จะให้ประสบการณ์ที่ดีที่สุดแก่นักพัฒนาได้ และมีพื้นที่มากพอให้ทั้งสองปรัชญาอยู่ร่วมกัน
ถ้าเป็น LWW สงสัยว่าปริมาณข้อมูลของ client จะเพิ่มแบบเชิงเส้นตามจำนวน operation หรือไม่
กล่าวคือ ยิ่งผู้ใช้แก้ไข DB มากขึ้น operation log ก็จะโตขึ้นเรื่อย ๆ หรือมี checkpoint หรือไม่ และถ้าผู้ใช้ทำ operation หลายล้านครั้งต่อวัน จะขยายตัวในแง่พื้นที่อย่างไร
อย่างไรก็ตาม LWW register เองไม่ได้บังคับว่าต้องเก็บประวัติ นี่เป็นเพียง implementation ปัจจุบันเพื่อให้ client ที่ offline ไปนานสามารถซิงก์ได้อย่างมีประสิทธิภาพ
ยังพูดได้ไม่เต็มปากว่ารองรับถึงวันละหนึ่งล้าน operation แล้ว แต่ข้อดีคือ server มีอำนาจตัดสิน
ในอนาคต Triplit server สามารถติดตาม timestamp การซิงก์ล่าสุดของ client แต่ละราย และค่อย ๆ prune ประวัติได้ คล้ายกับวิธีที่ Postgres ใช้
VACUUMจัดการ tuple ที่ตายแล้วถ้ามี Rust bindings ให้ใช้กับ Tauri ได้ก็คงดี
เมื่อรวมการเติบโตของ Tauri การรองรับอุปกรณ์มือถือที่กำลังจะมา และความนิยมของ SQLite ในช่วงหลัง อาจช่วยเติมช่องว่างของแอปแบบ offline-first และกลายเป็นตัวเลือกเริ่มต้นของทีมพัฒนาจำนวนมากได้
ElectricSQL ทำงานที่ชั้นฐานข้อมูลจึงเป็นอิสระจากภาษา และใช้ Rust บน server อยู่แล้ว ดังนั้น Rust bindings จึงทำงานได้ทั้งฝั่ง client และ server
ถ้าอยากร่วมพัฒนาก็บอกได้
ถ้าเป็นอย่างนั้น Triplit ก็น่าจะทำงานได้ทันที
ใช้ Triplit ในแอป React Native มาพักหนึ่งแล้ว และทำงานได้ดีมาก
แนะนำอย่างยิ่ง และเป็น DB แบบ local-first ตัวเดียวที่ตอบโจทย์เงื่อนไขที่ต้องการทั้งหมด
มี query language ที่เหมาะสมและ sane (ไม่ใช่ SQL), รองรับ TypeScript ดีเยี่ยม, รองรับ offline, รองรับ React Native และยังดีที่เป็น open source กับ self-host ได้
สงสัยว่าใช้ร่วมกับ PostgreSQL DB ที่มีอยู่แล้วไม่ได้หรือ
ยังไม่พร้อมเปิดเผย แต่ตั้งใจว่าจะให้คนได้ลองใช้เร็ว ๆ นี้
ผมเองก็กำลังเอนเอียงไปทางใช้ตัวนั้น