MVCC و تراکنشها تو RocksDB چطور کار میکنن؟
خلاصهٔ کاملتر
RocksDB یه پایگاهدادهی کلید-مقداریه که رو یه ساختار LSM-tree ساخته شده، یعنی هیچوقت دیتای قبلی رو جای خودش عوض نمیکنه و هر نوشتن یه نسخهی تازه از کلید میسازه. نویسنده تو این پست میگه همین رفتار نصف راه پیادهسازی MVCC (کنترل همزمانی چندنسخهای) ـه؛ نصف دیگهش اینه که به خوانندهها یه نمای ثابت و پایدار از دیتا بدی، حتی وقتی نویسندهها دارن همزمان کار میکنن. این مدل باعث میشه خواننده و نویسنده مجبور نباشن منتظر هم بمونن، برخلاف قفلگذاری ساده که سرعت رو پایین میاره.
برای اینکه این کار عملی بشه، RocksDB به هر نوشتن یه شمارهترتیب (sequence number) یکتا و صعودی میده و اونو کنار کلید و مقدار ذخیره میکنه. دو تا شماره رو دنبال میکنه: آخرین شمارهی تخصیصدادهشده و شمارهی منتشرشده که خوانندهها میبینن. وقتی یه کوئری شروع میشه، همون شمارهی منتشرشدهی لحظهی شروع رو قفل میکنه و فقط نسخههایی از کلید رو میبینه که شمارهشون مساوی یا کمتر از اون ـه؛ نسخههای جدیدتر که بعد از شروع کوئری نوشته شدن، نادیده گرفته میشن. این جستجو رو یه ساختار skip list با پیچیدگی زمانی لگاریتمی انجام میده.
روی همین مکانیزم، RocksDB اتمیکبودن رو هم پیاده میکنه: وقتی چند تا put تو یه write batch با هم اجرا میشن، همهشون یهجا و همزمان برای خوانندهها دیده میشن، نه تیکهتیکه.
let mut wb = WriteBatch::new();
wb.put("a", "1");
wb.put("b", "2");
db.write(wb)?;Snapshot هم دقیقاً همین شمارهترتیب رو تو یه لحظهی مشخص ثابت نگه میداره تا چند تا خوندن پشتسرهم همه یه نسخهی واحد از دیتا رو ببینن؛ کامپکشن هم قبل از حذف نسخههای قدیمی، لیست snapshotهای فعال رو چک میکنه.
برای گاربیجکالکشن یه ساختار دیگه به اسم SuperVersion هست که با شمارندهی رفرنس، memtable و SST فایلهای در حال استفاده رو نگه میداره تا وقتی یه خوندن در جریانه، فایلش پاک نشه. نویسنده فرق لاک (قفل منطقی روی کلید) و لچ (قفل فیزیکی روی ساختار داده) رو هم باز میکنه و میگه مسیر اصلی خوندن و نوشتن تو RocksDB اصلاً لچ نگه نمیداره، فقط بعضی مسیرهای کمتکرار مثل جایگزینی SuperVersion بعد از flush یا compaction یه mutex سراسری کوتاه میگیرن.
لایهی تراکنش رو RocksDB روی همین نسخهبندی ساخته: نوشتهها اول تو بافر تراکنش میمونن و فقط موقع commit روی دیتابیس اعمال میشن. دو تا مدل داره: تراکنش pessimistic که همون لحظهی put یا delete، کلید رو قفل میکنه و بقیه رو تا commit یا timeout معطل نگه میداره؛ و تراکنش optimistic که اصلاً چیزی رو قفل نمیکنه و فقط موقع commit چک میکنه که کلیدهای درگیر از وقتی تراکنش شروع شده تغییر نکرده باشن، وگرنه commit شکست میخوره.
نکات کلیدی:
- LSM-tree پایهی MVCC ـه چون هیچوقت دیتا رو جای خودش عوض نمیکنه
- هر نوشتن یه sequence number میگیره که خوانندهها رو به یه نمای ثابت از دیتا محدود میکنه
- write batch و snapshot اتمیکبودن و ثبات خوندن رو تضمین میکنن
- SuperVersion با رفرنسکانتینگ مطمئن میشه فایلی که یه خواننده داره ازش استفاده میکنه پاک نشه
- تراکنشها دو مدلن: pessimistic با قفلگذاری زودهنگام، optimistic با چک تصادم موقع commit




