SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-10-23

วิธีใช้งาน SQLite บน VPS สำหรับแอปพลิเคชันขนาดเล็ก

เรียนรู้วิธีตั้งค่า SQLite ให้พร้อมใช้งานบน VPS อย่างปลอดภัยด้วยโหมด WAL และ busy_timeout พร้อมเทคนิคสำรองข้อมูลด้วย Litestream และข้อจำกัดที่ควรทราบก่อนเริ่มใช้งานจริง

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, July 30, 2026.

เมื่อ SQLite เป็นฐานข้อมูล production ที่เหมาะสมบน VPS

การใช้ SQLite ใน production บน VPS เป็นตัวเลือกที่เหมาะสมสำหรับแอปพลิเคชันขนาดเล็กส่วนใหญ่ เหตุผลนั้นตรงไปตรงมา: กระบวนการเดียวบนเครื่องเดียวที่เขียนข้อมูลลงไฟล์เดียว ไม่จำเป็นต้องใช้ database server จึงไม่มี daemon ที่ต้องดูแล ไม่มีพอร์ตที่ต้องกำหนด firewall ไม่มี password ที่ต้องหมุนเวียน และไม่มีเครื่องที่สองที่ต้องทำให้ทำงานอยู่เสมอ การ query เป็นการเรียกฟังก์ชันแทนการรับส่งข้อมูลผ่าน network ดังนั้นหน้าเว็บที่รัน 40 query จึงมีต้นทุนเป็นการเรียกฟังก์ชัน 40 ครั้ง

ข้อจำกัดมีขอบเขตชัดเจนและมีอยู่จริง SQLite อนุญาตให้มี writer ได้ครั้งละ 1 รายการสำหรับไฟล์ฐานข้อมูลทั้งหมด และไม่สามารถแชร์ไฟล์ระหว่าง 2 เครื่องได้ ข้อจำกัดทั้ง 2 ข้อนี้ไม่มีปัญหาสำหรับ VPS 1 เครื่องที่รันแอปพลิเคชัน 1 รายการ แต่จะกลายเป็นข้อจำกัดร้ายแรงทันทีเมื่อระบบมีขนาดเกินรูปแบบดังกล่าว คู่มือนี้ครอบคลุมการตั้งค่าที่ทำให้ SQLite ปลอดภัยบนเซิร์ฟเวอร์ การสำรองข้อมูลอย่างต่อเนื่องด้วย Litestream และจุดที่ควรหยุดใช้ SQLite

ให้ติดตั้ง command line tool ก่อน คำสั่งทั้งหมดด้านล่างทดสอบบน Ubuntu 24.04

sudo apt update
sudo apt install -y sqlite3
sqlite3 --version

คำสั่งนี้จะแสดง version ที่ขึ้นต้นด้วย 3. ตามด้วย build date และ source hash Ubuntu 24.04 มาพร้อม SQLite 3.45.1 ณ เดือน October 2026 แอปพลิเคชันของคุณอาจไม่ได้ใช้ binary นี้ เนื่องจาก language runtime ส่วนใหญ่ bundle SQLite library ของตนเอง ซึ่งมักเป็น version ที่ใหม่กว่า ดังนั้นให้ตรวจสอบ version ที่ database driver รายงานก่อนใช้งาน feature ใหม่ล่าสุด

เหตุใดโหมด 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

การคัดลอกข้อมูลทุกคืนอาจทำให้ข้อมูลการเขียนหายไปได้ถึง 1 วัน และการเรียกใช้ cp กับฐานข้อมูล SQLite ที่กำลังทำงานอาจสร้างสำเนาที่เปิดไม่ได้ วิธีที่ปลอดภัยมี 2 วิธี sqlite3 app.db ".backup /path/to/backup.db" ใช้ online backup interface ของ SQLite และทำงานกับฐานข้อมูลที่กำลังถูกใช้งานได้ Litestream ทำได้มากกว่านั้น โดยเฝ้าดู WAL และส่งการเปลี่ยนแปลงไปยัง object storage อย่างต่อเนื่อง ทำให้ความเสียหายของข้อมูลในกรณีเลวร้ายที่สุดลดจาก 1 วันเหลือประมาณ 1 วินาที

Litestream เป็น Go binary เดียวที่ทำงานอยู่ข้าง application โดยไม่อยู่ระหว่าง app กับฐานข้อมูล application ยังคงเขียนข้อมูลลง 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 version

v0.5.14 ยังคงเป็น release ที่หน้า official Linux install ระบุไว้ ณ เดือน October 2026 หลังจากนั้นมี release ใหม่ตามมา ได้แก่ v0.5.15 เมื่อ 21 July 2026 และ v0.5.17 ซึ่งเป็น tag ล่าสุด เมื่อ 31 August 2026 ให้เปลี่ยน version ในทั้ง 2 บรรทัดให้ตรงกับ tag ปัจจุบันในหน้า releases และใช้ package สำหรับ arm64 ที่ตรงกันแทน หาก VPS ของคุณเป็น arm64

ไฟล์ configuration อยู่ที่ /etc/litestream.yml ให้เริ่มด้วย local file replica เพราะจะช่วยยืนยันการทำงานครบทั้งวงจรโดยไม่ต้องใช้ cloud credentials

dbs:
  - path: /home/appuser/app/app.db
    replica:
      type: file
      path: /var/backups/litestream/app

โปรดสังเกตว่า field คือ replica ซึ่งเป็นเอกพจน์ Litestream 0.5 เปลี่ยน array replicas จาก series 0.3 เป็น replica block เดียว และ config ที่มี 2 entries จะ fail ตอน startup คู่มือจากบุคคลภายนอกหลายฉบับยังแสดง array แบบเก่า ดังนั้นให้คัดลอกรูปแบบด้านบน แทนการใช้ตัวอย่างแรกที่ค้นหาเจอ series 0.5 ยังเปลี่ยนชื่อ subcommand จาก litestream wal เป็น litestream ltx เนื่องจาก on-disk backup format เปลี่ยนแปลง

ตรวจสอบว่า config parse ได้ก่อนเปิดใช้งานส่วนใด

sudo litestream databases -config /etc/litestream.yml

จากนั้นทดสอบการทำงานไปกลับด้วยตนเอง รูปแบบนี้ไม่ใช้ config file และ replicate ฐานข้อมูล 1 รายการไปยัง 1 path

mkdir -p /tmp/replica
litestream replicate ~/app/app.db file:///tmp/replica/app

คำสั่งนี้จะทำงานใน foreground และทำงานต่อเนื่อง ใน shell อีกหน้าต่างหนึ่ง ให้เขียน row แล้ว restore 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;"

จำนวนที่แสดงจะรวม row ใหม่ด้วย หากไม่รวม แสดงว่าการเปลี่ยนแปลงยัง sync ไม่เสร็จ Litestream จะ push ตาม sync-interval ซึ่งมีค่าเริ่มต้นเป็น 1 วินาที ดังนั้นให้รอแล้ว restore อีกครั้ง 1 วินาทีนั้นคือ recovery point ของคุณด้วย หากเกิด crash การเขียนข้อมูลจาก sync interval ล่าสุดจะสูญหายได้ไม่เกินช่วงดังกล่าว และไม่มี configuration ใดทำให้ค่านี้เป็น 0 ได้

สำหรับ storage จริง ให้เปลี่ยน replica block เป็น S3 URL วิธีนี้ใช้ได้กับ Amazon S3 และ object storage ที่เข้ากันได้กับ S3 จาก provider รายอื่น

dbs:
  - path: /home/appuser/app/app.db
    replica:
      url: s3://your-bucket-name/app
      region: us-east-1

snapshot:
  interval: 24h
  retention: 24h

อย่าเก็บ credentials ไว้ในไฟล์นั้น Litestream จะอ่าน LITESTREAM_ACCESS_KEY_ID และ LITESTREAM_SECRET_ACCESS_KEY จาก environment ดังนั้นให้เก็บค่าเหล่านี้ไว้ใน systemd drop-in ที่ root เป็นเจ้าของและมี mode เป็น 600

ค่า snapshot ด้านบนเป็นค่าเริ่มต้น และค่า retention เริ่มต้นมักทำให้เข้าใจผิด Retention คือระยะเวลาที่ Litestream เก็บ snapshots และไฟล์ที่เกี่ยวข้องไว้ จึงเป็นตัวกำหนดด้วยว่าสามารถ restore ย้อนกลับไปได้ไกลเพียงใด ค่า 24 ชั่วโมงหมายความว่า หากพบ migration ที่ผิดพลาดในเช้าวันพุธ จะไม่สามารถกู้คืนสถานะของวันจันทร์ได้อีกจากข้อมูลที่เก็บไว้ ใน Litestream 0.5 retention เป็นการตั้งค่าแบบ global ใน root snapshot block ไม่ใช่การตั้งค่าราย replica หากต้องการเก็บไว้ 1 สัปดาห์ ให้เปลี่ยน block นั้นเป็น retention: 168h และยอมรับค่าใช้จ่าย storage ที่เพิ่มขึ้น บรรทัด retention: ภายใน replica block เป็น syntax ของ series 0.3 Litestream 0.5 จะเพิกเฉยต่อบรรทัดนี้โดยไม่แสดง error และยังคงใช้ค่าเริ่มต้น 24 ชั่วโมง

พิสูจน์การกู้คืนข้อมูลก่อนที่คุณจะจำเป็นต้องใช้มัน

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 ติดตั้ง unit litestream ที่อ่านไฟล์ /etc/litestream.yml

sudo systemctl enable litestream
sudo systemctl start litestream
sudo journalctl -u litestream -f

ผลลัพธ์ปกติจะแสดงชื่อฐานข้อมูลแต่ละรายการจาก config แล้วจะไม่มีข้อความเพิ่มเติม นอกจากบรรทัดการ sync ตามช่วงเวลา หากเกิดข้อผิดพลาด no such file or directory กับ path ของฐานข้อมูล แสดงว่า path ใน config ไม่ถูกต้อง หรือ process ไม่สามารถอ่าน path นั้นได้ โดยค่าเริ่มต้น unit จะทำงานในฐานะ root ซึ่งมีสิทธิ์มากเกินกว่าที่งานนี้ต้องใช้ Litestream ต้องอ่านและเขียนได้ทั้งฐานข้อมูลและไดเรกทอรีที่เก็บฐานข้อมูล เนื่องจากทำงานกับไฟล์ -wal และ -shm ที่อยู่ถัดจากฐานข้อมูล ดังนั้นให้กำหนด user account เดียวกับที่แอปพลิเคชันของคุณใช้งานอยู่ ซึ่งคือ appuser ในตัวอย่างนี้

# /etc/systemd/system/litestream.service.d/override.conf
[Service]
User=appuser
Group=appuser

ใช้ sudo systemctl daemon-reload และ sudo systemctl restart litestream เพื่อใช้งานการตั้งค่านี้ การตั้งค่า service account เฉพาะที่มีสิทธิ์เท่าที่จำเป็น ใช้เวลาเพียงไม่กี่นาที และเป็นความแตกต่างระหว่าง backup agent กับ process ที่ทำงานเป็น root อีกตัวบนเซิร์ฟเวอร์

รายละเอียดเกี่ยวกับลำดับการทำงานมีความสำคัญ หากต้องสร้างเครื่องใหม่ตั้งแต่ต้น คุณต้องกู้คืนฐานข้อมูลก่อนเริ่มแอปพลิเคชัน litestream restore รองรับ -if-db-not-exists ซึ่งจะ exit ด้วยค่า 0 เมื่อไฟล์มีอยู่แล้ว จึงสามารถเรียกใช้ได้ทุกครั้งที่บูต ให้ใส่ไว้ในบรรทัด ExecStartPre ของ unit ของแอปพลิเคชัน แล้ว VPS เครื่องใหม่จะดึงฐานข้อมูลลงมา ขณะที่เครื่องเดิมจะไม่ดำเนินการใด ๆ litestream replicate มี flag -restore-if-db-not-exists ที่สอดคล้องกัน หากต้องการเก็บการตั้งค่านี้ไว้ในที่เดียว

กรณีที่ SQLite ใช้งานไม่ได้บน VPS

Network filesystem. นี่คือข้อจำกัดที่ไม่สามารถแก้ด้วยการตั้งค่าได้ WAL mode กำหนดให้ทุก process ที่ใช้ฐานข้อมูลต้องแชร์หน่วยความจำพื้นที่ขนาดเล็กร่วมกัน ซึ่งเป็นหน้าที่ของไฟล์ -shm เอกสาร SQLite ระบุข้อกำหนดนี้ไว้อย่างชัดเจนโดยไม่มีข้อยกเว้น:

ทุก process ที่ใช้ฐานข้อมูลต้องอยู่บน host เดียวกัน และ WAL ใช้งานผ่าน network filesystem ไม่ได้

ดังนั้น ฐานข้อมูลบน NFS ที่ mount ไว้ (network file system) หรือ share แบบ SMB อาจเกิดความเสียหายได้ และไม่มี pragma ใดป้องกันปัญหานี้ได้ ความแตกต่างที่มักถูกมองข้ามคือ network block device ซึ่งเป็น storage เพิ่มเติมที่ผู้ให้บริการ VPS ส่วนใหญ่เชื่อมต่อให้ จะปรากฏใน Linux เป็นดิสก์ทั่วไปที่มี filesystem ทั่วไป และใช้งานได้ตามปกติ ส่วน file share ที่ mount ไว้ไม่ใช่กรณีเดียวกัน

Application server อีกเครื่องหนึ่ง. ไม่มี setting ใดทำให้กรณีนี้ใช้งานได้ เมื่อจำเป็นต้องให้ 2 เครื่องให้บริการข้อมูลชุดเดียวกัน คุณต้องใช้ฐานข้อมูลที่สื่อสารผ่าน network ควรตัดสินใจเปลี่ยนแนวทางนี้ตั้งแต่ยังมีเวลาเพียงพอสำหรับวางแผน

Workload ที่เขียนข้อมูลจำนวนมาก. การที่มี writer ได้ครั้งละ 1 ตัวเป็นคุณสมบัติของรูปแบบไฟล์ ไม่ใช่ค่าที่ปรับได้ การเขียนข้อมูลระยะสั้นมีต้นทุนต่ำ เพราะแต่ละ commit เป็นการเขียนต่อท้าย WAL ดังนั้น throughput จึงขึ้นกับ latency ของการเขียนข้อมูลขนาดเล็กบนดิสก์มากกว่า CPU ดู NVMe เทียบกับ SATA SSD storage บน VPS เพื่อดูความแตกต่างดังกล่าว ปัญหาที่แท้จริงคือ transaction ที่ใช้เวลานาน เพราะจะทำให้ writer อื่นทั้งหมดต้องรอคิวอยู่ด้านหลัง

Query เชิงวิเคราะห์. SQLite เป็น row store ที่ออกแบบมาสำหรับ transaction ส่วน dashboard ที่ต้องสแกนข้อมูล 100 ล้านแถวเป็นงานคนละประเภท และควรใช้เครื่องมือคนละแบบ ดู DuckDB เทียบกับ SQLite สำหรับงานบน server เพื่อดูว่าขอบเขตดังกล่าวอยู่ตรงไหน

VACUUM ภายใต้การทำ replication. VACUUM แบบเต็มจะเขียนไฟล์ฐานข้อมูลใหม่ทั้งหมด ทำให้การติดตามระดับ page ที่ Litestream ใช้อ้างอิงใช้ไม่ได้ และเอกสาร Litestream แนะนำไม่ให้เรียกใช้ขณะ replication ทำงานอยู่ โดยแนะนำให้ใช้ VACUUM INTO ซึ่งจะเขียนสำเนาที่ compact แล้วลงในไฟล์แยก และไม่แก้ไขไฟล์ต้นฉบับ หากจำเป็นต้องทำ vacuum ในไฟล์เดิม ขั้นตอนตามเอกสารจะยาวกว่าการ restart ได้แก่ หยุด replicator, ทำ vacuum, ลบ directory สำหรับติดตามข้อมูลที่ซ่อนอยู่ของ Litestream ซึ่งอยู่ถัดจากฐานข้อมูล และลบข้อมูล replica เดิมบน remote จากนั้นจึงเริ่มการทำงานอีกครั้งเพื่อให้เริ่มจาก full snapshot ใหม่ ขั้นตอนสุดท้ายนี้จะลบประวัติการกู้คืนที่มีอยู่เดิม

Replicator 2 ตัวบนฐานข้อมูลเดียวกัน. ห้ามเรียกใช้ process ของ Litestream 2 ตัวกับฐานข้อมูลเดียวกันหรือกับ replica destination เดียวกัน เอกสารระบุอย่างชัดเจนว่าผู้ดูแลต้องป้องกันไม่ให้เกิดกรณีนี้ และผลลัพธ์คือ replica ที่ไม่สามารถกู้คืนได้

สิ่งที่ 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 ครอบคลุมเฉพาะฐานข้อมูลเท่านั้น ดังนั้นควรมีการสำรองข้อมูลไฟล์ทั่วไปควบคู่กันไปด้วย

บทความนี้มีประโยชน์หรือไม่

#sqlite#wal#litestream#backups#production