การปรับแต่งประสิทธิภาพ SQLite: การตั้งค่า PRAGMA สำหรับการใช้งานจริง & การเข้ารหัส ChaCha20
คำนำ
การตั้งค่า SQLite สำหรับระบบอีเมลในสภาพแวดล้อมจริงไม่ได้หมายถึงแค่การทำให้มันใช้งานได้เท่านั้น — แต่หมายถึงการทำให้มันเร็ว ปลอดภัย และเชื่อถือได้ภายใต้ภาระงานหนัก หลังจากที่ประมวลผลอีเมลนับล้านฉบับที่ Forward Email เราได้เรียนรู้ว่าสิ่งใดที่สำคัญจริง ๆ สำหรับประสิทธิภาพของ SQLite
คู่มือนี้ครอบคลุมการตั้งค่าการใช้งานจริงของเรา ผลการทดสอบประสิทธิภาพในหลายเวอร์ชันของ Node.js และการปรับแต่งเฉพาะที่สร้างความแตกต่างเมื่อคุณจัดการกับปริมาณอีเมลจำนวนมาก
การถดถอยของประสิทธิภาพ Node.js ในเวอร์ชัน v22 และ v24
เราค้นพบการถดถอยของประสิทธิภาพอย่างมีนัยสำคัญใน Node.js เวอร์ชัน v22 และ v24 ที่ส่งผลกระทบต่อประสิทธิภาพของ SQLite โดยเฉพาะกับคำสั่ง SELECT ผลการทดสอบของเราชี้ให้เห็นว่าการดำเนินการ SELECT ลดลงประมาณ 57% ต่อวินาทีใน Node.js v24 เมื่อเทียบกับ v20 เราได้รายงานปัญหานี้กับทีม Node.js ใน nodejs/node#60719
เนื่องจากการถดถอยนี้ เราจึงใช้วิธีระมัดระวังในการอัปเกรด Node.js แผนปัจจุบันของเราคือ:
- เวอร์ชันปัจจุบัน: ขณะนี้เราใช้ Node.js v18 ซึ่งได้สิ้นสุดการสนับสนุนระยะยาว ("EOL") แล้ว คุณสามารถดู ตารางเวลาการสนับสนุน LTS ของ Node.js ได้ที่นี่
- แผนอัปเกรด: เราจะอัปเกรดเป็น Node.js v20 ซึ่งเป็นเวอร์ชันที่เร็วที่สุดตามผลการทดสอบของเราและไม่ได้รับผลกระทบจากการถดถอยนี้
- หลีกเลี่ยง v22 และ v24: เราจะไม่ใช้ Node.js v22 หรือ v24 ในสภาพแวดล้อมจริงจนกว่าปัญหาประสิทธิภาพนี้จะได้รับการแก้ไข
นี่คือตารางเวลาที่แสดงตารางการสนับสนุน LTS ของ Node.js และเส้นทางการอัปเกรดของเรา:
สถาปัตยกรรม SQLite สำหรับ Forward Email ในการใช้งานจริง
นี่คือวิธีที่เราใช้ SQLite ในการใช้งานจริง:
การตั้งค่า PRAGMA ที่เราใช้จริง
นี่คือสิ่งที่เราใช้จริงในระบบ production โดยตรงจาก setup-pragma.js:
// Forward Email's actual production PRAGMA settings
async function setupPragma(db, session, cipher = 'chacha20') {
// Quantum-resistant encryption
db.pragma(`cipher='${cipher}'`);
db.key(Buffer.from(decrypt(session.user.password)));
// Core performance settings
db.pragma('journal_mode=WAL');
db.pragma('secure_delete=ON');
db.pragma('auto_vacuum=FULL');
db.pragma(`busy_timeout=${config.busyTimeout}`);
db.pragma('synchronous=NORMAL');
db.pragma('foreign_keys=ON');
db.pragma(`encoding='UTF-8'`);
db.pragma('optimize=0x10002');
// Critical: Use disk for temp storage, not memory
db.pragma('temp_store=1');
// Custom temp directory to avoid disk full errors
const tempStoreDirectory = path.join(path.dirname(db.name), '/tmp');
await mkdirp(tempStoreDirectory);
db.pragma(`temp_store_directory='${tempStoreDirectory}'`);
}
Important
เราใช้ temp_store=1 (ดิสก์) แทน temp_store=2 (หน่วยความจำ) เพราะฐานข้อมูลอีเมลขนาดใหญ่สามารถใช้หน่วยความจำเกิน 10+ GB ได้ง่ายในระหว่างการทำงานเช่น VACUUM
ผลลัพธ์การทดสอบประสิทธิภาพ
เราทดสอบการตั้งค่าของเรากับตัวเลือกต่าง ๆ ในหลายเวอร์ชันของ Node.js นี่คือข้อมูลจริง:
ผลลัพธ์ประสิทธิภาพ Node.js v20.19.5
| การตั้งค่า | การตั้งค่า (มิลลิวินาที) | แทรก/วินาที | เลือก/วินาที | อัปเดต/วินาที | ขนาดฐานข้อมูล (MB) |
|---|---|---|---|---|---|
| Forward Email Production | 120.1 | 10,548 | 17,494 | 16,654 | 3.98 |
| WAL Autocheckpoint 1000 | 89.7 | 11,800 | 18,383 | 22,087 | 3.98 |
| Cache Size 64MB | 90.3 | 11,451 | 17,895 | 21,522 | 3.98 |
| Memory Temp Storage | 111.8 | 9,874 | 15,363 | 21,292 | 3.98 |
| Synchronous OFF (Unsafe) | 94.0 | 10,017 | 13,830 | 18,884 | 3.98 |
| Synchronous EXTRA (Safe) | 94.1 | 3,241 | 14,438 | 3,405 | 3.98 |
Tip
การตั้งค่า wal_autocheckpoint=1000 แสดงประสิทธิภาพโดยรวมที่ดีที่สุด เรากำลังพิจารณาเพิ่มการตั้งค่านี้ใน config production ของเรา
การแยกแยะการตั้งค่า PRAGMA
การตั้งค่าหลักที่เราใช้
| PRAGMA | ค่า | จุดประสงค์ | ผลกระทบต่อประสิทธิภาพ |
|---|---|---|---|
cipher |
'chacha20' |
การเข้ารหัสที่ต้านทานควอนตัม | ภาระงานน้อยกว่าการใช้ AES |
journal_mode |
WAL |
การบันทึกล่วงหน้า (Write-Ahead Logging) | เพิ่มประสิทธิภาพพร้อมกัน 40% |
secure_delete |
ON |
เขียนทับข้อมูลที่ถูกลบ | ความปลอดภัยแลกกับค่าใช้จ่าย 5% |
auto_vacuum |
FULL |
การเก็บคืนพื้นที่อัตโนมัติ | ป้องกันฐานข้อมูลบวม |
busy_timeout |
30000 |
เวลารอเมื่อฐานข้อมูลถูกล็อก | ลดความล้มเหลวในการเชื่อมต่อ |
synchronous |
NORMAL |
สมดุลระหว่างความทนทานและประสิทธิภาพ | เร็วกว่าการตั้งค่า FULL 3 เท่า |
foreign_keys |
ON |
ความสมบูรณ์ของข้อมูลเชิงสัมพันธ์ | ป้องกันการเสียหายของข้อมูล |
temp_store |
1 |
ใช้ดิสก์สำหรับไฟล์ชั่วคราว | ป้องกันการใช้หน่วยความจำเกิน |
การตั้งค่าที่เราไม่ใช้ (แต่คุณอาจต้องการ)
| PRAGMA | เหตุผลที่เราไม่ใช้ | คุณควรพิจารณาหรือไม่? |
|---|---|---|
wal_autocheckpoint=1000 |
ยังไม่ได้ตั้งค่า | ใช่ - การทดสอบของเราชี้ให้เห็นการเพิ่มประสิทธิภาพ 12% |
cache_size=-64000 |
ค่าเริ่มต้นเพียงพอ | อาจจะ - ปรับปรุง 8% สำหรับงานที่เน้นอ่านมาก |
mmap_size=268435456 |
ความซับซ้อนเทียบกับประโยชน์ | ไม่ - กำไรน้อย ปัญหาเฉพาะแพลตฟอร์ม |
analysis_limit=1000 |
เราใช้ 400 | ไม่ - ค่าที่สูงกว่าจะทำให้การวางแผนคำสั่งช้า |
Caution
เราหลีกเลี่ยงการใช้ temp_store=MEMORY โดยเฉพาะ เพราะไฟล์ SQLite ขนาด 10GB อาจใช้ RAM มากกว่า 10GB ในระหว่างการทำงาน VACUUM
การเข้ารหัส ChaCha20 กับ AES256
เรามุ่งเน้นความต้านทานควอนตัมมากกว่าประสิทธิภาพดิบ:
// กลยุทธ์สำรองการเข้ารหัสของเรา
try {
db.pragma(`cipher='chacha20'`);
db.key(Buffer.from(decrypt(session.user.password)));
db.pragma('journal_mode=WAL');
} catch (err) {
// สำรองสำหรับเวอร์ชัน SQLite เก่า
if (cipher === 'chacha20' && err.code === 'SQLITE_NOTADB') {
return setupPragma(db, session, 'aes256cbc');
}
throw err;
}
การเปรียบเทียบประสิทธิภาพ:
-
ChaCha20: ~10,500 การแทรกต่อวินาที
-
AES256CBC: ~11,200 การแทรกต่อวินาที
-
ไม่เข้ารหัส: ~12,800 การแทรกต่อวินาที
ต้นทุนประสิทธิภาพ 6% ของ ChaCha20 เทียบกับ AES คุ้มค่ากับความต้านทานควอนตัมสำหรับการเก็บอีเมลระยะยาว
ที่เก็บข้อมูลชั่วคราว: /tmp กับ /dev/shm
เรากำหนดตำแหน่งที่เก็บข้อมูลชั่วคราวอย่างชัดเจนเพื่อหลีกเลี่ยงปัญหาพื้นที่ดิสก์:
// การตั้งค่าที่เก็บข้อมูลชั่วคราวของ Forward Email
const tempStoreDirectory = path.join(path.dirname(db.name), '/tmp');
await mkdirp(tempStoreDirectory);
db.pragma(`temp_store_directory='${tempStoreDirectory}'`);
// ตั้งค่าตัวแปรสภาพแวดล้อมด้วย
process.env.SQLITE_TMPDIR = tempStoreDirectory;
ประสิทธิภาพ /tmp กับ /dev/shm
| ตำแหน่งที่เก็บข้อมูล | เวลา VACUUM | การใช้หน่วยความจำ | ความน่าเชื่อถือ |
|---|---|---|---|
/tmp (ดิสก์) |
2.3 วินาที | 50MB | ✅ เชื่อถือได้ |
/dev/shm (RAM) |
0.8 วินาที | 2GB+ | ⚠️ อาจทำให้ระบบล่ม |
| ค่าเริ่มต้น | 4.1 วินาที | ตัวแปร | ❌ ไม่แน่นอน |
Warning
การใช้ /dev/shm สำหรับที่เก็บข้อมูลชั่วคราวอาจใช้ RAM ทั้งหมดในระหว่างการทำงานขนาดใหญ่ ควรใช้ที่เก็บข้อมูลชั่วคราวบนดิสก์สำหรับการใช้งานจริง
การปรับแต่งโหมด WAL
การเขียนล่วงหน้า (Write-Ahead Logging) สำคัญสำหรับระบบอีเมลที่มีการเข้าถึงพร้อมกัน:
ผลกระทบของการตั้งค่า WAL
การทดสอบของเราแสดงว่า wal_autocheckpoint=1000 ให้ประสิทธิภาพดีที่สุด:
// การปรับแต่งที่อาจทำให้ดีขึ้นที่เรากำลังทดสอบ
db.pragma('wal_autocheckpoint=1000');
ผลลัพธ์:
-
ค่าเริ่มต้น autocheckpoint: 10,548 การแทรกต่อวินาที
-
wal_autocheckpoint=1000: 11,800 การแทรกต่อวินาที (+12%) -
wal_autocheckpoint=0: 9,200 การแทรกต่อวินาที (WAL โตเกินไป)
การออกแบบสคีมาเพื่อประสิทธิภาพ
สคีมาของเราสำหรับเก็บอีเมลเป็นไปตามแนวทางปฏิบัติที่ดีที่สุดของ SQLite:
-- ตารางข้อความพร้อมลำดับคอลัมน์ที่ปรับแต่ง
CREATE TABLE messages (
id INTEGER PRIMARY KEY,
mailbox_id INTEGER NOT NULL,
uid INTEGER NOT NULL,
date INTEGER NOT NULL,
flags TEXT,
subject TEXT,
from_addr TEXT,
to_addr TEXT,
message_id TEXT,
raw BLOB, -- BLOB ขนาดใหญ่ไว้ท้าย
FOREIGN KEY (mailbox_id) REFERENCES mailboxes(id)
);
-- ดัชนีสำคัญสำหรับประสิทธิภาพ IMAP
CREATE INDEX idx_messages_mailbox_date ON messages(mailbox_id, date DESC);
CREATE INDEX idx_messages_uid ON messages(mailbox_id, uid);
CREATE INDEX idx_messages_flags ON messages(mailbox_id, flags) WHERE flags IS NOT NULL;
Tip
ควรวางคอลัมน์ BLOB ไว้ที่ท้ายสุดของการกำหนดตารางเสมอ SQLite จะเก็บคอลัมน์ขนาดคงที่ก่อน ทำให้การเข้าถึงแถวเร็วขึ้น
การปรับแต่งนี้มาจากผู้สร้าง SQLite โดยตรง, D. Richard Hipp:
"นี่คือคำแนะนำ - ให้ทำคอลัมน์ BLOB เป็นคอลัมน์สุดท้ายในตารางของคุณ หรือจะเก็บ BLOB ไว้ในตารางแยกที่มีแค่สองคอลัมน์: คีย์หลักแบบจำนวนเต็มและ BLOB เอง แล้วเข้าถึงเนื้อหา BLOB โดยใช้ join เมื่อจำเป็น หากคุณวางฟิลด์จำนวนเต็มขนาดเล็กต่างๆ หลัง BLOB SQLite จะต้องสแกนเนื้อหา BLOB ทั้งหมด (ตามลิงก์ลิสต์ของหน้าในดิสก์) เพื่อไปยังฟิลด์จำนวนเต็มที่ท้ายสุด และนั่นจะทำให้ช้าลงแน่นอน"
— D. Richard Hipp, ผู้เขียน SQLite
เราได้นำการปรับแต่งนี้ไปใช้ใน โครงสร้าง Attachments ของเรา โดยย้ายฟิลด์ body ที่เป็น BLOB ไปไว้ท้ายสุดของการกำหนดตารางเพื่อประสิทธิภาพที่ดีขึ้น
การจัดการการเชื่อมต่อ
เราไม่ใช้ connection pooling กับ SQLite — ผู้ใช้แต่ละคนจะได้ฐานข้อมูลที่เข้ารหัสของตัวเอง วิธีนี้ให้การแยกกันอย่างสมบูรณ์ระหว่างผู้ใช้ เหมือนกับ sandboxing แตกต่างจากสถาปัตยกรรมของบริการอื่นที่ใช้ MySQL, PostgreSQL หรือ MongoDB ที่อีเมลของคุณอาจถูกเข้าถึงโดยพนักงานที่ไม่หวังดี ฐานข้อมูล SQLite ต่อผู้ใช้ของ Forward Email ทำให้ข้อมูลของคุณแยกกันอย่างสมบูรณ์และถูก sandboxed
เราไม่เคยเก็บรหัสผ่าน IMAP ของคุณ ดังนั้นเราไม่เคยเข้าถึงข้อมูลของคุณ — ทุกอย่างทำในหน่วยความจำ เรียนรู้เพิ่มเติมเกี่ยวกับ แนวทางการเข้ารหัสที่ต้านทานควอนตัม ที่อธิบายว่าระบบของเราทำงานอย่างไร
// วิธีฐานข้อมูลต่อผู้ใช้
async function getDatabase(session) {
const dbPath = path.join(
config.databaseDir,
session.user.domain_name,
`${session.user.username}.db`
);
const db = new Database(dbPath, {
cipher: 'chacha20',
readonly: session.readonly || false
});
await setupPragma(db, session);
return db;
}
วิธีนี้ให้:
-
การแยกกันอย่างสมบูรณ์ระหว่างผู้ใช้
-
ไม่มีความซับซ้อนของ connection pool
-
การเข้ารหัสอัตโนมัติสำหรับแต่ละผู้ใช้
-
การสำรองข้อมูล/กู้คืนที่ง่ายขึ้น
ด้วย auto_vacuum=FULL เราแทบไม่ต้องทำ VACUUM ด้วยตนเอง:
// กลยุทธ์การทำความสะอาดของเรา
db.pragma('optimize=0x10002'); // เมื่อเปิดการเชื่อมต่อ
db.pragma('optimize'); // เป็นระยะ (รายวัน)
// ทำ vacuum ด้วยตนเองเฉพาะการทำความสะอาดใหญ่
if (deletedDataPercentage > 25) {
db.exec('VACUUM');
}
ผลกระทบของ Auto Vacuum ต่อประสิทธิภาพ:
-
auto_vacuum=FULL: คืนพื้นที่ทันที, เขียนช้าลง 5% -
auto_vacuum=INCREMENTAL: ควบคุมด้วยตนเอง, ต้องเรียกPRAGMA incremental_vacuumเป็นระยะ -
auto_vacuum=NONE: เขียนเร็วที่สุด, ต้องทำVACUUMด้วยตนเอง
การตรวจสอบและวินิจฉัย
เมตริกสำคัญที่เราติดตามในระบบจริง:
// คำสั่งตรวจสอบประสิทธิภาพ
const stats = {
page_count: db.pragma('page_count', { simple: true }),
page_size: db.pragma('page_size', { simple: true }),
freelist_count: db.pragma('freelist_count', { simple: true }),
wal_checkpoint: db.pragma('wal_checkpoint(PASSIVE)', { simple: true })
};
const dbSizeMB = (stats.page_count * stats.page_size) / 1024 / 1024;
const fragmentationPct = (stats.freelist_count / stats.page_count) * 100;
Note
เราตรวจสอบเปอร์เซ็นต์การแตกกระจายและเริ่มการบำรุงรักษาเมื่อเกิน 15%
ประสิทธิภาพของ Node.js เวอร์ชันต่างๆ
การทดสอบเปรียบเทียบอย่างละเอียดของเราข้ามเวอร์ชัน Node.js แสดงความแตกต่างของประสิทธิภาพอย่างมีนัยสำคัญ:
ผลลัพธ์ข้ามเวอร์ชันทั้งหมด
| เวอร์ชัน Node | Forward Email Production | การแทรกข้อมูลสูงสุด/วินาที | การเลือกข้อมูลสูงสุด/วินาที | การอัปเดตสูงสุด/วินาที | หมายเหตุ |
|---|---|---|---|---|---|
| v18.20.8 | 10,658 / 14,466 / 18,641 | 11,663 (ปิด Sync) | 14,868 (หน่วยความจำชั่วคราว) | 20,095 (MMAP) | ⚠️ เตือนเครื่องยนต์ |
| v20.19.5 | 10,548 / 17,494 / 16,654 | 11,800 (WAL อัตโนมัติ) | 18,383 (WAL อัตโนมัติ) | 22,087 (WAL อัตโนมัติ) | ✅ แนะนำ |
| v22.21.1 | 9,829 / 15,833 / 18,416 | 11,260 (ปิด Sync) | 17,413 (MMAP) | 20,731 (MMAP) | ⚠️ ช้ากว่าโดยรวม |
| v24.11.1 | 9,938 / 7,497 / 10,446 | 10,628 (Incr Vacuum) | 16,821 (Incr Vacuum) | 19,934 (Incr Vacuum) | ❌ ช้าลงอย่างมีนัยสำคัญ |
ข้อมูลเชิงลึกเกี่ยวกับประสิทธิภาพหลัก
Node.js v18 (Legacy LTS):
- ประสิทธิภาพการแทรกข้อมูลใกล้เคียงกับ v20 (10,658 เทียบกับ 10,548 ops/sec)
- การเลือกข้อมูลช้ากว่า v20 ถึง 17% (14,466 เทียบกับ 17,494 ops/sec)
- แสดงคำเตือน npm engine สำหรับแพ็กเกจที่ต้องการ Node ≥20
- การปรับแต่งการจัดเก็บข้อมูลชั่วคราวในหน่วยความจำทำงานได้ดีกว่าการตรวจสอบจุดตรวจสอบอัตโนมัติ WAL
- ยอมรับได้สำหรับแอปพลิเคชันรุ่นเก่า แต่แนะนำให้อัปเกรด
Node.js v20 (แนะนำ):
- ประสิทธิภาพโดยรวมสูงสุดในทุกการดำเนินการ
- การปรับแต่งการตรวจสอบจุดตรวจสอบอัตโนมัติ WAL ให้การเพิ่มประสิทธิภาพที่สม่ำเสมอ 12%
- ความเข้ากันได้ดีที่สุดกับโมดูล SQLite แบบเนทีฟ
- เสถียรที่สุดสำหรับงานในสภาพแวดล้อมการผลิต
Node.js v22 (ยอมรับได้):
- การแทรกข้อมูลช้ากว่า v20 7%, การเลือกข้อมูลช้ากว่า 9%
- การปรับแต่ง MMAP ให้ผลลัพธ์ดีกว่าการตรวจสอบจุดตรวจสอบอัตโนมัติ WAL
- ต้องติดตั้ง
npm installใหม่ทุกครั้งที่เปลี่ยนเวอร์ชัน Node - ยอมรับได้สำหรับการพัฒนา แต่ไม่แนะนำสำหรับการผลิต
Node.js v24 (ไม่แนะนำ):
- การแทรกข้อมูลช้ากว่า v20 6%, การเลือกข้อมูลช้ากว่า 57%
- ประสิทธิภาพการอ่านข้อมูลถดถอยอย่างมีนัยสำคัญ
- การทำ vacuum แบบเพิ่มขึ้นทำงานได้ดีกว่าการปรับแต่งอื่น ๆ
- หลีกเลี่ยงสำหรับแอปพลิเคชัน SQLite ในการผลิต
ความเข้ากันได้ของโมดูลเนทีฟ
ปัญหา "ความเข้ากันได้ของโมดูล" ที่เราเจอในตอนแรกได้รับการแก้ไขโดย:
# สลับเวอร์ชัน Node และติดตั้งโมดูลเนทีฟใหม่
nvm use 22
rm -rf node_modules
npm install
ข้อควรพิจารณาสำหรับ Node.js v18:
- แสดงคำเตือน engine:
Unsupported engine { required: { node: '>=20.0.0' } } - ยังคงคอมไพล์และรันได้สำเร็จแม้มีคำเตือน
- แพ็กเกจ SQLite สมัยใหม่หลายตัวตั้งเป้า Node ≥20 เพื่อการสนับสนุนที่ดีที่สุด
- แอปพลิเคชันรุ่นเก่าสามารถใช้ v18 ต่อไปได้ด้วยประสิทธิภาพที่ยอมรับได้
Important
ควรติดตั้งโมดูลเนทีฟใหม่ทุกครั้งที่เปลี่ยนเวอร์ชัน Node.js โมดูล better-sqlite3-multiple-ciphers ต้องคอมไพล์สำหรับแต่ละเวอร์ชัน Node โดยเฉพาะ
Tip
สำหรับการใช้งานในสภาพแวดล้อมการผลิต ให้ใช้ Node.js v20 LTS ประโยชน์ด้านประสิทธิภาพและความเสถียรมีมากกว่าคุณสมบัติภาษาใหม่ใน v22/v24 Node v18 ยอมรับได้สำหรับระบบรุ่นเก่าแต่แสดงการถดถอยของประสิทธิภาพในการอ่านข้อมูล
รายการตรวจสอบก่อนการใช้งานจริง
ก่อนการใช้งานจริง ให้แน่ใจว่า SQLite มีการปรับแต่งเหล่านี้:
- ตั้งค่าตัวแปรสภาพแวดล้อม
SQLITE_TMPDIR - ตรวจสอบว่ามีพื้นที่ดิสก์เพียงพอสำหรับการทำงานชั่วคราว (2 เท่าของขนาดฐานข้อมูล)
- กำหนดค่าการหมุนเวียนบันทึกสำหรับไฟล์ WAL
- ตั้งค่าการตรวจสอบขนาดฐานข้อมูลและการกระจายตัวของข้อมูล
- ทดสอบกระบวนการสำรองข้อมูล/กู้คืนพร้อมการเข้ารหัส
- ตรวจสอบการรองรับการเข้ารหัส ChaCha20 ในการสร้าง SQLite ของคุณ
การแก้ไขปัญหาที่พบบ่อย
ข้อผิดพลาด "Database is locked"
// เพิ่มเวลารอ busy timeout
db.pragma('busy_timeout=60000'); // 60 วินาที
// ตรวจสอบธุรกรรมที่ทำงานนาน
const info = db.pragma('wal_checkpoint(FULL)');
if (info.busy > 0) {
console.warn('WAL checkpoint ถูกบล็อกโดยผู้อ่านที่กำลังใช้งาน');
}
การใช้หน่วยความจำสูงระหว่าง VACUUM
// ตรวจสอบหน่วยความจำก่อน VACUUM
const beforeMem = process.memoryUsage();
db.exec('VACUUM');
const afterMem = process.memoryUsage();
console.log(
`การเปลี่ยนแปลงหน่วยความจำจาก VACUUM: ${
(afterMem.heapUsed - beforeMem.heapUsed) / 1024 / 1024
}MB`
);
ประสิทธิภาพการสืบค้นช้า
// เปิดใช้งานการวิเคราะห์การสืบค้น
db.pragma('analysis_limit=400'); // การตั้งค่าของ Forward Email
db.exec('ANALYZE');
// ตรวจสอบแผนการสืบค้น
const plan = db
.prepare('EXPLAIN QUERY PLAN SELECT * FROM messages WHERE date > ?')
.all(Date.now() - 86400000);
console.log(plan);
การมีส่วนร่วมของ Forward Email ในโอเพนซอร์ส
เราได้แบ่งปันความรู้เกี่ยวกับการปรับแต่ง SQLite กลับสู่ชุมชน:
-
การปรับปรุงเอกสาร Litestream - ข้อเสนอแนะของเราเพื่อเคล็ดลับประสิทธิภาพ SQLite ที่ดีกว่า
-
Better SQLite3 Multiple Ciphers - การรองรับการเข้ารหัส ChaCha20
-
งานวิจัยการปรับแต่งประสิทธิภาพ SQLite - อ้างอิงในการใช้งานของเรา
-
แพ็กเกจ npm ที่มียอดดาวน์โหลดพันล้านครั้งมีผลต่อระบบนิเวศ JavaScript อย่างไร - การมีส่วนร่วมที่กว้างขึ้นของเราต่อการพัฒนา npm และ JavaScript
Benchmark Source Code
โค้ดเบนช์มาร์กทั้งหมดมีให้ในชุดทดสอบของเรา:
# Run the benchmarks yourself
git clone https://github.com/forwardemail/sqlite-benchmarks
cd sqlite-benchmarks
npm install
npm run benchmark
เบนช์มาร์กทดสอบ:
-
การผสมผสาน PRAGMA ต่างๆ
-
ประสิทธิภาพ ChaCha20 เทียบกับ AES256
-
กลยุทธ์การตรวจสอบจุดเช็คพอยต์ WAL
-
การกำหนดค่าที่เก็บข้อมูลชั่วคราว
-
ความเข้ากันได้ของเวอร์ชัน Node.js
What's Next for SQLite at Forward Email
เรากำลังทดสอบการปรับแต่งเหล่านี้อย่างต่อเนื่อง:
-
การปรับแต่ง WAL Autocheckpoint: เพิ่ม
wal_autocheckpoint=1000ตามผลลัพธ์เบนช์มาร์ก -
การบีบอัด: ประเมิน sqlite-zstd สำหรับการจัดเก็บไฟล์แนบ
-
ขีดจำกัดการวิเคราะห์: ทดสอบค่าที่สูงกว่าค่า 400 ปัจจุบันของเรา
-
ขนาดแคช: พิจารณาการปรับขนาดแคชแบบไดนามิกตามหน่วยความจำที่มีอยู่
Getting Help
มีปัญหาด้านประสิทธิภาพ SQLite หรือไม่? สำหรับคำถามเฉพาะเกี่ยวกับ SQLite, SQLite Forum เป็นแหล่งข้อมูลที่ยอดเยี่ยม และ performance tuning guide ครอบคลุมการปรับแต่งเพิ่มเติมที่เรายังไม่จำเป็นต้องใช้
เรียนรู้เพิ่มเติมเกี่ยวกับ Forward Email โดยอ่าน คำถามที่พบบ่อย