تصور کنید یک عامل هوش مصنوعی بتواند مانند یک برنامهنویس حرفهای، از حافظه و مسیرهای استدلالی خود «برانچ» بگیرد و در صورت شکست، به سادگی به نسخه قبلی بازگردد. این قابلیت که پیش از این تنها در ابزارهایی مثل گیت (Git) وجود داشت، حالا با معرفی کتابخانه Prolly برای دادههای محلی در دسترس است. این نقشه مرتبشده با آدرسدهی محتوایی، به توسعهدهندگان اجازه میدهد برنامههای «اول-محلی» (Local-first) را با همان قدرت نسخهبندی گیت بسازند. با استفاده از درختهای prolly، یک عامل AI میتواند تاریخچهای تغییرناپذیر از حافظه خود نگه دارد، مسیرهای استدلال را شاخه بزند و وضعیتها را بدون سربار یک پایگاه داده سنتی ادغام کند. کاربران از این بسته با نام prolly-map استفاده میکنند، در حالی که وارد کردن کدها بسیار مختصر است: use prolly::{Config, Prolly};.
این فناوری در حالی معرفی میشود که صنعت نرمافزار به سمت رویکرد «اول-محلی» (Local-first) حرکت میکند؛ یعنی دادهها روی دستگاه کاربر میمانند و بهصورت غیرهمزمان همگام میشوند. در اکثر ذخیرهسازهای کلید-مقدار فعلی، دادهها تغییرپذیر (Mutable) هستند، به این معنی که وقتی مقداری را تغییر میدهید، حالت قبلی برای همیشه پاک میشود مگر اینکه یک سیستم ثبت وقایع (Logging) پیچیده بسازید. Prolly این مشکل را با تبدیل هر بهروزرسانی به یک هندل درخت تغییرناپذیر حل میکند، در حالی که دادههای تغییرنیافته بین نسخهها به اشتراک گذاشته میشوند.
همانطور که در تحلیلهای قبلی ما دربارهی مدیریت وضعیت در سیستمهای توزیعشده اشاره کردیم، حذف تغییرپذیری (Mutability) کلید دستیابی به پایداری در مقیاس است.
مکانیسم درختهای Prolly
طبق مستندات فنی، Prolly در هسته خود یک ابزار ذخیرهسازی محتوا-آدرسدهی شده (Content-addressed storage primitive) است. هر گره در درخت با یک شناسه محتوا (CID) شناسایی میشود که در واقع یک هش SHA-256 از بایتهای قطعی (Deterministic) گره است و طول آن ۳۲ بایت است. اگر دو گره محتوای یکسانی داشته باشند، CID یکسانی میگیرند؛ این یعنی در نسخههای مختلف یک نقشه، بخشهای بزرگی از ساختار بهصورت مشترک استفاده میشوند. در این سیستم، اگر ریشه دو درخت یکی باشد، کل درختها برابرند.
برخلاف درختهای B-tree استاندارد که در نقاط ثابت تقسیم میشوند، Prolly از تکهبندی تعریفشده توسط محتوا (Content-defined chunking) استفاده میکند. به این معنا که مرزها توسط خود دادهها و با تکنیکهایی مثل بررسی مرزهای xxHash64 یا توزیعهای Weibull تعیین میشوند. ساختار TreeFormat به فراخوانکنندهها اجازه میدهد بین معیارهای تعداد ورودی (entry-count)، بایتهای منطقی (logical-byte) یا بایتهای کدگذاریشده (encoded-byte) انتخاب کنند. سیاستهای «فقط-کلید» (Key-only) بهویژه مفید هستند زیرا مرزهای تکه را حتی زمانی که مقادیر تغییر میکنند، پایدار نگه میدارند.
وقتی تغییری کوچک رخ میدهد، تنها مسیر affected تا ریشه بازنویسی شود و بقیه درخت دستنخورده بماند. نویسنده کانونی (Canonical writer) از اولین تکه آسیبدیده شروع به استریم میکند و به محض اینکه مرزهای تعریفشده توسط محتوا دوباره تراز شوند، از پسوند قدیمی استفاده میکند. این امر تضمین میکند که محتوای منطقی معادل، مستقل از تاریخچه ویرایش، به ریشه یکسانی همگرا شود و در نتیجه عملیات Diff و Merge از نظر محاسباتی بسیار ارزان شوند.
جزئیات مدل دادهای
جزئیات مدل دادهای این کتابخانه به شرح زیر است:
- هندل درخت (Tree Handle): یک
Treeدر واقع یک هندل کوچک و پایدار است که شاملroot(یکOption<Cid>) وConfigمورد استفاده برای تکهبندی و کدگذاری است. این هندل مالک دادههای گره نیست. - ساختار گره: گرهها کلیدهای مرتبشده و مقادیر موازی را ذخیره میکنند. گرههای برگ (Leaf nodes) بایتهای خام کاربر را ذخیره میکنند، در حالی که گرههای داخلی CID فرزندان را نگه میدارند. هر گره سطح خود (
level- صفر برای برگها)، تعداد فرزندان برای رکوردهای منطقی (child_counts) و یک شناسه فرمت (format) را ردیابی میکند. - سریالسازی: گرهها از یک فرمت قطعی و خود-توصیفگر با یک هدر جادویی
CRABاستفاده میکنند. این کریت از یک سیاست «برش سخت» (hard-cutover) برای فرمتها استفاده میکند، به این معنی که فرمتهای آزمایشی قدیمیتر رمزگشایی نمیشوند. - مسیر خواندن: متد
get(&tree, key)یک جستوجوی ریشه-تا-برگ با پیچیدگی O(log n) انجام میدهد. APIهای بازه مانندrange_pageوreverse_pageامکان پیمایش محدود و مبتنی بر کرسر (cursor-based) را فراهم میکنند.
مکانیسمهای پیشرفته گره و تکهبندی
چیدمان گرهها و کدگذاری:
گرهها را میتوان از طریق NodeLayoutSpec با چیدمانهای مختلف پیکربندی کرد، از جمله فرمتهای PrefixCompressed (فشردهسازی پیشوند) یا Plain (ساده). تنظیمات Encoding متادیتای کدگذاری مقدار ذخیره شده را ثبت میکند. این تنظیمات در ترکیب با سیاست تکهبندی، تضمین میکنند که سریالسازی برای یک محموله و نسخه خاص، قطعی باشد.
سیاستهای تکهبندی:
توسعهدهندگان میتوانند بر اساس شکل دادههای خود، از چندین سیاست داخلی انتخاب کنند:
entry_count_key_hash(): پیشفرض برای مرزهای پایدار فقط-کلید و توزیع پیشبینیپذیر.entry_count_key_value_hash(): زمانی استفاده میشود که محتوای مقدار باید در هویت مرزها مشارکت کند.logical_bytes_key_weibull(): ایدهآل برای رکوردهایی که اندازه آنها بهطور قابل توجهی متفاوت است و تکههای احتمالی نرمی را فراهم میکند.logical_bytes_rolling_hash(): با استفاده از یک پنجره غلتان (rolling window)، همگامسازی قدرتمندی را پس از جابهجایی محتوا فراهم میکند.
قابلیتهای کلیدی برای توسعهدهندگان
به نقل از مستندات github.com، این کتابخانه قابلیتهای سطح بالایی را ارائه میدهد:
- بهروزرسانیهای تغییرناپذیر: عملیات
put،deleteوbatchدرخت فعلی را تغییر نمیدهند، بلکه یک هندلTreeجدید برمیگردانند. هندل قدیمی تا زمانی که ذخیرهساز حاوی گرههای ارجاع شده باشد، معتبر میماند. - تفاضل بهینه (Efficient Diffing): به دلیل هویت سبک مرکل (Merkle-style)، سیستم میتواند CIDهای یکسان را هرس کند و از کل زیردرختها بپرد. این سیستم از
range_diffبرای هرس زیردرختهای خارج از یک بازه کلید نیمهباز وstructural_diff_pageبرای ثبت نقاط بازرسی (checkpoint) در کارهای حجیم پشتیبانی میکند. - ادغام سهطرفه: پشتیبانی از استراتژیهای ادغام بدون تضاد و حلکنندههای سفارشی. شامل
merge_explainاست که یکMergeExplanationشامل ردی از زیردرختهای بازاستفاده شده و بازههای بازنویسی شده گرهها برمیگرداند. همچنین ازcrdt_mergeبرای رفتارهای خودکار بدون تضاد مانند «آخرین نویسنده برنده است» (last-writer-wins) پشتیبانی میکند. - اثباتهای قابل تایید: خواننده میتواند یک کلید یا بازهای از مقادیر را در برابر یک ریشه CID تایید کند بدون اینکه به کل ذخیرهساز دسترسی داشته باشد. اشکال اثبات شامل
prove_key،prove_keys،prove_range،prove_prefixو اثباتهای مبتنی بر صفحه برای اسکنهای کرسر است. اینها را میتوان از طریقsign_proof_bundle_hmac_sha256در یک پوش HMAC-SHA256 برای تشخیص دستکاری قرار داد. - جستوجوی کلید-بایتی مرتب: کلیدها بایتهای خامی هستند که بهصورت لغتنامهای (lexicographically) مرتب شدهاند. یک کمکی به نام
KeyBuilderاجازه میدهد کلیدهای ترکیبی ایمن با استفاده ازpush_u64،push_timestamp_millisو سایر کمکیهای عددی ساخته شوند تا ترتیب بایتها با ترتیب عددی مطابقت داشته باشد.
ذخیرهسازی پلاگینپذیر و پشتیبانی Async
Prolly بهگونهای طراحی شده که نسبت به Runtime خنثی باشد. این کتابخانه یک موتور AsyncProlly برای محیطهای با تأخیر بالا و یک نمای سنکرون Prolly برای برنامههای مسدودکننده (Blocking) فراهم میکند. مسیر سنکرون هیچ Runtimeای ایجاد نمیکند، رشتهای را متوقف نمیکند و فراخوانیها را به Tokio ارسال نمیکند.
لایه ذخیرهسازی کاملاً از طریق trait Store قابل جایگزینی است. متدهای مورد نیاز شامل get ،put ،delete و batch است، با بهینهسازیهای اختیاری مانند batch_get_ordered و batch_put_with_hint.
بکاندهای پشتیبانی شده:
- داخلی:
MemStore(در حافظه) وFileNodeStore(فضای نام CID تکهبندی شده پایدار). - آداپتورها: SQLite (
prolly-store-sqlite)، RocksDB (prolly-store-rocksdb)، Redis، redb، PGlite و SlateDB. - ابری (Cloud-Native): پشتیبانی Async بومی برای PostgreSQL، MySQL، Turso (با قابلیت اختیاری push/pull ابری از طریق
turso-cloud-sync)، DynamoDB، Cosmos DB و Spanner.
برای محمولههای بسیار حجیم، کتابخانه از یک BlobStore پشتیبانی میکند تا مقادیری که از یک آستانه پیکربندی شده فراتر میروند را خارج کند. مقادیر در یک ذخیرهساز محتوا-آدرسدهی شده نوشته میشوند و درخت یک پوش ValueRef::Blob حاوی CID بلاب و طول آن را ذخیره میکند. این کار از متورم شدن گرههای برگ توسط مقادیر بزرگ جلوگیری میکند. FileBlobStore رابط BlobStoreScan را پیاده میکند تا GC بتواند از لیست بکاند به عنوان مجموعه کاندید استفاده کند.
کاربردهای هوش مصنوعی و RAG
برای مهندسان AI، بیشترین ارزش فوری در تولید بازیابیافزا (RAG) قطعی نهفته است. با ثبت CID دقیق ریشه یک نقشه مجاورتی، توسعهدهندگان تضمین میکنند که یک مدل زبانی بزرگ (LLM) — شبیه کتابخانهداری که میلیاردها صفحه را خوانده و حالا با همان لحن جواب میدهد — برای یک پرسش خاص، دقیقاً همان زمینه (Context) را دریافت کند، حتی اگر ایندکس اصلی بعداً بهروز شود. این یعنی پاسخهای هوش مصنوعی کاملاً بازتولیدپذیر میشوند و امکان بازگشت (Rollback) کامل فراهم است.
سایر کاربردهای عملی AI عبارتند از:
- لاگ رویدادهای عامل: استفاده از دستههای (batch) با حجم بالای افزودن برای ثبت پیامها، فراخوانی ابزارها، نوشتنهای حافظه، نقاط بازرسی و خلاصهها (همانطور که در
agent_event_log.rsدیده میشود). - حافظه گفتگو: ایجاد برانچ برای «تلاشهای» مختلف عامل و ادغام مسیر موفق در حافظه اصلی با استفاده از انتشار CAS (Compare-And-Swap).
- نقشههای مجاورتی: پشتیبانی بومی از ایندکسگذاری نزدیکترین همسایه تقریبی (ANN) با شتابدهندههای SQ8/PQ/HNSW، اجرای async/SIMD و اثباتهای محدود به دیسکریپتور. این موارد در
docs/proximity-map.mdمستند شده است. - ردیابی منشأ (Provenance): استفاده از مقادیری که منبع، پارسر، Embedding، مدل و منشأ CID را حمل میکنند.
مدیریت پیشرفته وضعیت
برای سادهسازی تجربه توسعهدهنده، کتابخانه یک نمای VersionedMap را شامل میشود. این ابزار نیاز به هماهنگی دستی هندلهای درخت و ریشههای نامگذاری شده را از بین میبرد. این رابط یک تاریخچه خطی با مدیریت خودکار Head فراهم میکند و به توسعهدهندگان اجازه میدهد دستور rollback_to(version_id) را به همان سادگی یک سیستم کنترل نسخه فراخوانی کنند. همچنین از typed::<K,V,KC,VC> برای حذف کدهای تکراری سریالسازی با استفاده از VersionedJsonCodec یا VersionedCborCodec پشتیبانی میکند.
برای سیستمهای پیچیده، Prolly از تراکنشهای سختگیرانه (Strict Transactions) پشتیبانی میکند. یک تراکنش، گرههای جدید و نوشتنهای ریشه نامگذاری شده را در حافظه بافر میکند، هر ریشه نامگذاری شدهای را که خوانده است اعتبارسنجی میکند و سپس گرهها و ریشههای مرحلهبندی شده را بهصورت اتمیک Commit میکند. این امر تضمین میکند که یک نقشه منبع و ایندکسهای ثانویه مشتق شده از آن (مثلاً ایندکس email-to-user-id) همواره بهصورت همزمان بهروز شوند. SqliteStore از این تراکنشهای سختگیرانه از طریق یک تراکنش SQL واحد پشتیبانی میکند.
مانیفستهای ریشه و ابزارهای شبیه گیت
در حالی که هندلهای Tree تغییرناپذیر هستند، برنامهها به نامهای پایدار برای برانچها و نقاط بازرسی نیاز دارند. ManifestStore امکان ریشههای نامگذاری شده (اشارهگرهای تغییرپذیر به درختهای تغییرناپذیر) را فراهم میکند. این امر عملیات شبیه گیت را ممکن میسازد:
- شاخه زدن (Branching): بارگذاری یک Head منبع و انتشار آن تحت یک نام جدید با استفاده از
compare_and_swap_named_rootبرای جلوگیری از بازنویسی برانچهای موجود. - تگگذاری (Tagging): انتشار یک نام پایدار برای یک درخت موجود.
- Fast-Forwarding: انتقال یک برانچ از یک Head قدیمی به یک Head جدید از طریق CAS.
- Checkout: خواندن بازهای از درخت و تبدیل آن به وضعیت برنامه یا یک دایرکتوری کاری (workdir).
جمعآوری زباله و همگامسازی
از آنجایی که هر بهروزرسانی گرههای جدیدی ایجاد میکند، کتابخانه یک جمعآوریکننده زباله (GC) مبتنی بر قابلیت دسترسی (reachability-based) را پیاده کرده است. توسعهدهندگان میتوانند سیاستهای NamedRootRetention را تعریف کنند — مانند نگه داشتن N ریشه جدید تحت یک پیشوند، نامهای دقیق ریشه، یا تمام ریشههای بهروز شده از یک زمان خاص (Unix-millisecond) — و GC تنها CIDهایی را پاک میکند که دیگر غیرقابل دسترس هستند. متد plan_gc به عنوان یک اجرای آزمایشی عمل کرده و گرهها و بایتهای قابل بازیابی را قبل از حذف توسط sweep_gc گزارش میدهد.
برای همگامسازی بین دستگاهها، Prolly از «برنامهریزی گرههای مفقود» (missing-node planning) استفاده میکند. یک کلاینت میتواند ریشه محلی خود را با یک ریشه راه دور با استفاده از plan_missing_nodes مقایسه کند، که درخت منبع را پیمایش کرده و مقصد را با خواندنهای دستهای مرتبشده بررسی میکند. این ابزار همگامسازی مرکل پهنای باند را با کپی کردن تنها CIDهای خاصی که مقصد فاقد آنهاست، به حداقل میرساند. اگر یک ذخیرهساز بایتی را برگرداند که با CID درخواست شده مطابقت ندارد، عملیات با خطای Error::CidMismatch شکست میخورد.
حافظه پنهان (Caching) و عملکرد
هر مدیر یک node_cache برای گرههای تغییرناپذیر و یک rightmost_path_cache برای حجمهای کاری با افزودن زیاد (append-heavy) دارد. توسعهدهندگان میتوانند مصرف حافظه را از طریق node_cache_max_nodes یا node_cache_max_bytes محدود کنند. برای بهینهسازی مسیرهای داغ (hot paths)، کتابخانه از pin_tree_root و pin_tree_path پشتیبانی میکند که مسیرهای خاص ریشه-تا-برگ را در حافظه نگه میدارند تا از I/O ذخیرهساز جلوگیری شود.
علاوه بر این، ذخیرهسازهای دارای قابلیت Hint میتوانند یک راهنمای مسیر راستترین (rightmost-path hint) یا راهنمای مسیر ریشه-تا-برگ را برای بازههای پیشوند داغ ذخیره کنند. این به Workerهای جدید اجازه میدهد تا لنگر افزودن (append anchor) یا مسیرهای خاص هر مستاجر (tenant) را بهسرعت با استفاده از hydrate_prefix_path_hint بازیابی کنند. نویسندگان همچنین میتوانند راهنمای ChangedSpan را منتشر کنند تا بازههای احتمالی داغ را برای ایندکسگذاری پسزمینه اولویتبندی کنند.
متریکهای مدیر و مشاهدهپذیری
برای نظارت بر عملکرد، Prolly و AsyncProlly متریکهای تجمعی را ارائه میدهند. این متریکها موارد nodes_written ،nodes_read ،node_cache_misses و cache_evictions را ردیابی میکنند. متریکها بایتهای سریالسازی شده گره را قبل از هرگونه فشردهسازی خاص بکاند میشمارند. این به توسعهدهندگان اجازه میدهد فشار واقعی I/O و کارایی حافظه پنهان را برای حجم کاری خاص خود مشاهده کنند.
این معماری فرض بنیادی را که وضعیت باید یک نقطه زمانی واحد و تغییرپذیر باشد، تغییر میدهد. با تبدیل یک ایندکس پایگاه داده به مجموعهای از اسنپشاتهای تغییرناپذیر، توسعهدهندگان میتوانند قابلیتهای Undo/Redo، حسابرسی (Auditing) و ویرایش مشارکتی را به عنوان شهروندان درجه یک لایه داده پیادهسازی کنند.
گام بعدی شما
- اگر در حال توسعه عاملهای AI با حافظه بلندمدت هستید، کتابخانه
prolly-mapرا برای جایگزینی با DBهای تغییرپذیر بررسی کنید. - برای پیادهسازی RAG بازتولیدپذیر، از ثبت CID ریشه در هر درخواست استنتاج استفاده کنید.
- ساختار
VersionedMapرا برای پیادهسازی قابلیت Undo/Redo در حافظه عامل خود به کار بگیرید.
اما تأثیر این معماری بر کاهش هزینههای استنتاج در مقیاس کلان حتی جذابتر است — به تحلیل ما درباره بهینهسازی KV Cache مراجعه کنید.




گفتگو