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

เปรียบเทียบ DuckDB กับ SQLite เลือกใช้อย่างไรบนเซิร์ฟเวอร์

ไขข้อสงสัยการเลือกใช้ DuckDB และ SQLite บน VPS เดียวกัน วิเคราะห์ความแตกต่างระหว่าง OLTP และ OLAP พร้อมตัวอย่างการใช้งานจริงเพื่อจัดการข้อมูล Parquet และ CSV อย่างมีประสิทธิภาพ

การเปรียบเทียบ DuckDB กับ SQLite บนเซิร์ฟเวอร์: คำตอบในประโยคเดียว

SQLite เป็นเอนจินแบบ OLTP (online transaction processing) ซึ่งจัดเก็บข้อมูลในรูปแบบแถวและถูกสร้างมาเพื่ออ่านหรือเขียนข้อมูลทีละจำนวนน้อยได้อย่างรวดเร็วและปลอดภัย ส่วน DuckDB เป็นเอนจินแบบ OLAP (online analytical processing) ซึ่งจัดเก็บข้อมูลในรูปแบบคอลัมน์และถูกสร้างมาเพื่อสแกนข้อมูลหลายล้านแถวเพื่อสรุปผล ทั้งสองเป็นไลบรารีแบบฝังตัว (embedded library) ที่เปิดไฟล์ข้อมูลโดยตรงและไม่ต้องรันโปรเซสเซิร์ฟเวอร์แยกต่างหากให้คุณต้องคอยดูแล

ดังนั้นคำตอบที่ตรงไปตรงมาสำหรับคำถามที่ว่า "ควรเลือกตัวไหน" มักจะเป็น "ใช้ทั้งคู่บน VPS เดียวกัน" โดยให้แอปพลิเคชันของคุณเก็บสถานะการทำงานจริงไว้ใน SQLite และใช้ DuckDB สำหรับการอ่านไฟล์ Parquet หรือ CSV เพื่อทำรายงาน ทั้งสองเครื่องมือนี้ไม่ได้แข่งขันกันเพราะทำหน้าที่ต่างกันอย่างชัดเจน

เหตุผลที่การจัดเก็บแบบแถวและแบบคอลัมน์ส่งผลต่อคำตอบ

SQLite เขียนข้อมูลหนึ่งแถวให้เป็นส่วนที่ต่อเนื่องกันภายในหน้าข้อมูล (page) การดึงข้อมูลคำสั่งซื้อหนึ่งรายการโดยใช้ primary key จะเข้าถึงหน้าดัชนีหนึ่งหน้าและหน้าข้อมูลหนึ่งหน้า ซึ่งเท่ากับการอ่าน 2 ครั้ง นี่คือสิ่งที่แอปพลิเคชันทำซ้ำหลายพันครั้งต่อวินาที เช่น การอ่านข้อมูลผู้ใช้ การอัปเดตเซสชัน หรือการเพิ่มคำสั่งซื้อ

DuckDB เขียนข้อมูลแต่ละคอลัมน์แยกกันและทำการบีบอัดข้อมูล การหาผลรวม amount_cents จากข้อมูล 5 ล้านแถวจะอ่านเฉพาะคอลัมน์ amount_cents เท่านั้น โดยจะข้ามไบต์อื่นทั้งหมดในไฟล์และประมวลผลผลรวมผ่านโค้ดแบบ vectorised บนชุดข้อมูลแต่ละกลุ่ม คอลัมน์อื่นจะไม่ถูกอ่านจากดิสก์เลย ซึ่งนี่คือที่มาของความเร็ว

หากนำเอนจินแต่ละตัวไปทำงานในรูปแบบของอีกตัวหนึ่ง SQLite ที่ต้องหาผลรวมของคอลัมน์จะต้องไล่ดูทุกแถวและดึงข้อมูลทั้งแถวออกมาจากหน้าข้อมูลเพื่อเข้าถึงฟิลด์เดียว ทำให้ต้องอ่านดิสก์มากกว่าที่จำเป็นมาก ส่วน DuckDB ที่ต้องเพิ่มคำสั่งซื้อหนึ่งรายการจะต้องเข้าถึงพื้นที่จัดเก็บของทุกคอลัมน์เพื่อใส่ค่าเพียงค่าเดียว และต้องใช้ write lock กับไฟล์ฐานข้อมูลทั้งหมดเพื่อดำเนินการดังกล่าว ทั้งสองเอนจินไม่ได้ทำงานผิดพลาด แต่ละตัวกำลังตอบโจทย์ในรูปแบบที่ไม่ได้ถูกออกแบบมาให้รองรับ

จุดแข็งของ SQLite: สถานะของแอปพลิเคชันแบบ transactional

เลือกใช้ SQLite เมื่อการเขียนข้อมูลมีขนาดเล็ก เกิดขึ้นบ่อยครั้ง และต้องไม่สูญหาย เช่น ข้อมูล session, รายการสั่งซื้อ, แถวในคิว, การตั้งค่า หรือสิ่งใดก็ตามที่ request ของเว็บสร้างขึ้น

sudo apt update && sudo apt install -y sqlite3
sudo install -d -o "$USER" -g "$USER" /srv/app

สร้างตารางและเปิดใช้งาน write-ahead logging ในขั้นตอนเดียวกัน

sqlite3 /srv/app/app.db <<'SQL'
PRAGMA journal_mode = WAL;
CREATE TABLE IF NOT EXISTS orders (
  id           INTEGER PRIMARY KEY,
  customer     TEXT    NOT NULL,
  placed_at    TEXT    NOT NULL,
  amount_cents INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS orders_customer ON orders (customer);
INSERT INTO orders (customer, placed_at, amount_cents)
  VALUES ('ana', '2026-07-30T09:14:00Z', 4200);
SQL

บรรทัดแรกของผลลัพธ์คือ wal ซึ่งเป็น PRAGMA ที่รายงานโหมดที่เปลี่ยนไป และถือเป็นการตั้งค่าที่มีประโยชน์ที่สุดบนเซิร์ฟเวอร์ ในโหมด rollback journal เริ่มต้นนั้น ผู้เขียนจะบล็อกผู้อ่านทุกราย แต่ในโหมด WAL ผู้อ่านจะยังคงอ่านสถานะที่ commit ล่าสุดได้ในขณะที่ผู้เขียนหนึ่งรายกำลังต่อท้ายข้อมูล ดังนั้นรายงานที่ทำงานช้าจึงไม่ทำให้ request ของเว็บที่ตามหลังมาหยุดชะงักอีกต่อไป

ตรวจสอบว่าแถวข้อมูลถูกบันทึกกลับมาแล้ว:

sqlite3 /srv/app/app.db "SELECT * FROM orders WHERE customer = 'ana';"

คุณจะได้รับ 1|ana|2026-07-30T09:14:00Z|4200 และมีไฟล์ปรากฏขึ้นข้างฐานข้อมูลอีกสองไฟล์คือ app.db-wal และ app.db-shm ซึ่งทั้งสองไฟล์เป็นส่วนหนึ่งของฐานข้อมูล การคัดลอกเฉพาะ app.db ในขณะที่แอปพลิเคชันกำลังทำงานจะทำให้คุณได้ไฟล์สำรองที่ไม่สมบูรณ์ ซึ่งจะมีการกล่าวถึงรายละเอียดเพิ่มเติมในส่วนถัดไป

SQLite ยังคงอนุญาตให้มีผู้เขียนได้ครั้งละหนึ่งรายเท่านั้น ข้อจำกัดนี้เป็นรูปแบบการล็อกไม่ใช่คิว ดังนั้นผู้เขียนรายที่สองที่รอเป็นเวลานานเกินไปจะพบกับข้อผิดพลาด database is locked แทนที่จะถูกบล็อกไปตลอดกาล ให้เพิ่มระยะเวลารอด้วย PRAGMA busy_timeout = 5000; ในทุกการเชื่อมต่อที่แอปพลิเคชันของคุณเปิดขึ้น การตั้งค่าระยะเวลารอ 5 วินาทีจะช่วยลดข้อผิดพลาดเหล่านี้ได้ส่วนใหญ่สำหรับภาระงานเว็บทั่วไป

จุดแข็งของ DuckDB: การวิเคราะห์ข้อมูลบนไฟล์ที่คุณมีอยู่แล้ว

เลือกใช้ DuckDB เมื่อคำถามขึ้นต้นด้วย "เท่าไหร่" (how many), "จำนวนเท่าใด" (how much) หรือ "สิบอันดับแรกคืออะไร" (which top ten) และข้อมูลนำเข้าเป็นไฟล์ CSV หรือ Parquet จำนวนมาก ให้ติดตั้ง command line client เวอร์ชัน 2.0.0 (ซึ่งเป็นรุ่น LTS) ณ วันที่ 27 ตุลาคม 2026 ดังนี้:

curl https://install.duckdb.org | sh

สคริปต์จะติดตั้งรุ่นเสถียรล่าสุดให้โดยอัตโนมัติ เว้นแต่คุณจะระบุเวอร์ชันไว้ เช่น curl https://install.duckdb.org | DUCKDB_VERSION=1.5.6 sh เพื่อคงไว้ที่รุ่นก่อนหน้า สคริปต์จะติดตั้ง binary ไว้ที่ ~/.duckdb/cli/latest/duckdb และแสดงบรรทัดคำสั่งสำหรับเพิ่มลงใน PATH ของคุณ จากนั้นให้ยืนยันว่าโปรแกรมทำงานได้ปกติ:

~/.duckdb/cli/latest/duckdb :memory: "SELECT version();"

สร้างไฟล์ตัวอย่างที่สมจริงเพื่อทดสอบการคิวรี คำสั่งนี้จะเขียนข้อมูลคำสั่งซื้อจำนวน 5 ล้านแถวลงในไฟล์ Parquet โดยบีบอัดด้วย zstd:

mkdir -p /srv/data
duckdb :memory: "
COPY (
  SELECT i                                                     AS id,
         'cust_' || (i % 5000)                                 AS customer,
         TIMESTAMP '2026-01-01 00:00:00' + INTERVAL (i) MINUTE AS placed_at,
         (i * 37) % 20000                                      AS amount_cents
  FROM range(5000000) t(i)
) TO '/srv/data/orders.parquet' (FORMAT parquet, COMPRESSION zstd);"

ตอนนี้ให้ลองถามคำถามเชิงวิเคราะห์ เปิด shell ขึ้นมา เปิดตัวจับเวลา และคิวรีไฟล์โดยตรงโดยไม่ต้องผ่านขั้นตอนการนำเข้าข้อมูล (import):

.timer on
SELECT customer,
       count(*)                AS orders,
       sum(amount_cents)/100.0 AS revenue
FROM '/srv/data/orders.parquet'
GROUP BY customer
ORDER BY revenue DESC
LIMIT 5;

ให้อ่านตัวเลขผลลัพธ์จาก .timer ของคุณเอง แทนที่จะเชื่อตัวเลขที่เผยแพร่ทั่วไป เพราะผลลัพธ์ขึ้นอยู่กับความเร็วของดิสก์และจำนวนคอร์ที่คุณมี สิ่งที่สำคัญคือรูปแบบการทำงาน กระบวนการนี้ไม่มี CREATE TABLE, ไม่มี INSERT และไม่มีขั้นตอนการโหลดข้อมูล: DuckDB อ่านส่วนท้าย (footer) ของไฟล์ Parquet เพื่อระบุว่าคิวรีต้องการ column chunks ส่วนใดบ้าง แล้วอ่านเฉพาะส่วนนั้นเท่านั้น การทำงานกับไฟล์ทั้งไดเรกทอรีก็ใช้วิธีเดียวกันผ่าน glob เช่น FROM '/srv/data/orders-*.parquet' ซึ่งเป็นวิธีที่ทำให้การส่งออกข้อมูลรายวันตลอดทั้งเดือนกลายเป็นคิวรีเดียวได้

ความเร็วของดิสก์คือปัจจัยพื้นฐานของกระบวนการทั้งหมดนี้ และการสแกนคอลัมน์เป็นการอ่านแบบลำดับ (sequential read) ดังนั้นช่องว่างระหว่าง NVMe กับที่เก็บข้อมูล SATA รุ่นเก่าบน VPS จะปรากฏให้เห็นชัดเจนกว่าการอ่านแบบสุ่มขนาดเล็กของ SQLite

การอ่านฐานข้อมูล SQLite จาก DuckDB

ทั้งสองเอนจินสามารถทำงานร่วมกันได้ผ่านส่วนขยาย sqlite ของ DuckDB ให้ทำการ attach ฐานข้อมูลของแอปพลิเคชันในโหมดอ่านอย่างเดียว เพื่อให้มั่นใจว่าการคิวรีเพื่อวิเคราะห์ข้อมูลจะไม่สามารถเขียนทับสถานะปัจจุบันได้:

INSTALL sqlite;
LOAD sqlite;
ATTACH '/srv/app/app.db' AS app (TYPE sqlite, READ_ONLY);
SELECT customer, count(*) AS orders
FROM app.orders
GROUP BY customer
ORDER BY orders DESC;

คำสั่งนี้จะอ่านแถวข้อมูลจากไฟล์ SQLite ในขณะที่คิวรีทำงานโดยไม่มีการคัดลอกข้อมูล วิธีนี้สะดวกแต่ไม่ได้มีความเร็วสูง เนื่องจากข้อมูลบนดิสก์ยังคงจัดเก็บในรูปแบบ row storage และ DuckDB จำเป็นต้องไล่อ่านข้อมูลทีละแถว ควรใช้สำหรับการส่งออกข้อมูล (export) เท่านั้น ไม่ควรใช้กับแดชบอร์ดที่มีการโหลดข้อมูลใหม่ทุก 30 วินาที:

COPY (SELECT * FROM app.orders)
  TO '/srv/data/orders-2026-07.parquet' (FORMAT parquet, COMPRESSION zstd);

คำสั่งเดียวนี้คือรูปแบบการทำงานทั้งหมด SQLite จะเป็นผู้ดูแลแถวข้อมูลล่าสุดที่ใช้งานจริง ส่วนการส่งออกข้อมูลตามกำหนดการจะเปลี่ยนข้อมูลในช่วงเวลาที่ปิดรอบแล้วให้เป็นรูปแบบ Parquet โดย DuckDB จะทำหน้าที่ตอบคำถามทุกอย่างที่ครอบคลุมระยะเวลาหลายเดือน ในขณะที่ฐานข้อมูลของแอปพลิเคชันจะมีขนาดเล็ก ซึ่งช่วยให้การเขียนข้อมูลยังคงทำได้อย่างรวดเร็ว

ควรดำเนินการส่งออกข้อมูลตามกำหนดการแทนการทำด้วยตนเอง การใช้ คู่ของ systemd service และ timer เป็นขนาดที่เหมาะสมสำหรับงานนี้ โดยมีหนึ่ง unit สำหรับรัน COPY และหนึ่ง timer สำหรับสั่งให้ทำงานทุกคืน

การรันทั้งสองอย่างบน VPS เดียวกัน

ไม่มีส่วนใดที่จำเป็นต้องใช้ container หรือพอร์ต ทั้งสอง engine เป็นเพียงไลบรารี การติดตั้งจึงเป็นเพียงการติดตั้งแพ็กเกจและระบุ path ของไฟล์ หาก stack ส่วนที่เหลือของคุณรันอยู่บน Docker Compose บน VPS เดียวกัน ให้ mount directory ข้อมูลเข้าไปใน container ที่ต้องการใช้งานแทนการเพิ่ม service ฐานข้อมูล เนื่องจากไม่มี service ใดให้เพิ่ม

มีกฎ 2 ข้อที่จะช่วยให้การจัดการนี้ไม่มีปัญหา

กำหนด directory แยกสำหรับแต่ละ engine: /srv/app สำหรับไฟล์ SQLite ที่แอปพลิเคชันเขียน และ /srv/data สำหรับไฟล์ Parquet ที่ระบบวิเคราะห์อ่าน หากใช้ directory ร่วมกัน งานสำรองข้อมูลที่ทำ snapshot ของไฟล์หนึ่งอาจเกิดการแย่งชิงทรัพยากรกับอีกไฟล์หนึ่งได้

ห้ามกำหนดให้สอง process เข้าถึงไฟล์ฐานข้อมูล DuckDB เดียวกันในโหมดอ่าน-เขียนพร้อมกัน มีเพียง process เดียวเท่านั้นที่สามารถถือครองไฟล์ DuckDB เพื่อเขียนข้อมูลได้ และ process ที่สองจะเปิดไฟล์ไม่ได้เลย การมีผู้อ่านหลายรายสามารถทำได้หากทุกรายตั้งค่า access_mode = 'READ_ONLY' สิ่งนี้มักทำให้ผู้ที่ย้ายมาจาก SQLite ประหลาดใจ เนื่องจากใน SQLite หลาย process สามารถแชร์ไฟล์ร่วมกันได้ตามปกติ หากระบบวิเคราะห์ของคุณอ่านเฉพาะไฟล์ Parquet ปัญหานี้จะไม่เกิดขึ้น ซึ่งเป็นอีกหนึ่งเหตุผลที่ควรเก็บสถานะที่ต้องการความคงทนไว้ใน SQLite

การสำรองข้อมูลมีความแตกต่าง และความแตกต่างนั้นส่งผลกระทบ

ฐานข้อมูล SQLite ที่กำลังทำงานอยู่ประกอบด้วยไฟล์ 3 ไฟล์ และการคัดลอกไฟล์เหล่านั้นด้วย cp ในระหว่างที่มีการเขียนข้อมูลจะทำให้ได้ไฟล์ที่เปิดได้แต่ข้อมูลไม่ถูกต้อง ให้ใช้คำสั่งสำรองข้อมูลของตัวเอนจินเอง ซึ่งจะทำการสร้าง snapshot ที่มีความสอดคล้องกันในขณะที่แอปพลิเคชันยังคงเขียนข้อมูลต่อไปได้:

sqlite3 /srv/app/app.db ".backup '/srv/backup/app-$(date -u +%Y%m%dT%H%M%SZ).db'"
sqlite3 /srv/backup/app-20260730T091400Z.db "PRAGMA integrity_check;"

integrity_check จะแสดงผล ok หากการคัดลอกเสร็จสมบูรณ์ หากเป็นค่าอื่น ให้ทิ้ง snapshot นั้นและเริ่มทำการสำรองข้อมูลใหม่

ไฟล์ Parquet จะไม่มีการเปลี่ยนแปลงหลังจากถูกเขียนลงดิสก์แล้ว จึงไม่จำเป็นต้องจัดการเป็นพิเศษ ให้สำรองข้อมูลทั้งไดเรกทอรีได้เลย ส่งทั้งสองพาธออกจากเซิร์ฟเวอร์ด้วย การสำรองข้อมูล restic จาก VPS ของคุณ แล้วเลเยอร์ข้อมูลทั้งหมดของคุณจะกลายเป็นเพียงสองไดเรกทอรีในงานสำรองข้อมูลเดียว

รูปแบบความล้มเหลวและข้อความแจ้งเตือนที่คุณจะพบ

database is locked จาก SQLite ไม่ว่าจะมาจาก driver ของแอปพลิเคชันหรือจาก shell sqlite3 หมายความว่ามีการเชื่อมต่ออื่นถือ write lock ไว้นานเกินกว่าที่ timeout ของคุณกำหนดไว้ นี่ไม่ใช่ปัญหาไฟล์เสียหาย ให้ตั้งค่า PRAGMA busy_timeout ในทุกการเชื่อมต่อ จากนั้นตรวจสอบหา transaction ที่ทำงานยาวนานเกินไปซึ่งควรจะแบ่งเป็น transaction ย่อยๆ หลายรายการ

unable to open database file หลังจากเปลี่ยนสิทธิ์การเข้าถึง (permission) มักหมายความว่า process สามารถเขียนไฟล์ได้แต่ไม่สามารถเขียนในไดเรกทอรีนั้นได้ SQLite จะสร้างไฟล์ app.db-wal และ app.db-shm ไว้ข้างๆ ฐานข้อมูล ดังนั้นตัวไดเรกทอรีเองต้องสามารถเขียนได้ ไม่ใช่แค่ไฟล์ .db เท่านั้น

IO Error: Could not set lock on file จาก DuckDB หมายความว่ามี process อื่นเปิดฐานข้อมูลนั้นเพื่อเขียนอยู่แล้ว ให้ปิด shell อื่น หรือเปิด shell ของคุณในโหมดอ่านอย่างเดียว (read only)

Out of Memory Error จาก DuckDB บน VPS ขนาดเล็กหมายความว่าคิวรีต้องการหน่วยความจำขณะทำงาน (working memory) มากกว่าที่มีอยู่ โดยปกติ DuckDB จะย้ายผลลัพธ์ชั่วคราวไปไว้บนดิสก์ (spill) รวมถึง session แบบ :memory: ด้วย ดังนั้นการเปิดไฟล์ฐานข้อมูลจึงไม่ใช่ปัจจัยที่ทำให้การ spill ทำงานได้ มันจะเขียนไฟล์ชั่วคราวเหล่านั้นลงในไดเรกทอรี .tmp ที่อยู่ข้างไฟล์ฐานข้อมูล หรือหากเป็น session ในหน่วยความจำ มันจะใช้ไดเรกทอรี .tmp ที่สัมพันธ์กับตำแหน่งที่คุณเริ่มโปรแกรม ดังนั้นควรเริ่มโปรแกรมในที่ที่เขียนไฟล์ได้และมีพื้นที่ดิสก์เหลือ หรือระบุตำแหน่งอื่นด้วย SET temp_directory = '/var/tmp/duckdb'; การตั้งค่าที่สำคัญคือขีดจำกัด: SET memory_limit = '2GB'; ค่าเริ่มต้นคือ 80% ของ RAM ในเครื่อง ดังนั้นบนเครื่องที่รันบริการอื่นอยู่ ขีดจำกัดนี้คือสิ่งที่ป้องกันไม่ให้คิวรีเฉพาะกิจ (ad hoc query) แย่ง RAM ไปจากแอปพลิเคชันของคุณ ฟังก์ชันรวมกลุ่ม (aggregate) บางอย่าง เช่น string_agg() ไม่สามารถ spill ได้ และการรวมกลุ่มแบบเรียงลำดับบางประเภทก็เช่นกัน ดังนั้นคิวรีที่สร้างขึ้นจากฟังก์ชันเหล่านี้อาจทำให้หน่วยความจำเต็มได้

Binder Error: Referenced column "amount" not found เมื่อคิวรีไฟล์ Parquet มักหมายความว่า schema ของไฟล์ไม่ตรงกับที่คุณเข้าใจ ให้รัน DESCRIBE SELECT * FROM '/srv/data/orders.parquet'; เพื่ออ่านชื่อคอลัมน์ที่แท้จริงออกมาดู

วิธีการเลือกใช้งานจริง

ให้พิจารณารูปแบบการเขียนข้อมูล หากมีการเขียนข้อมูลขนาดเล็กจำนวนมากที่ต้องคงอยู่แม้เกิดเหตุการณ์ไฟฟ้าดับ SQLite คือตัวเลือกที่เหมาะสม จากนั้นให้พิจารณารูปแบบการอ่านข้อมูล หากต้องการการสแกนข้อมูลทั้งหมดพร้อมการคำนวณสรุปผลจากประวัติข้อมูลระยะยาว DuckDB คือตัวเลือกที่เหมาะสม ระบบส่วนใหญ่ในความเป็นจริงมักต้องการทั้งสองอย่าง คำตอบที่ถูกต้องคือการแบ่งงานให้แต่ละเอนจินจัดการในส่วนที่ตนเองถนัด แทนที่จะบังคับให้เอนจินใดเอนจินหนึ่งทำงานทั้งหมด

สิ่งที่ควรหลีกเลี่ยงคือการย้ายสถานะของแอปพลิเคชันที่กำลังทำงานอยู่ (live application state) ไปยัง DuckDB เพียงเพราะรายงานทำงานช้า สาเหตุที่รายงานทำงานช้าเกิดจากรูปแบบการจัดเก็บข้อมูล (storage layout) ดังนั้นวิธีแก้ไขที่ถูกต้องคือการส่งออกข้อมูล (export) ไม่ใช่การเขียนเส้นทางการเขียนข้อมูล (write path) ของคุณใหม่

FAQ

DuckDB สามารถแทนที่ SQLite สำหรับฐานข้อมูลแอปพลิเคชันของฉันได้หรือไม่

ไม่ได้ หากแอปพลิเคชันของคุณมีการเขียนข้อมูลบ่อยครั้ง DuckDB จะทำการล็อกการเขียน (write lock) ทั้งไฟล์ฐานข้อมูล โดยอนุญาตให้มีกระบวนการอ่าน-เขียนได้เพียงหนึ่งรายการในขณะนั้น และถูกปรับแต่งมาเพื่อการเปลี่ยนแปลงข้อมูลจำนวนมาก (bulk changes) มากกว่าการแทรกข้อมูลทีละแถว (single-row inserts) ให้เก็บสถานะธุรกรรมไว้ใน SQLite และใช้ ATTACH '/srv/app/app.db' AS app (TYPE sqlite, READ_ONLY); เพื่อให้ DuckDB อ่านข้อมูลเมื่อต้องการทำรายงาน

DuckDB เร็วกว่า SQLite สำหรับงานวิเคราะห์ข้อมูลจริงหรือไม่

สำหรับการสแกนและคำนวณผลรวม (aggregates) บนตารางขนาดใหญ่ คำตอบคือใช่ เหตุผลมาจากรูปแบบการจัดเก็บข้อมูล ไม่ใช่เทคนิคการปรับแต่ง DuckDB จะอ่านเฉพาะคอลัมน์ที่ระบุในคำสั่ง query และประมวลผลค่าเป็นชุด (batches) ในขณะที่ SQLite ต้องไล่ดูทั้งแถวเพื่อเข้าถึงฟิลด์เดียว สำหรับการดึงข้อมูลแถวเดียวด้วย primary key ผลลัพธ์จะกลับกัน เพราะ SQLite เข้าถึงเพียงสองหน้า (pages) ในขณะที่ DuckDB ต้องเข้าถึงที่เก็บข้อมูลของทุกคอลัมน์

ฉันจำเป็นต้องใช้ RAM จำนวนมากเพื่อรัน DuckDB บน VPS หรือไม่

ไม่จำเป็น แต่ควรจำกัดการใช้งานและเผื่อพื้นที่สำหรับการ spill ข้อมูล DuckDB จะทำการ spill ผลลัพธ์ระหว่างการประมวลผลลงดิสก์โดยค่าเริ่มต้น แม้ในเซสชัน :memory: ตราบใดที่ไดเรกทอรีชั่วคราวสามารถเขียนข้อมูลได้และมีพื้นที่ว่าง จากนั้นให้ตั้งค่า SET memory_limit = '2GB'; เป็นค่าที่ VPS ของคุณสามารถจัดสรรให้ได้ หากปล่อยไว้ที่ค่าเริ่มต้นคือ 80% ของ RAM การประมวลผล GROUP BY ขนาดใหญ่อาจทำให้เกิด Out of Memory Error หรือทำให้บริการอื่นถูกดึงออกจาก RAM ได้

ฉันจะนำข้อมูลจาก SQLite ไปเป็น Parquet ได้อย่างไร

ให้แนบ (attach) ไฟล์ SQLite จาก DuckDB แล้วคัดลอกผลลัพธ์จาก query โดยตรงด้วย COPY (SELECT * FROM app.orders) TO '/srv/data/orders.parquet' (FORMAT parquet, COMPRESSION zstd); ให้รันคำสั่งนี้ตามกำหนดเวลาสำหรับรอบระยะเวลาที่ปิดแล้ว เช่น แถวข้อมูลของเดือนที่ผ่านมา และปล่อยให้แถวข้อมูลล่าสุดอยู่ใน SQLite ซึ่งเป็นที่ที่แอปพลิเคชันยังคงเขียนข้อมูลอยู่

ฉันควรสำรองข้อมูลตัวไหน และทำอย่างไร

ควรสำรองทั้งสองตัวด้วยวิธีที่ต่างกัน ให้ทำ snapshot ของ SQLite ด้วย sqlite3 app.db ".backup '/srv/backup/app.db'" แทนการใช้ cp เพราะฐานข้อมูลที่กำลังทำงานอยู่ถือเป็นทั้ง -wal และไฟล์ -shm ซึ่งการคัดลอกไฟล์โดยตรงอาจทำให้ข้อมูลเสียหายได้ ส่วนไฟล์ Parquet จะไม่มีการเปลี่ยนแปลงเมื่อเขียนเสร็จแล้ว ดังนั้นการคัดลอกไดเรกทอรีจึงเพียงพอแล้ว

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