SQLite WAL: قلّل انتظار الكتابة من 420ms لـ 70ms
مستوى القارئ: متوسط
هتعرف هنا إمتى تفعيل SQLite WAL يفرق فعلًا، وإزاي تضبطه بدون ما تسيب ملف WAL يكبر ويبطّأ القراءة بعد أسبوع تشغيل.
المشكلة باختصار
الطريقة الشائعة الغلط إنك تشوف رسالة database is locked فتزود retries وخلاص. الطريقة دي بتفشل لما عندك قراءات طويلة وكتابة قصيرة، لأنك بتعالج العرض مش السبب. الافتراض إن عندك تطبيق desktop، API صغير، queue محلية، أو edge service بتستخدم SQLite على نفس الماكينة، وفيه مثلًا 50 ألف قراءة في اليوم و500 إلى 3000 عملية كتابة.
في وضع rollback journal التقليدي، الكتابة ممكن تتداخل بشكل مؤلم مع القراءة. النتيجة إن p95 لانتظار الكتابة ممكن يوصل 420ms في اختبار ضغط بسيط. بعد تفعيل WAL، وإضافة busy_timeout، وجدولة checkpoint، ممكن تنزل نفس القراءة إلى حوالي 70-95ms حسب القرص وحجم المعاملات. الرقم هنا مثال قياس محلي، مش وعد ثابت لكل بيئة.
الفكرة بمثال بسيط
ركز في المثال ده: عندك كشك بيع صغير. فيه موظف بيقرأ الأسعار طول اليوم، وموظف تاني بيضيف عملية بيع كل دقيقة. بدل ما الموظف اللي بيكتب يقفل الدفتر كله على اللي بيقرأ، WAL يخليه يكتب في ورقة جانبية مؤقتة. القارئ يكمل من الدفتر الأساسي، والكاتب يضيف التغييرات في الورقة الجانبية. بعدين تيجي خطوة اسمها checkpoint ترجع الورقة الجانبية للدفتر الأساسي.
علميًا، SQLite WAL يعني Write-Ahead Log. التغييرات تتكتب في ملف -wal بدل تعديل ملف قاعدة البيانات الأساسي فورًا. ده يسمح للقراء والكاتب إنهم يشتغلوا في نفس الوقت في حالات كثيرة. لكن مهم جدًا: WAL لا يحوّل SQLite إلى PostgreSQL. ما زال فيه كاتب واحد فعليًا في نفس اللحظة.
الإعداد العملي القابل للنسخ
أفضل طريقة تبدأ بيها هي إعداد واضح عند فتح الاتصال، ثم قياس قبل وبعد. لو بتستخدم sqlite CLI، شغّل الأوامر دي مرة على قاعدة البيانات:
PRAGMA journal_mode=WAL;
PRAGMA busy_timeout=5000;
PRAGMA synchronous=NORMAL;
PRAGMA wal_autocheckpoint=1000;
ولو عندك سكربت Python صغير، خلي كل اتصال يضبط timeout ويقرأ الوضع الحالي:
import sqlite3
conn = sqlite3.connect("app.db", timeout=5)
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA busy_timeout=5000")
conn.execute("PRAGMA synchronous=NORMAL")
conn.execute("PRAGMA wal_autocheckpoint=1000")
mode = conn.execute("PRAGMA journal_mode").fetchone()[0]
print("journal_mode=", mode)