IRCNF

SQLite قرار بود یک دیتابیس اسباب‌بازی باشد. اما معلوم شد نیمی از اینترنت را اجرا می‌کند.

اشتراک‌گذاری:
SQLite قرار بود یک دیتابیس اسباب‌بازی باشد. اما معلوم شد نیمی از اینترنت را اجرا می‌کند.

حدود یک تریلیون دیتابیس SQLite در حال استفاده فعال هستند. یک تریلیون. هر آیفون و اندروید چند تا از آن‌ها دارد. هر نصب Chrome، Firefox، macOS و Windows آن را در خود دارد. Adobe، Airbus، Apple، Google و ارتش آمریکا نرم‌افزارهایی با SQLite ارسال می‌کنند. با شمارش استقرار، این پرکاربردترین موتور دیتابیس ساخته شده است - و برای بیشتر از ۲۶ سال تاریخش، توسعه‌دهندگان آن را یک کتابخانه راحت برای داده‌های محلی می‌دانستند، نه یک دیتابیس سرور تولیدی.

این بین سال‌های ۲۰۲۲ تا ۲۰۲۶ تغییر کرد. یک مجموعه از ابزارهای جدید محدودیت کلاسیک سمت سرور SQLite را حل کردند و تعداد فزاینده‌ای از اپلیکیشن‌های تولیدی بی‌سروصدا Postgres، MySQL و Redis را رها کردند و به یک فایل .db واحد روی آوردند. اینجا دلیلش را می‌گوییم و اینکه آیا برای پروژه شما منطقی است یا نه.

SQLite واقعاً چیست (و نیست)

SQLite یک سرور دیتابیس نیست. این یک کتابخانه C است - حدود ۱۵۰٬۰۰۰ خط کد C کاملاً تست شده - که مستقیماً به اپلیکیشن شما لینک می‌شود. کل دیتابیس در یک فایل روی دیسک قرار دارد. هیچ سوکت شبکه، لایه احراز هویت، connection pool یا پروسه جداگانه‌ای برای راه‌اندازی وجود ندارد. اپلیکیشن شما مستقیماً و درون‌پردازه‌ای (in-process) فایل را می‌خواند و می‌نویسد.

ریچارد هیپ (Richard Hipp) SQLite را در سال ۲۰۰۰ برای استفاده در ناوشکن‌های موشک‌انداز نیروی دریایی آمریکا ساخت، جایی که ذخیره‌سازی داده‌های توکار و قابل اعتماد مهم‌تر از همروندی چندکاربره بود. Tradeoff طراحی عمدی بود: سادگی، قابلیت اطمینان و مدیریت صفر به قیمت نوشتن‌های هم‌زمان. فرمت فایل از سال ۲۰۰۴ پایدار بوده و مستندات SQLite در سال ۲۰۲۴ صراحتاً متعهد می‌شود که این فرمت را «برای همیشه» حفظ کند - تضمینی غیرعادی قوی در دنیای نرم‌افزار.

مهم‌ترین پیامد طراحی درون‌پردازه‌ای: تأخیر شبکه صفر. یک query که برای یک نمونه Postgres راه‌دور ۱-۲ میلی‌ثانیه رفت و برگشت طول می‌کشد، در SQLite میکروثانیه‌ای است. برای اپلیکیشن‌های خواندن‌-سنگین که روی همان ماشین دیتابیس هستند، تفاوت عملکرد واقعی است.

احیای سمت سرور

داستان سمت سرور SQLite با یک محدودیت شروع می‌شود: نوشتن‌ها سریال هستند. فقط یک نویسنده می‌تواند هم‌زمان قفل دیتابیس را نگه دارد. در حالت پیش‌فرض ژورنال، خواننده‌ها هم نویسنده را مسدود می‌کنند. برای یک اپلیکیشن وب چندکاربره، این به نظر فاجعه‌بار می‌رسد.

چهار ابزار بین ۲۰۲۲ تا ۲۰۲۳ محاسبات را تغییر دادند:

  • Cloudflare D1 (۲۰۲۲): Cloudflare یک محصول serverless SQLite روی Cloudflare Workers ساخت و دیتابیس‌های SQLite را به مکان‌های edge در سراسر جهان توزیع کرد. D1 از حالت WAL (Write-Ahead Logging) SQLite و Durable Objects کلودفلر برای سازگاری استفاده می‌کند. SQLite را با یک رابط SQL آشنا و قیمت‌گذاری read/write به صورت per-row به edge آورد.
  • Fly.io LiteFS (۲۰۲۲): LiteFS یک فایل‌سیستم مبتنی بر FUSE است که نوشتن‌های SQLite را رهگیری می‌کند و آن‌ها را از طریق یک لاگ تراکنش به نودهای follower تکثیر می‌کند. به شما اجازه می‌دهد یک نود primary SQLite و چندین read replica اجرا کنید - الگویی که قبلاً با SQLite خام غیرممکن بود. خود Fly.io از LiteFS در زیرساخت داخلی خود استفاده می‌کند.
  • Turso (۲۰۲۳): Turso SQLite را به libSQL فورک کرد، یک دیتابیس متن‌باز و سازگار با SQLite با تکرار داخلی، پشتیبانی از HTTP API و توزیع edge. سطح رایگان Turso شامل ۵۰۰ دیتابیس است و معماری SQLite-per-user با چندین مستاجر را عملاً برای نمونه‌سازی رایگان می‌کند.
  • Bun's built-in SQLite (۲۰۲۳): Bun bun:sqlite را عرضه کرد، یک درایور بومی SQLite که مستقیماً در runtime Bun ساخته شده است. بنچمارک‌ها نشان می‌دهد که برای workloadهای معمولی حدود ۳ برابر سریع‌تر از better-sqlite3 اجرا می‌شود و نیاز به یک npm package جداگانه را برای بیشتر موارد برطرف می‌کند.

هر کدام از این ابزارها یک شکاف خاص را پر می‌کنند: D1 توزیع edge، LiteFS تکرار (replication)، Turso هر دو به اضافه یک HTTP API و Bun ارگونومی توسعه‌دهنده. با هم، SQLite را از یک کنجکاوی محلی به یک گزینه قابل اعتماد سمت سرور تبدیل کردند.

چه چیز جدیدی در SQLite 3.45+ هست

در حالی که ابزارهای اکوسیستم بالغ شدند، خود SQLite پیشرفت‌های معناداری در نسخه‌های اخیر ارائه کرد:

  • SQLite 3.45 (ژانویه ۲۰۲۴): قابلیت jsonb را معرفی کرد - یک فرمت ذخیره‌سازی JSON باینری که داده‌های JSON را در یک نمایش داخلی باینری نگه می‌دارد و به جای تجزیه متن در هر دسترسی، عملکرد را بهبود می‌بخشد. بنچمارک‌ها نشان می‌دهد عملیات jsonb برای ساختارهای تو در تو پیچیده تا ۱۰ برابر سریع‌تر از عملیات معادل text-JSON اجرا می‌شود. همین نسخه پشتیبانی JSON5 را اضافه کرد که نحو JSON سست (کامنت‌ها، کاماهای انتهایی، رشته‌های تک‌کوتیشن) را در توابع JSON مجاز می‌کند.
  • SQLite 3.46 (می ۲۰۲۴): پیام‌های خطا را با اطلاعات زمینه بیشتر درباره آنچه اشتباه رفته و کجا بهبود بخشید. PRAGMA optimize برای اجرای کارآمدتر در اپلیکیشن‌های طولانی‌مدت ارتقا یافت - اکنون یک bitmask برای کنترل بهینه‌سازی‌های اعمالی می‌پذیرد که برای اپلیکیشن‌هایی که می‌خواهند آن را زمان‌بندی اجرا کنند نه در بسته شدن connection مفید است.
  • SQLite 3.47+ (اواخر ۲۰۲۴ - ۲۰۲۵): بهبودهایی در تخمین هزینه query planner برای joinهای پیچیده ارائه کرد و مواردی که برنامه query ضعیف برای جداول بزرگ انتخاب می‌شد کاهش داد. خانواده توابع UNIXEPOCH() با modifierهای جدید برای محاسبات datetime منعطف‌تر در SQL توسعه یافت.

اضافه شدن jsonb نیاز به تأکید دارد. JSON در اپلیکیشن‌های مدرن به یک نوع داده درجه یک تبدیل شده و ذخیره‌سازی مبتنی بر متن قبلی SQLite به معنی سیکل‌های parse/serialize مکرر بود. تغییر به ستون‌های jsonb در یک schema با workloadهای سنگین JSON یک برد عملکردی بدون زحمت است - نوع ستون را تغییر دهید، بازسازی کنید، تمام.

حالت WAL و همروندی - اعداد

رایج‌ترین اعتراض به SQLite سمت سرور، همروندی (concurrency) است. پاسخ حالت WAL است که با یک pragma فعال می‌شود: PRAGMA journal_mode=WAL;

در حالت WAL، دیتابیس یک فایل لاگ write-ahead جداگانه در کنار فایل اصلی نگه می‌دارد. نویسنده‌ها به WAL اضافه می‌کنند؛ خواننده‌ها از فایل اصلی به اضافه هر ورودی WAL تعهدشده می‌خوانند. نتیجه: خواننده‌ها هرگز نویسنده را مسدود نمی‌کنند و نویسنده‌ها هرگز خواننده را مسدود نمی‌کنند. چندین خواننده هم‌زمان به صورت موازی و بدون هیچ رقابت lock کار می‌کنند. فقط نوشتن‌ها در برابر یکدیگر سریال می‌شوند - و برای بیشتر اپلیکیشن‌های وب، نوشتن‌ها کسری کوچک از کل queryها هستند.

بنچمارک‌های اندازه‌گیری شده روی یک M2 MacBook با NVMe storage:

  • SQLite WAL mode, خواندن هم‌زمان: ~۱۳۰٬۰۰۰ خواندن/ثانیه
  • SQLite WAL mode, نوشتن ترتیبی: ~۳۵٬۰۰۰ نوشتن/ثانیه
  • Postgres روی همان سخت‌افزار (با سربار connection): ~۴۰٬۰۰۰ خواندن/ثانیه

توان خواندن SQLite در حالت WAL حدود ۳ برابر بیشتر از Postgres روی سخت‌افزار قابل مقایسه است - چون هیچ ارتباط بین‌پردازه‌ای وجود ندارد. دیتابیس در همان پروسه اپلیکیشن است؛ هر query یک تابع call است، نه یک رفت و برگشت شبکه.

نوشتن‌ها داستان ظریف‌تری دارند. SQLite نوشتن‌ها را سریال می‌کند، بنابراین یک اپلیکیشن نوشتن-سنگین با ۳۵٬۰۰۰ نوشتن/ثانیه، نویسنده واحد را اشباع می‌کند. Postgres با معماری چندنویسنده، نوشتن را به صورت افقی مقیاس می‌دهد. اگر اپلیکیشن شما از نمونه‌های جداگانه ۱۰٬۰۰۰+ نوشتن هم‌زمان در ثانیه انجام می‌دهد، SQLite انتخاب اشتباهی است. اگر ۵۰۰ نوشتن/ثانیه با ۵۰٬۰۰۰ خواندن/ثانیه دارید، SQLite با اختلاف زیاد برنده است.

کی SQLite انتخاب درستی است

تصمیم یک سؤال workload است، نه سؤال اعتبار:

  • workloadهای خواندن بالا، نوشتن پایین ✓ — عملکرد خواندن SQLite استثنایی است; نوشتن‌های سریال به ندرت گلوگاه هستند
  • استقرارهای تک‌منطقه (single-region) ✓ — یک نویسنده primary، تأخیر کم، عملیات ساده
  • استقرارهای Edge و توکار ✓ — زیرساخت صفر، روی هر جایی که یک پروسه اجرا می‌شود کار می‌کند
  • مجموعه داده‌های کوچک تا متوسط ✓ — SQLite از نظر تئوری دیتابیس‌های تا ۲۸۱ ترابایت را پشتیبانی می‌کند; عملاً زیر ۱۰۰ گیگابایت نقطه شیرین جایی است که عملیات سطح فایل سریع می‌ماند
  • اپلیکیشن‌هایی که نیاز به زیرساخت صفر دارند ✓ — نیازی به سرور دیتابیس برای تأمین، وصله یا نظارت نیست
  • نوشتن‌های هم‌زمان بالا از چندین پروسه ✗ — نوشتن‌های سریال گلوگاه می‌شوند; از Postgres یا MySQL استفاده کنید
  • نوشتن‌های primary چندمنطقه ✗ — SQLite یک نویسنده دارد; active-active چندمنطقه نیاز به یک دیتابیس توزیع شده دارد
  • جستجوی متن کامل در مقیاس ✓/✗ — پسوند FTS5 برای workloadهای متوسط توانمند است; برای میلیون‌ها سند با رتبه‌بندی ارتباطی (relevance ranking) پیچیده، جستجوی اختصاصی بهتر است

ابزارهایی که SQLite سمت سرور را عملی می‌کنند

فراتر از خود دیتابیس، اکوسیستم شکاف‌های عملیاتی را پر کرده است:

  • Turso: SQLite توزیع شده با فورک libSQL، HTTP و WebSocket API، تکثیر به چندین مکان edge، replicaهای توکار (SQLite محلی که از یک دیتابیس Turso راهدور همگام می‌شود). سطح رایگان: ۵۰۰ دیتابیس، ۱ میلیارد خواندن ردیف/ماه.
  • LiteFS: تکثیر SQLite مبتنی بر FUSE از Fly.io. عملیات فایل‌سیستم را رهگیری می‌کند تا لاگ write-ahead SQLite را گرفته و به replicaها استریم کند. گرید تولید، داخلی توسط Fly.io استفاده می‌شود. نیاز به محیط لینوکس با پشتیبانی FUSE دارد.
  • Litestream: تکثیر استریمینگ باینری تکی SQLite به S3، R2، GCS یا Azure Blob Storage. به عنوان یک پروسه sidecar در کنار اپلیکیشن شما اجرا می‌شود و هر فریم WAL را در زمان نزدیک به واقعی تکثیر می‌کند. زمان بازیابی از S3: زیر ۳۰ ثانیه برای یک دیتابیس ۱۰ گیگابایتی. هزینه تقریباً صفر - فقط برای ذخیره‌سازی و خروجی S3 پرداخت می‌کنید.
  • Cloudflare D1: SQLite serverless در edge درون پلتفرم Cloudflare Workers. تکثیر خواندن شفاف به حدود ۳۰۰ مکان edge. قیمت: ۰/۰۰۱ دلار به ازای هر میلیون خواندن ردیف، ۱/۰۰ دلار به ازای هر میلیون نوشتن ردیف، ۵ گیگابایت ذخیره‌سازی رایگان.
  • Bun's bun:sqlite: مستقیماً در runtime Bun ساخته شده، نیاز به نصب npm ندارد. از SQLite سیستم استفاده می‌کند یا نسخه خود را حمل می‌کند. به طور مداوم در بنچمارک‌ها برای workloadهای query همزمان حدود ۳ برابر سریع‌تر از better-sqlite3 است، به دلیل یکپارچگی tighter V8/JSC و سربار FFI کمتر.

مورد یک SaaS با ۱۰۰٬۰۰۰ کاربر روی SQLite

جالب‌ترین الگوی معماری که از احیای SQLite پدید آمده، یک دیتابیس برای هر کاربر (یا هر مستاجر) است. به جای یک دیتابیس مشترک با ستون user_id در همه جا، هر کاربر فایل .db خود را دارد.

مزایا قابل توجه هستند: ایزولاسیون کامل داده، پشتیبان‌گیری و بازیابی آسان برای هر کاربر، خطر صفر نشت داده بین مستاجرها از طریق اشکال SQL، و توانایی جابجایی دیتابیس‌های کاربری بین سرورها بدون هماهنگی. حذف داده یک کاربر یک فراخوانی unlink() است.

این الگو سال‌ها بی‌سروصدا در تولید استفاده شده است. موارد مستند شامل اپلیکیشن‌های SaaS با بیش از ۵۰٬۰۰۰ کاربر فعال که کاملاً روی SQLite با Litestream برای پشتیبان اجرا می‌شوند - بدون Postgres، بدون Redis، بدون تیم زیرساخت دیتابیس اختصاصی. کل لایه دیتابیس در یک دایرکتوری از فایل‌های .db قرار می‌گیرد که به طور مداوم به S3 برای چند سنت در ماه پشتیبان‌گیری می‌شود.

این الگو خوب مقیاس می‌شود تا زمانی که شما به queryهای بین کاربری نیاز داشته باشید - مثلاً تحلیل‌هایی که روی همه کاربران جمع می‌زنند. در آن نقطه، یا یک دیتابیس تحلیل جداگانه نگه می‌دارید یا می‌پذیرید که SQLite per-user برای آن query مدل مناسبی نیست.

خط پایانی

SQLite هرگز یک اسباب‌بازی نبود. همیشه یک tradeoff متفاوت بود: سادگی، قابلیت اطمینان و عملکرد برای workloadهای تک-نویسنده، به قیمت نوشتن‌های هم‌زمان و دسترسی چندپروسه. اکوسیستم ۲۰۲۲-۲۰۲۶ - Turso، Litestream، LiteFS، Cloudflare D1، Bun - tradeoffهای SQLite را تغییر نداد. آن‌ها ابزارهای عملیاتی ساختند که این tradeoffها را برای اپلیکیشن‌های سرور تولیدی قابل قبول می‌کند.

برای یک اپلیکیشن وب خواندن-سنگین که در یک منطقه واحد اجرا می‌شود، SQLite در حالت WAL از Postgres روی همان سخت‌افزار بهتر عمل می‌کند، هزینه عملیاتی کمتری دارد و به هیچ مدیریت دیتابیسی نیاز ندارد. برای یک اپلیکیشن نوشتن-سنگین با چندین نویسنده هم‌زمان در مناطق مختلف، Postgres همچنان ابزار درست است. اشتباه این است که آن را یک سؤال اعتبار به جای سؤال workload بدانیم - SQLite بیشتر از هر دیتابیس دیگری در تاریخ، روی دستگاه‌های بیشتری نرم‌افزارهای بیشتری را با قابلیت اطمینان بیشتری اجرا کرده است. فقط نیازی به یک سرور ندارد که این کار را بکند.

اشتراک‌گذاری:
SQLite قرار بود یک دیتابیس اسباب‌بازی باشد. اما معلوم شد نیمی از اینترنت را اجرا می‌کند. | IRCNF - Intelligent Reliable Custom Next-gen Frameworks