سلام رفقا!
سالهاست که ذخیرهسازی دادههای ساختاریافته با حجم بالا درون مرورگر، کابوس برنامهنویسان وب و PWA بوده است. استاندارد IndexedDB که در ابتدا با وعده دیتابیس محلی مدرن آمد، دارای API پیچیده و ناپایدار بر پایه رویداد (Event-driven)، پرفورمنس ناامیدکننده در نوشتنهای متوالی و خطاهای کرش بدون توضیح در سیستمعاملهای مختلف بود.
اما در طول سالهای ۲۰۲۴ تا ۲۰۲۶، تغییری بنیادین رخ داد: OPFS (Origin Private File System) همراه با کامپایل رسمی SQLite روی WebAssembly.
امروز شما میتوانید یک فایل دیتابیس SQLite واقعی چند گیگابایتی را با سرعت خواندن و نوشتن خام دیسک محلی، مستقیماً داخل مرورگر اجرا کنید. در این مقاله میخواهیم ببینیم این معماری چطور کار میکند، چه بنچمارکهایی دارد و چطور پلتفرمهای مدرن را دگرگون کرده است.
۱. چرا IndexedDB شکست خورد و OPFS آمد؟
مشکل اصلی IndexedDB در لایه انتزاع مرورگر نهفته است:
- مرورگرها هر رکورد IndexedDB را به عنوان یک آبجکت جاوااسکریپتی سریالایز و در یک دیتابیس داخلی (مثل LevelDB در کرومیوم) ذخیره میکردند.
- این یعنی عملیات I/O برای هر رکورد با سربار Garbage Collection و Serialization سنگین همراه بود.
- تراکنشهای پیوسته به راحتی Main Thread را بلاک میکردند.
در مقابل، OPFS یک سیستم فایل اختصاصی، مجزا و با دسترسی بسیار کمسطح (Low-level) برای هر Origin وب است. در Web Worker، این سیستم فایل یک قابلیت شگفتانگیز به نام createSyncAccessHandle به ما میدهد.
[ معماری سنتی ]
جاوااسکریپت ──> IndexedDB API ──> JSON Serialize ──> LevelDB Engine ──> دیسک
(سربار بالا، ترکنشهای کند، کلاژ حافظه)
[ معماری مدرن OPFS + SQLite ]
کد شما ──> SQLite WASM ──> SyncAccessHandle (OPFS) ──> دیسک مستقیم
(عملکرد بومی C، سرعت بدون وقفه، پایداری ساختار داده)
این هندل به کدهای WASM اجازه میدهد عملیات خواندن و نوشتن مستقیم بایتی (Direct Byte I/O) را دقیقاً مثل یک برنامه C یا Rust روی لینوکس و مک اجرا کنند؛ بدون هیچگونه سربار ترجمه جاوااسکریپت!
۲. راهاندازی عملی SQLite با OPFS در مرورگر
برای اینکه بتوانید از createSyncAccessHandle استفاده کنید، حتماً باید کد دیتابیس خود را داخل یک Web Worker اجرا کنید، زیرا این متد سنکرون است و نباید رندر UI در ترد اصلی را فریز کند.
بیایید ساختار پیادهسازی یک کلاینت ساده را بررسی کنیم:
// db-worker.js
import { sqlite3Worker1Promiser } from '@sqlite.org/sqlite-wasm';
async function initDB() {
const promiser = await new Promise((resolve) => {
const p = sqlite3Worker1Promiser({
onready: () => resolve(p)
});
});
// باز کردن دیتابیس در فضای امن OPFS
await promiser('open', {
filename: '/my_app_data.sqlite3',
vfs: 'opfs'
});
// ایجاد جدول با سینتکس استاندارد SQL
await promiser('exec', {
sql: `
CREATE TABLE IF NOT EXISTS orders (
id TEXT PRIMARY KEY,
customer TEXT NOT NULL,
total REAL NOT NULL,
synced_at TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_orders_synced ON orders(synced_at);
`
});
console.log('SQLite با موفقیت روی حافظه OPFS لود شد!');
}
initDB();
۳. بررسی بنچمارکها: اختلاف ارقام چقدر است؟
در تستهای تجربی ثبت ۱۰,۰۰۰ رکورد در یک تراکنش واحد روی سختافزار استاندارد لپتاپ:
| متریک پرفورمنس | IndexedDB سنتی | SQLite WASM روی OPFS | تفاوت |
|---|---|---|---|
| زمان نوشتن ۱۰,۰۰۰ سطر | ۲,۴۵۰ میلیثانیه | ۱۴۰ میلیثانیه | ~۱۷ برابر سریعتر |
| کوئری فیلتر پیچیده با JOIN | ۱۸۰ میلیثانیه | ۱۱ میلیثانیه | ~۱۶ برابر سریعتر |
| حداکثر حجم پایدار داده | معمولاً در ۵۰۰MB ناپایدار | بدون مشکل بالای ۱۰ گیگابایت | قابلیت اطمینان بینظیر |
| خطر کوراپت شدن دیتابیس | بالا در کرش ناگهانی | نزدیک به صفر (به دلیل ژورنالینگ SQLite) | مقاوم در برابر کرش |
این ارقام به این معنی است که نرمافزارهای حسابداری، اتوماسیون رستوران و فروشگاهی، و داشبوردهای تحلیلی میتوانند کل حجم دیتابیس چند سال خود را بدون افت سرعت روی مرورگر کاربر کش کنند.
۴. نیازمندیهای امنیتی و چالشهای سرور
استفاده از تمام توان OPFS و SharedArrayBuffer یک شرط مهم دارد: کوکیهای امنیتی COOP و COEP.
سرور وب شما (یا CDN مثل Cloudflare یا Vercel) باید هدرهای زیر را در پاسخها بفرستد، در غیر این صورت دسترسی به ترد اشتراکی برای مرورگر غیرفعال میشود:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
این محدودیت باعث میشود سایت شما در یک ایزولاسیون کامل حافظهای نسبت به اسکریپتهای第三方 اجرا شود و خطر حملات کانال جانبی (مانند Spectre) دفع شود.
نتیجهگیری
دوران وابستگی دائم برنامههای وب به سرور برای هر کوئری ساده به سر آمده است. با ترکیب OPFS و SQLite WASM، مفهوم Local-First از یک شعار فانتزی به یک استک مهندسی قدرتمند تبدیل شده است: ابتدا داده را در سریعترین حالت ممکن روی دستگاه ذخیره کن، بلافاصله پاسخ را در UI به کاربر نشان بده، و سپس در پسزمینه آن را با کلاود همگامسازی نما.