วิธีใช้งาน SQLite บน VPS สำหรับแอปพลิเคชันขนาดเล็ก
เรียนรู้วิธีตั้งค่า SQLite ให้พร้อมใช้งานบน VPS อย่างปลอดภัยด้วยโหมด WAL และ busy_timeout พร้อมเทคนิคสำรองข้อมูลด้วย Litestream และข้อจำกัดที่ควรทราบก่อนเริ่มใช้งานจริง
เมื่อ SQLite เป็นฐานข้อมูลที่เหมาะสมสำหรับใช้งานจริงบน VPS
การใช้งาน SQLite ในสภาพแวดล้อมจริงบน VPS เป็นทางเลือกที่เหมาะสมสำหรับแอปพลิเคชันขนาดเล็กส่วนใหญ่ โดยมีเหตุผลที่เรียบง่ายคือ: กระบวนการทำงานหนึ่งเดียวบนเครื่องเดียวที่เขียนข้อมูลลงในไฟล์เดียวไม่จำเป็นต้องมีเซิร์ฟเวอร์ฐานข้อมูล ไม่ต้องมี daemon ให้คอยดูแล ไม่ต้องเปิดพอร์ตในไฟร์วอลล์ ไม่ต้องหมุนเวียนรหัสผ่าน และไม่ต้องมีเครื่องที่สองเพื่อคอยประคองสถานะ การสืบค้นข้อมูลเป็นการเรียกใช้ฟังก์ชันแทนที่จะเป็นการรับส่งข้อมูลผ่านเครือข่าย ดังนั้นหน้าเว็บที่รันการสืบค้น 40 ครั้งจึงมีต้นทุนเพียงแค่การเรียกใช้ฟังก์ชัน 40 ครั้งเท่านั้น
ข้อจำกัดนั้นมีขอบเขตที่ชัดเจนและเป็นจริง SQLite อนุญาตให้มีผู้เขียนข้อมูลได้ครั้งละหนึ่งรายต่อไฟล์ฐานข้อมูลทั้งหมด และไฟล์ดังกล่าวไม่สามารถแชร์ระหว่างสองเครื่องได้ ข้อจำกัดทั้งสองประการนี้ไม่มีปัญหาสำหรับ VPS เครื่องเดียวที่รันแอปพลิเคชันเดียว แต่ทั้งสองประการจะกลายเป็นปัญหาใหญ่ทันทีที่คุณขยายขนาดเกินกว่ารูปแบบดังกล่าว คู่มือนี้จะครอบคลุมถึงการตั้งค่าที่ทำให้ SQLite ปลอดภัยบนเซิร์ฟเวอร์ การสำรองข้อมูลอย่างต่อเนื่องด้วย Litestream และจุดที่คุณควรหยุดใช้งาน
ติดตั้งเครื่องมือบรรทัดคำสั่งก่อน ทุกขั้นตอนด้านล่างนี้ดำเนินการบน Ubuntu 24.04
sudo apt update
sudo apt install -y sqlite3
sqlite3 --versionคำสั่งดังกล่าวจะแสดงเวอร์ชันที่ขึ้นต้นด้วย 3. ตามด้วยวันที่สร้างและแฮชของซอร์สโค้ด Ubuntu 24.04 มาพร้อมกับ SQLite 3.45.1 ณ เดือนกรกฎาคม 2026 แอปพลิเคชันของคุณอาจไม่ได้ใช้ไบนารีนี้ เนื่องจากรันไทม์ของภาษาโปรแกรมส่วนใหญ่มักจะรวมไลบรารี SQLite ของตนเองมาด้วย ซึ่งมักจะเป็นเวอร์ชันที่ใหม่กว่า ดังนั้นควรตรวจสอบเวอร์ชันที่ไดรเวอร์ฐานข้อมูลของคุณรายงานก่อนที่จะพึ่งพาฟีเจอร์ใหม่ๆ
เหตุใดโหมด WAL จึงเป็นสิ่งแรกที่คุณควรเปลี่ยน
โดยค่าเริ่มต้น SQLite จะใช้ rollback journal ก่อนที่จะแก้ไขหน้าข้อมูล (page) ระบบจะคัดลอกหน้าข้อมูลต้นฉบับไปยังไฟล์ -journal จากนั้นจึงแก้ไขฐานข้อมูลโดยตรง เพื่อให้การดำเนินการนี้ปลอดภัย ระบบจะทำการล็อกแบบเอกสิทธิ์ (exclusive lock) ทั้งไฟล์ ส่งผลให้ผู้อ่านทุกคนต้องรอในขณะที่มีการเขียนข้อมูลเกิดขึ้น บนแล็ปท็อปอาจไม่มีใครสังเกตเห็นผลกระทบนี้ แต่บนเว็บเซิร์ฟเวอร์ การเขียนข้อมูลที่ล่าช้าเพียงครั้งเดียวจะทำให้ทุกคำขอที่เข้าถึงฐานข้อมูลต้องหยุดชะงัก
โหมด WAL (write-ahead log) จะเปลี่ยนลำดับการทำงานนี้ โดยผู้เขียนจะผนวกหน้าข้อมูลใหม่ลงในไฟล์ -wal แยกต่างหากและปล่อยให้ฐานข้อมูลหลักเป็นอิสระ ผู้อ่านจะยังคงอ่านไฟล์หลักจากสแนปชอต ณ เวลาที่เริ่มอ่าน ดังนั้นผู้อ่านจะไม่บล็อกผู้เขียน และผู้เขียนก็จะไม่บล็อกผู้อ่าน ในภายหลังกระบวนการ checkpoint จะคัดลอกหน้าข้อมูลที่สะสมอยู่ใน WAL กลับเข้าสู่ฐานข้อมูลหลัก การเปลี่ยนแปลงเพียงจุดเดียวนี้คือปัจจัยสำคัญที่ทำให้ SQLite สามารถใช้งานร่วมกับเว็บแอปพลิเคชันได้อย่างมีประสิทธิภาพ
เปิดใช้งานโหมด WAL และยืนยันสถานะ
mkdir -p ~/app
sqlite3 ~/app/app.db "PRAGMA journal_mode=WAL;"คำสั่งนี้จะแสดงผลลัพธ์เป็น wal ผลลัพธ์ดังกล่าวไม่ใช่เพียงการแสดงผลทั่วไป PRAGMA journal_mode จะส่งค่าโหมดที่ฐานข้อมูลกำลังใช้งานอยู่จริง ดังนั้นหากได้รับคำตอบเป็น delete หมายความว่าการเปลี่ยนแปลงล้มเหลวและคุณยังคงใช้งาน rollback journal อยู่
โหมด WAL มีสถานะคงอยู่ถาวร โดยเป็นแฟล็กที่อยู่ในส่วนหัวของฐานข้อมูล (database header) ไม่ใช่การตั้งค่าเฉพาะการเชื่อมต่อ ดังนั้นคุณจึงรันคำสั่งเพียงครั้งเดียวต่อไฟล์ฐานข้อมูลหนึ่งไฟล์ และการเชื่อมต่อทุกครั้งหลังจากนั้นจะได้รับค่านี้ไปใช้งาน รวมถึงหลังจากรีบูตระบบด้วย คุณสามารถพิสูจน์ได้ด้วยการสร้างการเชื่อมต่อใหม่
sqlite3 ~/app/app.db "PRAGMA journal_mode;"จากนั้นสร้างตารางและตรวจสอบสิ่งที่ปรากฏบนดิสก์
sqlite3 ~/app/app.db <<'SQL'
CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT NOT NULL);
INSERT INTO notes (body) VALUES ('first row');
SQL
ls -l ~/app/ขณะนี้มีไฟล์อยู่ 3 ไฟล์ ได้แก่ app.db, app.db-wal และ app.db-shm ไฟล์ -wal จะเก็บหน้าข้อมูลที่คอมมิตแล้วแต่ยังไม่ได้ทำ checkpoint ส่วนไฟล์ -shm เป็นดัชนีหน่วยความจำร่วม (shared memory index) ที่การเชื่อมต่อทุกรายการจะแมปเข้าถึง เพื่อให้ทุกการเชื่อมต่อรับทราบข้อมูลที่ตรงกันใน WAL ไฟล์ทั้งสองเป็นส่วนหนึ่งของฐานข้อมูลและไม่ใช่ไฟล์ชั่วคราว หากคุณคัดลอกเฉพาะ app.db ในขณะที่แอปพลิเคชันกำลังทำงาน คุณจะได้ไฟล์ที่ขาดข้อมูลการคอมมิตล่าสุดทั้งหมด หากคุณลบ app.db ทิ้งโดยปล่อยอีกสองไฟล์ไว้ SQLite จะนำหน้าข้อมูล WAL ที่ล้าสมัยเหล่านั้นไปปรับใช้กับไฟล์ใหม่ที่สร้างขึ้นภายใต้ชื่อเดิม ซึ่งเป็นสาเหตุที่ทำให้ฐานข้อมูลใหม่เสียหายในขณะที่ผู้ใช้พยายามรีเซ็ตฐานข้อมูล
การตั้งค่าการเชื่อมต่อที่แอปพลิเคชันระดับ Production ทุกตัวจำเป็นต้องมี
มีเพียง journal_mode เท่านั้นที่ถูกจัดเก็บไว้ในฐานข้อมูล การตั้งค่าอื่น ๆ ทั้งหมดที่ระบุด้านล่างนี้เป็นแบบต่อการเชื่อมต่อ (per connection) ซึ่งหมายความว่าแอปพลิเคชันของคุณต้องรันคำสั่งเหล่านี้ทุกครั้งที่เปิดการเชื่อมต่อ รวมถึงทุกการเชื่อมต่อที่ Pool สร้างขึ้นในเบื้องหลังด้วย
PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 5000;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;busy_timeout = 5000 สั่งให้ SQLite พยายามเชื่อมต่อกับฐานข้อมูลที่ถูกล็อกซ้ำเป็นเวลาสูงสุด 5000 มิลลิวินาทีก่อนที่จะส่งค่า database is locked กลับมา ค่าเริ่มต้นคือ 0 ดังนั้นโดยปกติแล้ว SQLite จะล้มเหลวทันทีหากมีการเขียนข้อมูลทับซ้อนกัน การตั้งค่าเพียงค่าเดียวนี้จะช่วยลดข้อผิดพลาดเรื่องการล็อกส่วนใหญ่ที่มักถูกเข้าใจผิดว่าเป็นปัญหาของ SQLite เอง
synchronous = NORMAL เป็นการตั้งค่าที่เหมาะสมเมื่อใช้งานในโหมด WAL และควรทำความเข้าใจถึงข้อแลกเปลี่ยนที่เกิดขึ้น ที่ระดับ FULL ตัว SQLite จะเรียกใช้ fsync บน WAL ในทุกการ commit ส่วนที่ระดับ NORMAL จะทำการ sync เฉพาะในช่วง checkpoint เท่านั้น เอกสารของ SQLite ระบุไว้อย่างชัดเจนถึงสิ่งที่คุณต้องสูญเสียไป นั่นคือธุรกรรม (transactions) จะไม่มีความทนทาน (durable) อีกต่อไปหากเกิดไฟฟ้าดับหรือการรีเซ็ตเครื่องอย่างกะทันหัน ฐานข้อมูลจะไม่เสียหายจากการสูญเสียพลังงานดังกล่าว เพียงแต่คุณจะสูญเสียข้อมูลการ commit ล่าสุดที่ยังไม่ได้ถูกเขียนลงดิสก์ สำหรับการใช้งานบน VPS นี่มักจะเป็นการแลกเปลี่ยนที่คุ้มค่า เพราะช่วยลดภาระการทำ fsync ออกจากกระบวนการเขียนข้อมูลในทุก ๆ ครั้ง
foreign_keys = ON ถูกปิดไว้โดยค่าเริ่มต้นเพื่อความเข้ากันได้กับเวอร์ชันก่อนหน้า และมีผลต่อการเชื่อมต่อเท่านั้น สคีมาที่เต็มไปด้วยคำสั่ง REFERENCES จะไม่มีผลบังคับใช้ใด ๆ จนกว่าแต่ละการเชื่อมต่อจะเปิดใช้งานฟังก์ชันนี้
มีการตั้งค่าอีกหนึ่งอย่างที่สำคัญในภายหลัง SQLite จะทำ checkpoint โดยอัตโนมัติเมื่อไฟล์ WAL มีขนาดเกิน 1000 หน้า และงานนี้จะถูกดำเนินการโดยการเชื่อมต่อใดก็ตามที่ทำธุรกรรมเสร็จสิ้นในขณะนั้น ซึ่งถือว่าเพียงพอสำหรับการใช้งานทั่วไป แต่จะกลายเป็นประเด็นเมื่อมีการรัน Litestream เนื่องจาก Litestream ต้องการควบคุมช่วงเวลาในการทำ checkpoint ด้วยตนเอง
เหตุใด database is locked จึงยังคงเกิดขึ้นหลังจากที่คุณตั้งค่า busy_timeout แล้ว
นี่คือความล้มเหลวที่ทำให้ผู้ใช้งานหันกลับไปใช้ Postgres และมันมีสาเหตุเฉพาะเพียงประการเดียว
การตั้งค่า busy timeout จะเป็นการติดตั้ง busy handler แต่ SQLite ไม่ได้รับประกันว่าจะเรียกใช้งานฟังก์ชันดังกล่าวเสมอไป
หาก SQLite พิจารณาว่าการเรียกใช้ busy handler อาจนำไปสู่ภาวะ deadlock ระบบจะส่งคืนค่า SQLITE_BUSY ให้กับแอปพลิเคชันทันทีแทนที่จะเรียกใช้ busy handler
ภาวะ deadlock ที่ระบบพยายามหลีกเลี่ยงจะเกิดขึ้นเมื่อมีการอัปเกรดทรานแซกชัน คำสั่ง BEGIN เปล่าๆ ใน SQLite หมายถึง BEGIN DEFERRED หากคำสั่งแรกหลังจากนั้นคือ SELECT แสดงว่าคุณอยู่ในทรานแซกชันแบบอ่าน เมื่อ UPDATE ในทรานแซกชันเดียวกันนั้นจำเป็นต้องเปลี่ยนเป็นทรานแซกชันแบบเขียน และมีการเขียนข้อมูลจากคอนเนคชันอื่นเกิดขึ้นตั้งแต่คุณเริ่มอ่าน SQLite จะไม่สามารถทำให้คุณรอได้ เนื่องจากสแนปชอตของคุณล้าสมัยไปแล้ว และการรอจะทำให้คอนเนคชันทั้งสองเกิดภาวะ deadlock ต่อกัน เอกสารระบุผลลัพธ์ไว้โดยตรงดังนี้:
คำสั่งเขียนที่ตามมาจะอัปเกรดทรานแซกชันเป็นทรานแซกชันแบบเขียนหากเป็นไปได้ หรือส่งคืนค่า SQLITE_BUSY
การตั้งค่า timeout 5000 มิลลิวินาทีของคุณจึงไม่ได้ถูกนำมาพิจารณาเลย ข้อผิดพลาดจะปรากฏขึ้นทันที ซึ่งเป็นเหตุผลว่าทำไมการตั้งค่าดังกล่าวจึงดูเหมือนไม่มีผลใดๆ
วิธีแก้ไขมีเพียงคำเดียว
BEGIN IMMEDIATE;
UPDATE notes SET body = 'edited' WHERE id = 1;
COMMIT;BEGIN IMMEDIATE จะทำการล็อกแบบเขียนตั้งแต่เริ่มต้น ก่อนที่จะอ่านข้อมูลใดๆ จึงไม่มีการอัปเกรดทรานแซกชัน ดังนั้นจึงไม่มีภาวะ deadlock ให้ต้องหลีกเลี่ยง ส่งผลให้ busy handler ทำงานและคอนเนคชันจะรอคิวแทนที่จะล้มเหลว ให้คงทรานแซกชันแบบอ่านอย่างเดียวไว้เป็นแบบ deferred ส่วนทรานแซกชันใดก็ตามที่มีการเขียนควรตั้งค่าเป็น immediate
สาเหตุที่สองของข้อผิดพลาดเรื่องการล็อกนั้นสังเกตได้ยากกว่า นั่นคือการเปิดทรานแซกชันแบบเขียนค้างไว้ในขณะที่ประมวลผลงานที่ล่าช้า SQLite จะจัดลำดับการเขียนแบบอนุกรม ดังนั้นทรานแซกชันที่เปิดขึ้นมา เรียกใช้ API ภายนอกผ่านเครือข่าย แล้วจึงค่อย commit จะบล็อกผู้เขียนรายอื่นทั้งหมดตลอดระยะเวลาของการเรียกใช้งานนั้น ให้คุณอ่านข้อมูลที่จำเป็น ปิดทรานแซกชัน ทำงานที่ล่าช้าให้เสร็จสิ้น จากนั้นจึงเปิดทรานแซกชันแบบเขียนสั้นๆ เพื่อบันทึกผลลัพธ์แทน
การสำรองข้อมูลอย่างต่อเนื่องด้วย Litestream
การคัดลอกข้อมูลแบบรายคืนอาจทำให้ข้อมูลที่เขียนลงไปสูญหายได้สูงสุดถึงหนึ่งวัน และการรัน cp กับฐานข้อมูล SQLite ที่กำลังใช้งานอยู่อาจทำให้ได้ไฟล์สำรองที่ไม่สามารถเปิดใช้งานได้ มีสองวิธีที่ปลอดภัย วิธีแรกคือ sqlite3 app.db ".backup /path/to/backup.db" ซึ่งใช้ส่วนติดต่อการสำรองข้อมูลออนไลน์ของ SQLite และสามารถทำงานกับฐานข้อมูลที่กำลังใช้งานอยู่ได้ ส่วน Litestream นั้นก้าวไปไกลกว่านั้น โดยจะคอยเฝ้าดู WAL และส่งการเปลี่ยนแปลงไปยังที่จัดเก็บข้อมูลแบบ Object Storage อย่างต่อเนื่อง ซึ่งช่วยลดความเสี่ยงในการสูญเสียข้อมูลจากระดับหนึ่งวันให้เหลือเพียงประมาณหนึ่งวินาที
Litestream เป็นไฟล์ไบนารี Go เพียงไฟล์เดียวที่ทำงานควบคู่ไปกับแอปพลิเคชันของคุณ โดยไม่ได้ทำหน้าที่เป็นตัวกลางระหว่างแอปพลิเคชันกับฐานข้อมูล แอปพลิเคชันของคุณจะเขียนข้อมูลลง SQLite ตามปกติเหมือนเดิม และ Litestream จะทำหน้าที่อ่าน WAL แล้วอัปโหลดเฉพาะส่วนที่มีการเปลี่ยนแปลงเท่านั้น
cd /tmp
curl -fsSL -O https://github.com/benbjohnson/litestream/releases/download/v0.5.14/litestream-0.5.14-linux-x86_64.deb
sudo dpkg -i litestream-0.5.14-linux-x86_64.deb
litestream versionv0.5.14 คือเวอร์ชันที่หน้าการติดตั้งสำหรับ Linux อย่างเป็นทางการระบุไว้ ณ เดือนกรกฎาคม 2026 และ v0.5.15 ได้ถูกปล่อยออกมาเมื่อวันที่ 21 กรกฎาคม 2026 ให้เปลี่ยนเวอร์ชันในทั้งสองบรรทัดให้ตรงกับแท็กปัจจุบันบนหน้า Releases และหาก VPS ของคุณเป็นสถาปัตยกรรม arm64 ให้เลือกใช้แพ็กเกจ arm64 ที่ตรงกันแทน
ไฟล์การตั้งค่าจะอยู่ที่ /etc/litestream.yml ให้เริ่มต้นด้วยการทำ Replica ลงไฟล์ในเครื่องก่อน เพราะวิธีนี้ช่วยพิสูจน์การทำงานของลูปทั้งหมดได้โดยไม่ต้องใช้ข้อมูลรับรอง (Credentials) ของระบบคลาวด์
dbs:
- path: /home/appuser/app/app.db
replica:
type: file
path: /var/backups/litestream/appโปรดสังเกตว่าฟิลด์ที่ใช้คือ replica ซึ่งเป็นรูปเอกพจน์ Litestream 0.5 ได้เปลี่ยนจากการใช้ชุดข้อมูล replicas ในซีรีส์ 0.3 มาเป็นการใช้บล็อก Replica เพียงบล็อกเดียว ดังนั้นไฟล์การตั้งค่าที่มีรายการซ้ำสองรายการจะทำให้โปรแกรมไม่สามารถเริ่มทำงานได้ คู่มือจากแหล่งอื่นจำนวนมากยังคงแสดงตัวอย่างแบบเก่าอยู่ ดังนั้นให้คัดลอกรูปแบบจากด้านบนนี้แทนการใช้ตัวอย่างแรกที่พบจากการค้นหา นอกจากนี้ ซีรีส์ 0.5 ยังได้เปลี่ยนชื่อคำสั่งย่อยจาก litestream wal เป็น litestream ltx เนื่องจากรูปแบบการสำรองข้อมูลบนดิสก์ได้เปลี่ยนไป
ตรวจสอบให้แน่ใจว่าไฟล์การตั้งค่าถูกต้องก่อนที่จะเปิดใช้งานฟังก์ชันใดๆ
sudo litestream databases -config /etc/litestream.ymlจากนั้นให้ทดสอบการทำงานแบบครบวงจรด้วยตนเอง รูปแบบคำสั่งนี้จะข้ามไฟล์การตั้งค่าและทำ Replica ฐานข้อมูลหนึ่งไฟล์ไปยังหนึ่งเส้นทาง
mkdir -p /tmp/replica
litestream replicate ~/app/app.db file:///tmp/replica/appคำสั่งดังกล่าวจะทำงานที่เบื้องหน้าและทำงานต่อไปเรื่อยๆ ในเชลล์ที่สอง ให้ลองเขียนข้อมูลลงไปหนึ่งแถวแล้วกู้คืน Replica ไปยังไฟล์ใหม่
sqlite3 ~/app/app.db "INSERT INTO notes (body) VALUES ('written after replication started');"
litestream restore -o /tmp/restored.db file:///tmp/replica/app
sqlite3 /tmp/restored.db "SELECT count(*) FROM notes;"จำนวนแถวที่นับได้จะรวมแถวใหม่ที่เพิ่มเข้าไปด้วย หากไม่พบข้อมูลดังกล่าว แสดงว่าการเปลี่ยนแปลงยังไม่ได้ซิงค์ เนื่องจาก Litestream จะทำการพุชข้อมูลตามค่า sync-interval ซึ่งมีค่าเริ่มต้นอยู่ที่ 1 วินาที ดังนั้นให้รอสักครู่แล้วลองกู้คืนใหม่อีกครั้ง ระยะเวลาหนึ่งวินาทีนั้นคือจุดกู้คืนข้อมูลของคุณ หากระบบล่ม ข้อมูลที่สูญหายจะเท่ากับข้อมูลที่เขียนในช่วงเวลาซิงค์ล่าสุดเท่านั้น และไม่มีการตั้งค่าใดที่จะทำให้ค่านี้เป็นศูนย์ได้
สำหรับการจัดเก็บข้อมูลจริง ให้เปลี่ยนบล็อก Replica เป็น URL ของ S3 ซึ่งสามารถใช้งานได้กับ Amazon S3 และ Object Storage อื่นๆ ที่รองรับโปรโตคอล S3
dbs:
- path: /home/appuser/app/app.db
replica:
url: s3://your-bucket-name/app
region: us-east-1
snapshot:
interval: 24h
retention: 24hอย่าใส่ข้อมูลรับรองไว้ในไฟล์การตั้งค่า Litestream จะอ่านค่า LITESTREAM_ACCESS_KEY_ID และ LITESTREAM_SECRET_ACCESS_KEY จากสภาพแวดล้อม (Environment) ดังนั้นให้ใส่ค่าเหล่านี้ไว้ในไฟล์ drop-in ของ systemd ที่มีเจ้าของเป็น root และตั้งค่าสิทธิ์เป็น 600
ค่า snapshot ด้านบนคือค่าเริ่มต้น และค่าเริ่มต้นของการเก็บรักษา (Retention) มักทำให้ผู้ใช้ประหลาดใจ ค่า Retention คือระยะเวลาที่ Litestream จะเก็บ snapshot และไฟล์ที่เกี่ยวข้องไว้ ซึ่งหมายถึงระยะเวลาย้อนหลังที่คุณสามารถกู้คืนข้อมูลได้ การตั้งค่าไว้ที่ 24 ชั่วโมงหมายความว่าหากคุณพบข้อผิดพลาดในการย้ายข้อมูลในเช้าวันพุธ คุณจะไม่สามารถกู้คืนข้อมูลจากสถานะของวันจันทร์ได้อีกต่อไป ดังนั้นควรตั้งค่า retention: 168h ไว้ที่หนึ่งสัปดาห์และยอมจ่ายค่าพื้นที่จัดเก็บข้อมูลที่เพิ่มขึ้นจะดีกว่า
พิสูจน์การกู้คืนข้อมูลก่อนที่คุณจะจำเป็นต้องใช้มัน
litestream restore -o /tmp/check.db /home/appuser/app/app.db
sqlite3 /tmp/check.db "PRAGMA integrity_check;"
sqlite3 /tmp/check.db "SELECT count(*) FROM notes;"เมื่อกำหนดพาธของฐานข้อมูล litestream restore จะค้นหาแบบจำลองที่ตรงกันใน /etc/litestream.yml และดึงข้อมูลลงมา PRAGMA integrity_check จะแสดงผล ok หากไฟล์อยู่ในสถานะปกติ และหากมีผลลัพธ์อื่นแสดงออกมา หมายความว่าสำเนาที่กู้คืนมานั้นไม่สามารถใช้งานได้ ให้รันคำสั่งนี้ตามกำหนดเวลาด้วย systemd service และ timer และตรวจสอบผลลัพธ์ที่ได้ จนกว่าคุณจะทำการกู้คืนข้อมูลสำรองได้สำเร็จสักครั้ง คุณจะไม่มีทางทราบได้เลยว่าระบบสำรองข้อมูลของคุณใช้งานได้จริงหรือไม่
การรัน Litestream ภายใต้ systemd
แพ็กเกจ Debian จะติดตั้งยูนิต litestream ซึ่งอ่านค่าจาก /etc/litestream.yml
sudo systemctl enable litestream
sudo systemctl start litestream
sudo journalctl -u litestream -fเอาต์พุตที่ปกติจะแสดงชื่อฐานข้อมูลแต่ละรายการจากไฟล์คอนฟิก จากนั้นจะเงียบไปยกเว้นบรรทัดการซิงค์ที่แสดงเป็นระยะ หากเกิดข้อผิดพลาด no such file or directory ที่เส้นทางฐานข้อมูลของคุณ แสดงว่าเส้นทางในไฟล์คอนฟิกไม่ถูกต้อง หรือกระบวนการไม่สามารถอ่านไฟล์นั้นได้ โดยค่าเริ่มต้นยูนิตจะรันในฐานะ root ซึ่งมีสิทธิ์เกินความจำเป็นสำหรับงานนี้ Litestream จำเป็นต้องสามารถอ่านและเขียนได้ทั้งตัวฐานข้อมูลและไดเรกทอรีที่เก็บฐานข้อมูลนั้น เนื่องจาก Litestream ทำงานร่วมกับไฟล์ -wal และ -shm ที่อยู่ถัดจากฐานข้อมูลของคุณ ดังนั้นควรระบุบัญชีผู้ใช้เดียวกับที่แอปพลิเคชันของคุณใช้งานอยู่
# /etc/systemd/system/litestream.service.d/override.conf
[Service]
User=appuser
Group=appuserนำไปใช้ด้วย sudo systemctl daemon-reload และ sudo systemctl restart litestream การตั้งค่า บัญชีบริการเฉพาะที่มีสิทธิ์น้อยที่สุด ใช้เวลาเพียงไม่กี่นาที และเป็นสิ่งที่สร้างความแตกต่างระหว่างเอเจนต์สำรองข้อมูลกับกระบวนการ root ลำดับที่สองบนเครื่อง
มีรายละเอียดเรื่องลำดับการทำงานหนึ่งประการที่สำคัญหากคุณจำเป็นต้องสร้างเครื่องขึ้นมาใหม่ทั้งหมด คุณต้องการให้ฐานข้อมูลถูกกู้คืนก่อนที่แอปพลิเคชันจะเริ่มทำงาน litestream restore รองรับ -if-db-not-exists ซึ่งจะส่งค่าสถานะ 0 ออกมาเมื่อไฟล์มีอยู่แล้ว จึงปลอดภัยที่จะรันในทุกครั้งที่บูตเครื่อง ให้ใส่คำสั่งนี้ไว้ในบรรทัด ExecStartPre ของยูนิตแอปพลิเคชันของคุณ แล้ว VPS เครื่องใหม่จะดึงฐานข้อมูลลงมา ในขณะที่เครื่องที่มีอยู่เดิมจะไม่ดำเนินการใดๆ litestream replicate มีแฟล็ก -restore-if-db-not-exists ที่สอดคล้องกันหากคุณต้องการรวมไว้ในที่เดียว
จุดที่ SQLite ทำงานไม่ได้บน VPS
ระบบไฟล์เครือข่าย (Network filesystems) นี่คือข้อจำกัดที่คุณไม่สามารถแก้ไขได้ด้วยการตั้งค่า โหมด WAL กำหนดให้ทุกกระบวนการที่ใช้งานฐานข้อมูลต้องแชร์พื้นที่หน่วยความจำขนาดเล็ก ซึ่งเป็นสิ่งที่ไฟล์ -shm จัดเตรียมไว้ เอกสารของ SQLite ระบุหลักการนี้ไว้อย่างชัดเจนโดยไม่มีข้อยกเว้น:
กระบวนการทั้งหมดที่ใช้ฐานข้อมูลต้องอยู่บนโฮสต์คอมพิวเตอร์เครื่องเดียวกัน WAL ไม่ทำงานผ่านระบบไฟล์เครือข่าย
ดังนั้น ฐานข้อมูลที่อยู่บน NFS (network file system) หรือ SMB share ที่เมาท์ไว้ อาจเกิดความเสียหายได้ และไม่มี pragma ใดที่ป้องกันปัญหานี้ได้ มีความแตกต่างที่หลายคนมองข้าม อุปกรณ์บล็อกเครือข่าย (network block device) ซึ่งเป็นสิ่งที่ผู้ให้บริการ VPS ส่วนใหญ่ใช้เป็นพื้นที่จัดเก็บข้อมูลเสริม จะปรากฏใน Linux เหมือนกับดิสก์ทั่วไปที่มีระบบไฟล์ปกติ ซึ่งกรณีนี้สามารถใช้งานได้ แต่การเมาท์ไฟล์แชร์ (file share) นั้นใช้งานไม่ได้
เซิร์ฟเวอร์แอปพลิเคชันเครื่องที่สอง ไม่มีการตั้งค่าใดที่ทำให้กรณีนี้ทำงานได้ เมื่อใดที่คุณต้องการเครื่องสองเครื่องเพื่อให้บริการข้อมูลชุดเดียวกัน คุณจำเป็นต้องใช้ฐานข้อมูลที่สื่อสารผ่านเครือข่ายได้ ให้ตัดสินใจเปลี่ยนผ่านในขณะที่คุณยังมีเวลาวางแผน
ภาระงานที่มีการเขียนข้อมูลจำนวนมาก (Write-heavy workloads) การอนุญาตให้เขียนข้อมูลได้ทีละหนึ่งกระบวนการเป็นคุณสมบัติของรูปแบบไฟล์ ไม่ใช่สิ่งที่ปรับแต่งได้ การเขียนข้อมูลขนาดเล็กมีต้นทุนต่ำเนื่องจากการคอมมิตแต่ละครั้งคือการต่อท้ายไฟล์ WAL ดังนั้นปริมาณงาน (throughput) จึงขึ้นอยู่กับความหน่วงในการเขียนข้อมูลขนาดเล็กของดิสก์มากกว่า CPU ของคุณ ดูที่ ความแตกต่างระหว่าง NVMe กับ SATA SSD บน VPS เพื่อดูว่าความแตกต่างนั้นเป็นอย่างไร ธุรกรรมที่ใช้เวลานานคือปัญหาที่แท้จริง เพราะมันจะทำให้ผู้เขียนข้อมูลรายอื่นทั้งหมดต้องรอต่อคิว
การสืบค้นเชิงวิเคราะห์ (Analytical queries) SQLite เป็นระบบจัดเก็บข้อมูลแบบแถวที่สร้างขึ้นเพื่อรองรับธุรกรรม แดชบอร์ดที่ต้องสแกนข้อมูลหนึ่งร้อยล้านแถวเป็นงานที่แตกต่างกันซึ่งต้องใช้เครื่องมือที่แตกต่างกัน และ การเปรียบเทียบ DuckDB กับ SQLite สำหรับงานเซิร์ฟเวอร์ ได้ครอบคลุมถึงขอบเขตที่ควรเปลี่ยนไปใช้เครื่องมืออื่น
VACUUM ภายใต้การทำสำเนา (Replication) การทำ VACUUM แบบเต็มรูปแบบจะเขียนไฟล์ฐานข้อมูลใหม่ทั้งหมด ซึ่งหมายความว่า Litestream จะต้องอัปโหลดข้อมูลทั้งหมดใหม่อีกครั้ง และเอกสารของ Litestream แนะนำไม่ให้รันคำสั่งนี้ในขณะที่การทำสำเนากำลังทำงานอยู่ ให้หยุดตัวทำสำเนา (replicator) ทำการ vacuum แล้วจึงเริ่มการทำงานใหม่ และเตรียมรับมือกับการสร้างสแนปชอตใหม่ทั้งหมด
ตัวทำสำเนาสองตัวบนฐานข้อมูลเดียว ห้ามรันกระบวนการ Litestream สองกระบวนการกับฐานข้อมูลเดียวกันหรือปลายทางการสำเนาเดียวกันโดยเด็ดขาด เอกสารระบุไว้อย่างชัดเจนว่าการป้องกันปัญหานี้เป็นความรับผิดชอบของคุณ และผลลัพธ์ที่เกิดขึ้นคือสำเนาที่คุณไม่สามารถกู้คืนได้
สิ่งที่ Litestream ไม่ครอบคลุม
Litestream ทำหน้าที่ปกป้องเฉพาะไฟล์ฐานข้อมูลเท่านั้น ส่วนไฟล์ที่อัปโหลด การตั้งค่าแอปพลิเคชัน ใบรับรอง TLS (transport layer security) และไฟล์ unit files นั้น คุณยังคงต้องจัดการด้วยตนเอง คุณควรใช้งานร่วมกับ การสำรองข้อมูลนอกเครื่องแบบเข้ารหัสด้วย restic ตามตารางเวลาเพื่อให้ครอบคลุมทั้งสองส่วน หากคุณใช้เครื่องใหม่ สิบนาทีแรกบน VPS ใหม่ จะครอบคลุมการสร้างบัญชีผู้ใช้และการตั้งค่าไฟร์วอลล์ ซึ่งคู่มือนี้ถือว่าคุณได้ดำเนินการเสร็จสิ้นแล้ว
FAQ
SQLite เพียงพอสำหรับการใช้งานในระดับโปรดักชันหรือไม่?
สำหรับแอปพลิเคชันเดียวบนเซิร์ฟเวอร์เดียว คำตอบคือใช่ หากคุณเปิดใช้งานโหมด WAL, ตั้งค่า busy timeout และสำรองข้อมูลอย่างต่อเนื่อง ข้อจำกัดที่สำคัญคือโครงสร้างของระบบ: รองรับผู้เขียนได้ครั้งละหนึ่งรายและต้องอยู่บนโฮสต์เครื่องเดียวกัน แอปพลิเคชันที่อยู่ในข้อจำกัดเหล่านี้จะได้ฐานข้อมูลที่ไม่ต้องผ่านเครือข่ายและไม่ต้องมีกระบวนการแยกต่างหากเพื่อคอยตรวจสอบ หากแอปพลิเคชันของคุณไม่รองรับข้อจำกัดเหล่านี้ คุณจำเป็นต้องใช้ฐานข้อมูลแบบไคลเอนต์-เซิร์ฟเวอร์ และการปรับแต่งใดๆ ก็ไม่สามารถเปลี่ยนข้อจำกัดนี้ได้
ทำไมฉันยังคงได้รับ database is locked หลังจากตั้งค่า busy_timeout แล้ว?
เพราะ SQLite จะข้ามตัวจัดการ busy handler เมื่อการรออาจทำให้เกิดสภาวะ deadlock ธุรกรรมที่เริ่มต้นด้วย BEGIN เปล่าๆ จะถูกเลื่อนการทำงานออกไป: การเปิด SELECT จะทำให้ธุรกรรมอยู่ในสถานะอ่าน และการเขียนในภายหลังจะต้องทำการอัปเกรด หากมีการเชื่อมต่ออื่นเขียนข้อมูลเข้ามาในช่วงเวลานั้น SQLite จะส่งคืน SQLITE_BUSY ทันทีแทนที่จะเรียกใช้ busy handler ของคุณ เนื่องจากสแนปชอตการอ่านของคุณล้าสมัยไปแล้ว ให้เริ่มต้นธุรกรรมใดๆ ที่จะมีการเขียนด้วย BEGIN IMMEDIATE เพื่อให้มีการจองล็อกการเขียนไว้ตั้งแต่ต้นและทำให้ค่า timeout มีผลใช้งาน
ฉันสามารถเก็บฐานข้อมูล SQLite ไว้บนที่จัดเก็บข้อมูลเครือข่ายได้หรือไม่?
ไม่ได้ หากเป็นระบบไฟล์เครือข่ายเช่น NFS หรือ SMB โหมด WAL จำเป็นต้องให้ทุกกระบวนการแชร์หน่วยความจำผ่านไฟล์ -shm และเอกสารของ SQLite ระบุว่าทุกกระบวนการที่ใช้ฐานข้อมูลต้องอยู่บนคอมพิวเตอร์โฮสต์เครื่องเดียวกัน อุปกรณ์บล็อกเครือข่ายที่เชื่อมต่อโดยผู้ให้บริการของคุณเป็นคนละกรณีกัน: Linux จะมองเห็นเป็นดิสก์ปกติที่มีระบบไฟล์ปกติอยู่บนนั้น และ SQLite สามารถทำงานบนนั้นได้
ฉันยังจำเป็นต้องใช้ Litestream หากฉันสำรองข้อมูลแบบรายวันอยู่แล้วหรือไม่?
ขึ้นอยู่กับว่าคุณยอมรับการสูญเสียข้อมูลได้มากน้อยเพียงใด งานสำรองข้อมูลรายวันหมายความว่าคุณอาจสูญเสียข้อมูลที่เขียนไปนานถึง 24 ชั่วโมง Litestream จะทำการซิงค์ข้อมูลประมาณวินาทีละครั้ง ดังนั้นหากระบบขัดข้อง คุณจะสูญเสียข้อมูลเพียงประมาณหนึ่งวินาทีล่าสุดเท่านั้น นอกจากนี้ยังปลอดภัยกว่าการคัดลอกไฟล์ฐานข้อมูลด้วย cp ซึ่งอาจคัดลอกฐานข้อมูลในขณะที่กำลังเขียนข้อมูลอยู่ Litestream ครอบคลุมเฉพาะฐานข้อมูลเท่านั้น ดังนั้นควรมีการสำรองข้อมูลไฟล์ทั่วไปควบคู่กันไปด้วย