| ۱-۱ | لایههای Ceph: سه رابط بلوک، شیء و فایل همگی روی یک لایهی مشترک RADOS ساخته شدهاند |
| ۱-۲ | بازسازی چند به یک در RAID در برابر بازیابی چند به چند در Ceph: در Ceph همهی OSDهای سالم در ترمیم مشارکت میکنند |
| ۱-۳ | نقش daemonها: کلاینت نقشهها را از MONها میگیرد و سپس مستقیم با OSDها کار میکند |
| ۱-۴ | مسیر جایگذاری: نام شیء به یک PG نگاشت میشود و CRUSH آن PG را به مجموعهای از OSDها در دامنههای خرابی متفاوت میبرد |
| ۱-۵ | مسیر تصمیمگیری در برنامهریزی: نوع بار، دامنهی خرابی، شبکه و ظرفیت |
| ۱-۶ | توپولوژی آزمایشگاه: سه node برای شروع، node چهارم برای گسترش، و یک ماشین کلاینت |
| ۱-۷ | مراحل bootstrap و افزودن میزبانها با cephadm |
| ۱-۸ | سه لایهی مسیر یک شیء. در لایهی منطقی، نام شیء با تابع hash به نام rjenkins به یک عدد ۳۲ بیتی تبدیل میشود (hobject_t این hash را همراه namespace و snap نگه میدارد) و ceph_stable_mod آن را به یک PG از pool نگاشت میکند. در لایهی نگاشت، CRUSH با seed جایگذاری (pps) و rule آن pool فهرست OSDها را میسازد، سپس استثناهای ثبتشده در OSDMap یعنی upmap و pg_temp/primary_temp روی آن اعمال میشوند و up set و acting set به دست میآیند. نخستین عضو acting، primary است. در لایهی فیزیکی، هر PG روی هر OSD یک collection در BlueStore است (مثلاً ۳.۱a_head). فرادادهی هر شیء یک onode در RocksDB است و داده از طریق زنجیرهی extent، blob و pextent به بازههای خام روی دستگاه بلوکی اشاره میکند |
| ۱-۹ | پیشوندهای کلید در RocksDB داخل BlueStore که در BlueStore.cc تعریف شدهاند. S برای superblock، T برای آمار، C برای collectionها، O برای onodeها، M برای omap، L برای تراکنشهای deferred، B برای freelist و X برای shared blobها است. ردیف پایین فیلدهای اصلی pg_log_entry_t را نشان میدهد که در omap شیء ویژهی pgmeta هر PG ذخیره میشود. این فیلدها عبارتاند از عملیات، شناسهی شیء، نسخه و نسخهی قبلی، reqid برای تشخیص درخواست تکراری پس از reconnect کلاینت، و mod_desc که در EC اجازهی rollback میدهد |
| ۱-۱۰ | نتیجهی محاسبهی دقیق روی نگاشت واقعی CRUSH برای ۱۲۰ OSD روی ۱۰ میزبان و pool سهنسخهای (crushtool و osdmaptool، Ceph 18.2.7). چپ: احتمال اینکه سه خرابی همزمان دستکم یک PG را کاملاً از بین ببرند، برحسب pg_num، همراه با کران بالای pg_num/C(۱۲۰,۳). راست: تعداد OSDهای همPG با یک OSD (میانگین و بازه) و تعداد PGهایی که پس از out شدن osd.0 روی پرکارترین OSD مقصد مینشینند |
| ۱-۱۱ | زمان بازگرداندن افزونگی پس از خرابی یک دیسک پر ۱۶ ترابایتی. قرمز RAID با یک دیسک یدک است و سبز Ceph، که در آن تعداد PGهای دیسک خراب و تعداد PGهای پرکارترین مقصد از نگاشت واقعی CRUSH (osdmaptool --mark-out 0) آمده است. محور عمودی لگاریتمی است و فقط نرخ ۰.۱۵ TB/h برای هر دیسک فرضی است |
| ۲-۱ | ساختار یک image در RADOS: شیء header برای فراداده و اشیای دادهی ۴ مگابایتی که فقط پس از نوشتن ساخته میشوند |
| ۲-۲ | راههای دسترسی به RBD: librbd در فضای کاربر (QEMU، rbd-nbd، دروازهی NVMe-oF) و krbd در هسته |
| ۲-۳ | زنجیرهی image والد، snapshot و clone؛ flatten وابستگی clone به والد را قطع میکند |
| ۲-۴ | دو حالت mirroring: در حالت journal همهی نوشتنها ثبت و بازپخش میشوند؛ در حالت snapshot تفاضل snapshotهای دورهای منتقل میشود |
| ۲-۵ | توپولوژی mirroring: rbd-mirror در site-b تغییرات را از site-a میخواند؛ برای failback به rbd-mirror در site-a هم نیاز است |
| ۲-۶ | چیدمان یک تصویر فرمت ۲ در RADOS. ردیف بالا فهرستهای سطح pool را نشان میدهد: در omap شیء rbd_directory کلیدهای name_ و id_ نام و شناسه را به هم نگاشت میکنند. ردیف میانی اشیای فرادادهی هر تصویر است: rbd_id.<name> شناسه را نگه میدارد، و rbd_header.<id> اندازه، order، features، object_prefix، snapshotها و والد را در omap ذخیره میکند و watcherها روی همین شیء ثبت میشوند. object map برای هر شیء داده دو بیت حالت دارد. ردیف پایین اشیای داده است که نامشان از object_prefix و یک عدد hex شانزدهرقمی ساخته میشود؛ فقط اشیایی ساخته میشوند که واقعاً نوشته شده باشند (thin provisioning) |
| ۲-۷ | انتقال exclusive lock بین دو کلاینت. B ابتدا مالک فعلی را میخواند و اگر قفل گرفته شده باشد، از طریق watch/notify روی header درخواست قفل میفرستد. اگر A سالم باشد، طبق policy (پیشفرض: آزادسازی خودکار) کش را flush و قفل را رها میکند. اگر A دیگر watch نداشته باشد، B قفل را میشکند و با rbd_blocklist_on_break_lock=true (پیشفرض) آدرس A را blocklist میکند تا نوشتنهای دیررسیدهی A پذیرفته نشوند. سپس B قفل را میگیرد و در PostAcquire، object map و journal را باز میکند |
| ۲-۸ | زنجیرهی شیءهای یک image با قالب ۲. ردیف اول ساختن id از instance id و یک عدد تصادفی است. ردیف دوم نگاشت نام به id در rbd_id و rbd_directory، و سرآیند image است. ردیف سوم object map، شیءهای دادهای که با اولین نوشتن ساخته میشوند، و ورودی trash هنگام حذف است. id نمونه نمادین است |
| ۲-۹ | پنج درخواست discard روی سه شیء چهارمگابایتی. نوار خاکستری بالای هر ردیف بازهی درخواستی است. قرمز remove، نارنجی truncate، آبی zero و هاشور «رد شده» است. گرد شدن به ۶۴ KiB باعث میشود discardهای کوچکتر از یک واحد کامل حذف شوند |
| ۲-۱۰ | مسیر تصمیم discard در librbd: تبدیل به extentهای شیء، هرس با واحد ۶۴ KiB (مگر اینکه skip_partial_discard خاموش باشد یا cache کثیف وجود داشته باشد) و انتخاب remove، truncate یا zero. برای clone با parent، حذف کامل به truncate تبدیل میشود تا دادهی parent دوباره پیدا نشود |
| ۲-۱۱ | USED اندازهگیریشده روی کلاستر آزمایشی Ceph 18.2.7 برای چهار بار کاری. خاکستری بایتهای واقعاً نوشتهشده است، آبی rbd du --exact و نارنجی rbd du پیشفرض (شمارش کل شیء از object map). محور عمودی لگاریتمی است. در بار تصادفی D، --exact هم حدود دویست برابر دادهی نوشتهشده را نشان میدهد، چون هر شیء را از بایت ۰ تا بالاترین بایت نوشتهشده میشمارد |
| ۲-۱۲ | ترتیب گامهای switchover، failover اجباری و failback |
| ۳-۱ | نگاشت سرویسهای OpenStack به اجزای Ceph: هر سرویس pool یا رابط اختصاصی خودش را دارد |
| ۳-۲ | توزیع کاربران و keyringها: سرویسهای کنترلی هر کدام کاربر خودشان را دارند و nova-compute از طریق secret در libvirt به کلید client.cinder دسترسی پیدا میکند |
| ۳-۳ | copy-on-write سرتاسری: هم volume در Cinder و هم دیسک موقت Nova clone همان snapshot در Glanceاند |
| ۳-۴ | مسیر بوت از volume: Cinder volume را با clone میسازد و nova-compute از طریق libvirt و librbd آن را به QEMU میدهد |
| ۳-۵ | الگوی رایج OpenStack روی RBD. ردیف بالا نشان میدهد که Glance تصویر را با یک snapshot ذخیره میکند. در clone v1 این snapshot باید protect شود؛ در clone v2 (پیشفرض rbd_default_clone_format=auto، در صورتی که min_compat_client حداقل mimic باشد) این کار لازم نیست و snapshot والد حتی با وجود فرزند قابل حذف است و به trash میرود. ردیف میانی صدها volume را نشان میدهد که بدون کپی داده از یک snapshot ساخته شدهاند. ردیف پایین پیامدهای عملیاتی است: خواندن ناحیههای نوشتهنشده از والد سرویس داده میشود، اولین نوشتن روی هر شیء یک copyup به اندازهی کل شیء ایجاد میکند، و flatten وابستگی را قطع ولی کل فضا را مصرف میکند |
| ۳-۶ | مسیر نوشتن روی یک تصویر clone. اگر شیء فرزند وجود داشته باشد، نوشتن مستقیم است. در غیر این صورت، CopyupRequest کل محدودهی شیء را از snapshot والد میخواند، در صورت وجود deep-flatten و snapshotهای فرزند داده را به آنها هم کپی میکند (deep_copy)، object map را بهروز میکند و در نهایت یک عملیات اتمیک واحد روی OSD میفرستد. این عملیات شامل متد cls به نام rbd.copyup یا sparse_copyup است که دادهی والد را فقط در صورت نبود شیء مینویسد، و به دنبال آن خود write کاربر میآید. اتمیک بودن این ترکیب تضمین میکند که هیچ خوانندهای شیء نیمهکپیشده نبیند |
| ۳-۷ | نگاشت یک نوشتن ۱ مگابایتی از offset ۱۰.۵ MiB به شیءهای RADOS در دو چیدمان. بالا: stripe_count = ۱ (پیشفرض)، که کل نوشتن در شیء شمارهی ۲ و از offset ۰x280000 قرار میگیرد. پایین: stripe_unit = ۶۴K و stripe_count = ۴، که همان نوشتن به ۱۶ قطعهی ۶۴K تقسیم و به نوبت میان چهار شیء پخش میشود. شمارهی قطعههای بافر روی هر شیء نوشته شده است. همهی قطعهها در «مجموعهی شیء» صفر هستند، چون ۴۲ تا ۴۵ کمتر از ۶۴ stripe در هر شیء است |
| ۳-۸ | اندازهی object map برحسب اندازهی image، برای سه اندازهی شیء، روی محورهای لگاریتمی. هر شیء دو بیت میگیرد، پس اندازهی map با اندازهی image نسبت خطی دارد. خطچینهای عمودی بزرگترین image سازگار با object map برای هر اندازهی شیء هستند، یعنی جایی که تعداد شیءها به ۲۵۶ میلیون میرسد. نقطهی سیاه volume یک ترابایتی معمولی با شیء ۴ MiB است که فقط ۶۴ KiB map دارد |
| ۳-۹ | ماشین حالت یک خانهی object map. خانهها در آغاز NONEXISTENT هستند. پیش از نخستین نوشتن به EXISTS میروند، چون librbd map را قبل از داده بهروز میکند تا پس از خرابی کلاینت، شیءِ موجودی «ناموجود» علامت نخورد. هنگام ساخت snapshot همهی خانههای EXISTS در map فعلی (HEAD) به EXISTS_CLEAN تبدیل میشوند. هر نوشتن بعدی خانه را دوباره EXISTS میکند. حذف دو مرحله دارد: ابتدا PENDING، سپس پس از حذف موفق NONEXISTENT |
| ۳-۱۰ | جدول حالت محاسبهی diff برای شش شیء نمونه از snap1 تا HEAD. هر خانه وضعیت شیء در آن object map را نشان میدهد (نارنجی EXISTS، آبی EXISTS_CLEAN، خاکستری NONEXISTENT) و زیر آن حالت diff پس از پردازش همان map آمده است. ستون آخر نتیجه است: قرمز یعنی دادهی تغییرکرده که باید منتقل شود، بنفش یعنی ناحیهای که باید صفر گزارش شود، سبز و سفید یعنی بدون تغییر. o2 نشان میدهد چرا HOLE در snapshot میانی تغییر نمیکند: نتیجه فقط به وضعیت در map آخر بستگی دارد |
| ۳-۱۱ | سمت چپ: تعداد IO پذیرفتهشده در هر ثانیه برای یک کلاینت اشباع، در سه پیکربندی. بدون burst نرخ ثابت ۱۰۰ است. با burst ۳۰۰ و burst_seconds ۲، نرخ حدود سه ثانیه ۳۰۰ میماند و بعد به ۱۰۰ برمیگردد. اگر کلاینت پیش از آن سه ثانیه بیکار بوده باشد، ثانیهی نخست ۵۸۵ IO میگیرد. سمت راست: توکنهای افزودهشده در هر tick برای سه مقدار avg. پخش صحیح avg میان tickها، جمع هر ثانیه را دقیق نگه میدارد. این عددها از بازپیادهسازی پایتونی TokenBucketThrottle با فرض تقاضای پیوسته به دست آمدهاند، نه از اندازهگیری روی کلاستر |
| ۴-۱ | معماری RGW: نمونههای بدون حالت پشت load balancer و poolهای جداگانه برای index، داده و فراداده |
| ۴-۲ | یک شیء S3 بیستمگابایتی در RADOS: شیء head با فراداده و manifest، اشیای tail برای بقیهی داده، و یک ورودی در shard مربوط به index |
| ۴-۳ | دو سازوکار چندمستاجری: tenant برای جداسازی فضای نام bucketها و account (از Squid) برای مدیریت سلفسرویس کاربران و سیاستها به سبک IAM |
| ۴-۴ | جریان احراز هویت Swift با Keystone: RGW توکن را نزد Keystone اعتبارسنجی میکند و برای هر پروژه خودکار کاربر میسازد |
| ۴-۵ | مسیر PUT یک شیء. radosgw شمارهی شارد را از hash نام شیء محاسبه میکند. ابتدا متد cls به نام bucket_prepare_op یک ورودی با حالت PENDING_MODIFY و یک tag یکتا در شارد ثبت میکند. سپس head (شامل حداکثر ۴MiB اول داده و xattrهای manifest، ACL و etag) و stripeهای tail نوشته میشوند. در پایان bucket_complete_op ورودی را با اندازه، etag و mtime به حالت COMPLETE میبرد و یک رکورد bilog برای همگامسازی multisite میسازد. اگر RGW بین این دو مرحله از کار بیفتد، ورودی pending باقی میماند و بعداً با dir_suggest_changes (هنگام list) یا radosgw-admin bucket check اصلاح میشود |
| ۴-۶ | چیدمان فیزیکی. در pool index، هر bucket به N شیء .dir.<marker>.<shard> تقسیم میشود و ورودیها در omap آنها نگهداری میشوند. در pool داده، head با نام <marker>_<key> و tailها با پیشوند __shadow_ ذخیره میشوند. در multipart هر part یک شیء __multipart_ جداست و CompleteMultipartUpload فقط manifest را میسازد و داده را کپی نمیکند. tail اشیای حذفشده یا بازنویسیشده بهصورت ناهمگام در صف GC (کلاس rgw_gc) پاک میشوند |
| ۴-۷ | سمت چپ: پخش ۱۰۰۰۰۰ کلید پشتسرهم میان ۱۱ shard ایندکس. خطچین میانگین است و همهی shardها در حدود یک درصد آن هستند. سمت راست: سه کلید نمونه گامبهگام از hash تا شمارهی shard، هم برای ۱۱ و هم برای ۲۳ shard. مرحلهی XOR بایت پایین hash را به بایت بالا میبرد، چون باقیماندهی تقسیم بر عدد اول کوچک بیشتر به بیتهای پایین حساس است |
| ۴-۸ | تعداد shard ایندکس برحسب تعداد شیء در bucket، روی محورهای لگاریتمی. آبی مسیر یک zone با ضریب ۲ است و قرمز مسیر multisite با ضریب ۸. هر پله یک reshard است و عدد کنار آن تعداد shard تازه (عدد اول) است. خط نقطهچین سقف ۱۰۰۰۰۰ شیء در هر shard است. هر بار که bucket از آن عبور کند، reshard صف میشود. سقف ۱۹۹۹ در هر دو مسیر به تعداد شیء متفاوتی میرسد |
| ۴-۹ | تعداد کل ورودیهای ایندکسی که برای ساختن یک صفحهی فهرست مرتب درخواست میشود، برحسب تعداد shard، برای max-keys برابر ۱۰۰۰ و ۱۰۰. خط نقطهچین همرنگ تعداد واقعی کلیدهای صفحه است. با shard کم، اضافهخوانی کم است. از جایی که n / s زیر ۸ میرود، حداقل ۸ غالب میشود، دو منحنی بر هم منطبق میشوند و هزینه با تعداد shard خطی بالا میرود. عددها فقط تلاش اول را نشان میدهند، پیش از گسترش ضریب |
| ۴-۱۰ | بالا: یک PUT سادهی ۱۰ مگابایتی. head (زرد) چهار مگابایت اول و فراداده را دارد و دو stripe در namespace shadow (آبی) با پیشوند تصادفی ۳۲ نویسهای بقیه را دارند. پایین: آپلود multipart با دو قطعهی ۱۰ مگابایتی. head بدون داده است و فقط manifest را دارد. هر قطعه یک شیء در namespace multipart (قرمز) برای ۴ مگابایت اول خود و دو stripe در namespace shadow دارد. نامها از get_implicit_location گرفته شدهاند. marker و upload id نمادین هستند |
| ۴-۱۱ | placement و storage class: دادهی تازه در کلاس STANDARD و پس از مدتی با lifecycle به کلاس سرد EC یا ذخیرهسازی ابری بیرونی منتقل میشود |
| ۵-۱ | سلسلهمراتب multisite: realm شامل zonegroupها و هر zonegroup شامل zoneهایی است که هر کدام در یک کلاستر Ceph جداگانه قرار دارند |
| ۵-۲ | از federated در Hammer به multisite نسخهی ۲: همگامسازی از یک agent بیرونی به درون خود radosgw منتقل شد و همهی zoneها نوشتنی شدند |
| ۵-۳ | گامهای راهاندازی: ساخت realm، zonegroup و master zone در کلاستر اصلی، سپس pull کردن realm و ساخت zone ثانوی در کلاستر دوم |
| ۵-۴ | سازوکار همگامسازی: zone مقصد datalog و bilog مبدأ را میخواند و اشیای تغییرکرده را با GET از endpoint مبدأ میکشد؛ فراداده از metadata log در master zone میآید |
| ۵-۵ | جریان کامل multisite. در zone مبدأ، هر PUT یا DELETE یک ورودی bilog در شارد index میسازد و یک ورودی datalog اعلام میکند که «آن شارد bucket تغییر کرده است». پنجرهی ۳۰ ثانیهای datalog تغییرات پیاپی روی یک شارد را ادغام میکند تا لاگ منفجر نشود. ساخت bucket یا کاربر در mdlog ثبت میشود که فقط master zone آن را تولید میکند. در zone مقصد سه لایه coroutine کار میکنند: meta sync، data sync برای هر شارد datalog، و bucket sync برای هر شارد bucket که bilog را میخواند. لایهی پایانی شیء را با fetch_remote_obj از مبدأ میکشد، محلی مینویسد و marker را در شیء وضعیت ذخیره میکند |
| ۵-۶ | ماشین حالت همگامسازی داده. ابتدا اشیای وضعیت ساخته میشوند (Init)، سپس همهی bucketهای مبدأ در full-sync map فهرست میشوند (BuildingFullSyncMaps)، و پس از آن هر شارد datalog مستقل از بقیه ابتدا full sync و بعد incremental sync را با poll بیستثانیهای اجرا میکند. خطاها در error repo ثبت و بعداً دوباره تلاش میشوند. اگر چند RGW در zone مقصد باشند، هر شارد با یک lease دودقیقهای فقط به یکی از آنها سپرده میشود. trim لاگهای مبدأ هر ۲۰ دقیقه و فقط تا کمترین markerی انجام میشود که همهی peerها به آن رسیدهاند |
| ۵-۷ | بالا: سهم shardهای datalog از ۱۱۰۰۰ bucket shard (۱۰۰۰ bucket با نام شمارهدار و ۱۱ shard برای هر کدام). فقط ۸۸ shard از ۱۲۸ shard کار دارند، در دستههایی ۱۱تایی که از ۸، ۲۴، …، ۱۲۰ شروع میشوند. پایین: همین تعداد bucket با نام تصادفی که همهی ۱۲۸ shard را میپوشاند. خطچین میانگین ایدهآل، یعنی حدود ۸۶ bucket shard برای هر shard datalog است. شبیهسازی ما، بر پایهی بازپیادهسازی choose_oid |
| ۵-۸ | بالا: یک bucket shard پرکار با ۱۲۰۰۰ نوشتن در ۱۲۰ ثانیه فقط شش ورودی datalog تولید میکند. اولی در لحظهی نخستین نوشتن ثبت میشود و بقیه در تیکهای ۲۲ ثانیهای renew. پایین: با نوشتن پراکنده، renew فقط وقتی انجام میشود که در پنجرهی قبل نوشتنی مانده باشد. نوشتن ثانیهی ۱۰۰ پس از انقضای پنجره بلافاصله ورودی میسازد |
| ۵-۹ | درخت تصمیم add_entry در main. ردیف اول مسیر ایمن را نشان میدهد: اگر watch روی sem-set قطع باشد، ورودی بیدرنگ نوشته میشود. ردیف دوم ادغام در پنجره و انتظار برای push در جریان است. ردیف سوم push واقعی و چرخهی renew است که پنجره را تمدید میکند و شمارندهها را کم میکند |
| ۵-۱۰ | چپ: shard mdlog برای ۱۰۰۰ کلید bucket با نام شمارهدار که فقط چهار shard را اشغال میکنند. راست: ۱۰۰۰ نام تصادفی که هر ۶۴ shard را میپوشانند، با ۷ تا ۳۱ کلید در هر shard. محاسبه با بازپیادهسازی rgw_shard_name |
| ۵-۱۱ | چرخهی trim datalog. هر ۲۰ دقیقه وضعیت همگامسازی از همهی peerها گرفته میشود. اگر یکی پاسخ ندهد، آن دور trim انجام نمیشود. برای هر shard کمترین marker پایدار حساب میشود (در full sync، next_step_marker) و فقط وقتی trim انجام میشود که از trim قبلی جلوتر باشد. ردیف پایین یک مثال نمادین است |
| ۵-۱۲ | تصمیمگیری هنگام از دست رفتن master zone: در خرابی کوتاه صبر کنید؛ در خرابی طولانی zone دیگری را master کنید |
| ۶-۱ | معماری CephFS: کلاینت فراداده را از MDS و داده را مستقیم از OSDها میگیرد؛ MDS journal و inodeها را در pool فراداده نگه میدارد |
| ۶-۲ | فراخوانی capability: وقتی کلاینت دوم فایل را باز میکند، MDS از کلاینت اول میخواهد دادهی bufferشده را flush کند و مجوزش را آزاد کند |
| ۶-۳ | چند MDS فعال: زیردرختها با pin صریح یا pin توزیعشده بین rankها تقسیم میشوند |
| ۶-۴ | چهار راه دسترسی به CephFS: کلاینت هسته، ceph-fuse، NFS از طریق Ganesha و SMB از طریق Samba |
| ۶-۵ | رمزگشایی رشتهی cap. ردیف اول پیشوندها را نشان میدهد: p برای pin، A برای Auth (مالکیت و مجوز)، L برای Link، X برای Xattr و F برای File (محتوا و اندازه). ردیف دوم بیتهای عمومی است که روی هر پیشوند اعمال میشوند: s برای خواندن مشترک، x برای انحصاری، c برای کش، r و w برای خواندن و نوشتن، b برای بافر نوشتن، a برای گسترش EOF و l برای lazyio. ردیف سوم سه حالت را نشان میدهد: خوانندهی عادی، تنها نویسنده با کش و بافر کامل، و دو نویسندهی همزمان روی یک فایل که MDS از آنها c و b را پس میگیرد. در حالت آخر همهی I/O بهصورت همگام به OSD میرود؛ این دلیل اصلی کندی برنامههایی است که همزمان از چند گره روی یک فایل مینویسند |
| ۶-۶ | چرخهی عمر یک عملیات فراداده. در ردیف اول، درخواست کلاینت در MDS قفلهای لازم (SimpleLock، ScatterLock و...) را میگیرد، کش را بهروز میکند و رویداد را در MDLog ثبت میکند. در ردیف دوم، رویدادها در segmentهای ۱۰۲۴ رویدادی جمع میشوند و MDS سعی میکند حداکثر ۱۲۸ segment نگه دارد. با expire شدن segment، dirfragها در omap نوشته میشوند. اگر trim عقب بماند، هشدار MDS_TRIM_BEHIND ظاهر میشود. ردیف سوم توالی حالتها پس از شکست یک rank است: replay، resolve (فقط در چند rank فعال)، reconnect (کلاینتها capهای خود را اعلام میکنند)، rejoin، clientreplay و در نهایت active. standby-replay با دنبال کردن پیوستهی journal، زمان replay را کوتاه میکند |
| ۶-۷ | چیدمان یک فایل ۱۰ مگابایتی. چپ: layout پیشفرض، با هر ۴ مگابایت پشتسرهم در یک شیء. راست: su برابر ۱ MiB و sc برابر ۴، که مگابایتهای پیاپی را بهصورت چرخشی بین چهار شیء پخش میکند. نام شیء ino به hex و شمارهی شیء به hex هشترقمی است. پیکان جای بایت ۹ MiB را در هر دو حالت نشان میدهد |
| ۶-۸ | فضای hash نامها در یک پوشه. ردیف بالا frag ریشه است، ردیف میانی هشت frag پس از تقسیم سهبیتی با _enc و تعداد نامهای هر کدام، و ردیف پایین تقسیم دوبارهی frag ۰۱۰ به هشت frag ششبیتی. خطهای رنگی جای hash سه نام نمونه را نشان میدهند. محور افقی ۲۴ بیت پایین hash است |
| ۶-۹ | تعداد dirfrag (آبی، پلهای) و اندازهی بزرگترین frag (قرمز) هنگام ساخت ۱۰۰۰۰۰ ورودی در یک پوشه، با فرض تقسیم بیدرنگ. خطچین آستانهی ۱۰۰۰۰ و نقطهچین آستانهی تقسیم سریع ۱۵۰۰۰ است. پس از تقسیم اول، اندازهی بزرگترین frag به حدود ۱۲۵۰ میافتد و دوباره تا ۱۰۰۰۰ بالا میرود. شبیهسازی ما با rjenkins و منطق frag_t |
| ۶-۱۰ | منحنی شمارندهی IWR یک dirfrag زیر بار نوشتن پایدار، با نیمهعمر ۵ ثانیه. هر منحنی به مقدار پایدار r × ۷.۲۱ میل میکند. خطچین آستانهی mds_bal_split_wr است و نقطهها لحظهی رد شدن آن را نشان میدهند. منحنی ۱۰۰۰ نوشتن بر ثانیه هرگز به آستانه نمیرسد |
| ۶-۱۱ | درخت تصمیم تقسیم و ادغام dirfrag. ردیف اول دو محرک تقسیم است: اندازه و داغی. ردیف دوم صفبندی عادی با تأخیر ۵ ثانیه یا مسیر سریع بالای ۱.۵ برابر آستانه است. ردیف سوم تقسیم به ۸ فرزند، ادغام fragهای زیر ۵۰ ورودی و سقف سخت ۱۰۰۰۰۰ را نشان میدهد |
| ۶-۱۲ | سلسلهمراتب volume، subvolumegroup و subvolume؛ درایور ceph-csi و Manila هر PVC یا share را به یک subvolume نگاشت میکنند |
| ۷-۱ | لایههای مدیریت daemon در cephadm: از دستور ceph orch تا کانتینر روی میزبان |
| ۷-۲ | منابع پیکربندی و ترتیب اولویت آنها؛ مقدار مؤثر هر daemon را با ceph config show ببینید |
| ۷-۳ | دو راه افزایش ظرفیت: دیسک بیشتر در همان گرهها یا گرههای بیشتر |
| ۷-۴ | روال امن تعویض دیسک: اول بازیابی، بعد بررسی safe-to-destroy، سپس حذف با حفظ id و تعویض فیزیکی |
| ۷-۵ | ترتیب ارتقا در cephadm: اول MGR و MON، سپس OSD و MDS، در پایان دروازهها |
| ۷-۶ | توالی پیامهای Paxos در MON: پس از انتخابات، رهبر با OP_COLLECT شمارهی پیشنهاد جدید را اعلام و با OP_LAST مقادیر ناتمام را جمع میکند؛ سپس برای هر تغییر، OP_BEGIN با نسخهی بعدی ارسال، OP_ACCEPT از همهی اعضای quorum دریافت، OP_COMMIT اعلام و با OP_LEASE اجازهی خواندن تا مدت mon_lease به peonها داده میشود |
| ۷-۷ | مسیر یک فرمان در یک سرویس Paxos مانند OSDMonitor: فرمانهای فقطخواندنی در preprocess_query از نقشهی فعلی پاسخ داده میشوند؛ فرمانهای تغییردهنده در prepare_update روی pending incremental اعمال، با تأخیر paxos_propose_interval دستهبندی و با propose_pending به Paxos پیشنهاد میشوند؛ پس از commit، نقشه بارگذاری، incremental به مشترکان منتشر و به فرماندهنده پاسخ داده میشود |
| ۷-۸ | زمانبندی یک OSD خاموش با پیشفرضها. پس از ۲۰ ثانیه بیپاسخی، همتایان روی دو میزبان گزارش میدهند و MON آن را down میکند. PGها degraded میشوند، اما داده جابهجا نمیشود. پس از ۶۰۰ ثانیه (بهعلاوهی جملهی laggy) OSD out میشود و backfill شروع میشود. خط بنفش استثناها را نشان میدهد: پرچم noout و down بودن کل یک rack (mon_osd_down_out_subtree_limit) که شمارنده را صفر میکند |
| ۷-۹ | چپ: رشد laggy_probability و laggy_interval با هر down اشتباه (OSD زنده که ۴۰ ثانیه بعد برمیگردد)، با وزن ۰.۳. راست: grace مؤثر شکست بعدی، یعنی ۲۰ ثانیهی پایه بهعلاوهی p × I. جملهی همتایان در این مثال صفر فرض شده است. اگر down بعدی بیش از ۴۸ ساعت بعد باشد، سابقه صفر میشود |
| ۷-۱۰ | چپ: درصد استفادهی دوازده OSD، با یک OSD چهار ترابایتی که ۸۰٪ پر است (قرمز). راست: برای هر OSD، دادهی خامی که میتوان نوشت تا همان OSD به full_ratio برسد. کمینهی آنها (خط قرمز) پایهی MAX AVAIL است و بر size یا (k + m) / k تقسیم میشود |
| ۷-۱۱ | نردبان نسبتهای پری با پیشفرضها: هشدار NEARFULL از ۸۵٪، توقف backfill به سمت OSD از ۹۰٪، پرچم FULL و توقف نوشتن کلاینتها از ۹۵٪، و failsafe خود OSD از ۹۷٪. سه نسبت اول در OSDMap نگهداری میشوند و با set-*-ratio تغییر میکنند. failsafe یک گزینهی پیکربندی OSD است |
| ۸-۱ | دو مرحلهی جاشناسی: نام شیء با hash به یک PG نگاشت میشود و CRUSH آن PG را به مجموعهای از OSDها نگاشت میکند |
| ۸-۲ | یک سلسلهمراتب CRUSH نمونه: root، datacenter، rack، host و OSDها با کلاس دستگاه |
| ۸-۳ | نقشههای کلاستر در MONها نگهداری و با epoch نسخهگذاری میشوند؛ کلاینتها و OSDها نقشهی جدید را از MON یا از یکدیگر میگیرند |
| ۸-۴ | سه مرحلهی نگاشت شیء به OSD: در کلاینت، نام شیء با ceph_str_hash درهمسازی و با ceph_stable_mod به شمارهی PG تبدیل میشود؛ سپس شمارهی PG با شناسهی pool ترکیب و بهعنوان ورودی x به crush_do_rule داده میشود؛ در پایان OSDMap OSDهای ناموجود را حذف و upmap و pg_temp را اعمال میکند تا acting set و primary به دست آید |
| ۸-۵ | الگوریتم straw2 در سه مرحله: برای هر آیتم bucket یک عدد شبهتصادفی شانزدهبیتی از hash ورودی، شناسهی آیتم و شمارهی تلاش ساخته و لگاریتم طبیعی آن با جدول محاسبه و بر وزن تقسیم میشود؛ آیتمی با بیشترین مقدار انتخاب میشود که از نظر ریاضی معادل انتخاب کمینه از متغیرهای نمایی با نرخ برابر وزن است و احتمال انتخاب هر آیتم را دقیقاً متناسب با وزن آن میکند؛ پیامد آن حداقل جابهجایی داده هنگام تغییر وزن است |
| ۸-۶ | جریان احراز هویت cephx: کلاینت با اثبات دانستن کلید مشترک از MON کلید جلسه و بلیت احراز میگیرد، سپس برای سرویس OSD بلیت سرویس رمزشده با کلید چرخشی آن سرویس دریافت میکند؛ OSD که کلیدهای چرخشی را از MON گرفته است، بلیت را رمزگشایی و انقضا و capها را بررسی میکند و پاسخ میدهد تا احراز دوطرفه کامل شود |
| ۸-۷ | ساختار قاب در پروتکل msgr2: preamble با اندازهی ثابت شامل برچسب نوع قاب، تعداد segmentها تا چهار و طول و همترازی هر segment و CRC جداگانه؛ سپس حداکثر چهار segment شامل سرآیند، front، middle و data؛ و در پایان epilogue که در حالت crc شامل CRC هر segment و در حالت secure رمز و احراز صحت AES-GCM کل قاب است |
| ۸-۸ | افزودن میزبان یازدهم (۱۲ OSD) به کلاستر ۱۰ × ۱۲ با straw2، pool سهنسخهای و ۸۱۹۲ PG، اندازهگیریشده با osdmaptool. خاکستری سهم ایدهآل است، سبز replicaهایی که به میزبان جدید رفتند و نارنجی جابهجایی بین OSDهای قدیمی که از عوض شدن r جایگاه در chooseleaf ناشی میشود |
| ۸-۹ | تغییر وزن CRUSH یک OSD (osd.۵ روی host0) از ۱.۰ به پنج مقدار و دستهبندی جایگذاریهای جابهجاشده: آبی خود osd.5، زرد دیگر OSDهای host0 که با تغییر وزن میزبان سهم تازه میگیرند یا از دست میدهند، قرمز میزبانهای نامربوط. چپ straw2 و راست straw قدیمی، با ۸۱۹۲ PG و اندازهگیری osdmaptool |
| ۸-۱۰ | جریان cephx: کلاینت هویتش را به MON ثابت میکند، ticket میگیرد و با آن مستقیماً با OSD کار میکند |
| ۸-۱۱ | نوشتن در pool تکثیری: کلاینت فقط با primary صحبت میکند؛ primary بهطور موازی به ثانویهها تکثیر میکند و پس از commit همهی نسخهها ack میدهد |
| ۸-۱۲ | حالتهای اصلی PG و گذارهای رایج بین آنها |
| ۹-۱ | دو نسل ابزار مدیریت گرافیکی Ceph: ابزارهای مستقل (Calamari، VSM، openATTIC) و Dashboard درون MGR |
| ۹-۲ | اجزای Dashboard: ماژول MGR، Grafana و Prometheus برای نمودارها، و در Tentacle دروازهی nginx و oauth2-proxy بهعنوان نقطهی ورود واحد |
| ۹-۳ | کنترل دسترسی مبتنی بر نقش در Dashboard: هر کاربر یک یا چند نقش دارد و هر نقش مجموعهای از مجوزها روی scopeهای مشخص است |
| ۹-۴ | کاربرد Dashboard در چرخهی عمر کلاستر: استقرار، پیکربندی سرویسها و کار روزانهی پایش و نگهداری |
| ۹-۵ | معماری ceph-mgr. ردیف بالا بخش C++ است. MgrStandby به MON beacon میفرستد و وقتی MON آن را active اعلام کند، Mgr نقشهها را مشترک میشود و ClusterState را نگه میدارد. DaemonServer گزارشهای دورهای (MMgrReport) و perf counterها را از همهی daemonها دریافت میکند. ردیف میانی میزبانی پایتون است: PyModuleRegistry ماژولها را کشف و بارگذاری میکند، ActivePyModules هر ماژول را در یک sub-interpreter جدا با thread مخصوص serve() اجرا میکند، و BaseMgrModule پل C++ به پایتون برای get، mon_command و set_store است. ردیف پایین ساختار یک ماژول را نشان میدهد: کلاس Module که از MgrModule ارث میبرد، با گزینهها، نوع اعلانهای موردنیاز، فرمانهای CLI از طریق decorator و حلقهی serve |
| ۹-۶ | اعتبار پیچیدگی چهار رمز نمونه. هر خانه یک نویسه است و رنگ آن دستهی نویسه و پهنایش اعتبار آن را نشان میدهد. خطچین حداقل ۱۰ است. رمز سوم با وجود اعتبار کافی به خاطر سه حرف پیاپی رد میشود. رمز چهارم نشان میدهد هر حرف غیر ASCII پنج اعتبار دارد. علامت سؤال جای حرف فارسی است |
| ۹-۷ | دو مرحلهی رسیدگی به یک درخواست REST در Dashboard. ردیف بالا احراز هویت با JWT است: امضا، زمان انقضا و نبود شناسهی توکن در فهرست توکنهای باطلشده. ردیف پایین مجوز است: scope کنترلر و مجوز متد HTTP باید هر دو در یکی از نقشهای کاربر وجود داشته باشند. ترکیب مجوزهای دو نقش متفاوت برای یک درخواست کافی نیست |
| ۹-۸ | خط زمانی یازده تلاش ورود با ACCOUNT_LOCKOUT_ATTEMPTS = ۱۰. هر تلاش ناموفق شمارنده را یکی بالا میبرد. شرط قفل پیش از سنجش رمز بررسی میشود. بنابراین تلاش یازدهم، با هر رمزی، حساب را غیرفعال میکند و فقط مدیر با ac-user-enable میتواند آن را باز کند |
| ۹-۹ | زمان از نوشتن یک شیء تا وقتی که pg dump آن را نشان میدهد، برای چهار مقدار mgr_stats_period. چپ: توزیع تجمعی ۱۵ آزمایش. راست: جعبهنمودار همراه با خطهای y = p و y = ۲p. اندازهگیری روی Ceph 18.2.7 |
| ۹-۱۰ | اندازهی پاسخ JSON (چپ) و میانهی زمان (راست) برای pg dump کامل، pg dump pgs_brief و status در کلاسترهایی با ۳۲ تا ۱۰۲۴ PG. اندازهگیری روی Ceph 18.2.7 |
| ۱۰-۱ | سنجش لایهبهلایه: هر لایه را فقط پس از شناختن سقف لایهی زیرین بسنجید |
| ۱۰-۲ | سه روش اجرای fio روی Ceph: موتور rbd مستقیم با librbd، دستگاه بلوکی نگاشتشده و فایل روی CephFS |
| ۱۰-۳ | دو مسیر ارتباط با daemon: ceph tell از طریق شبکه و ceph daemon از طریق admin socket محلی |
| ۱۰-۴ | سه سازوکار سنجش در کد Ceph. در ردیف بالا، osd bench در OSD::run_osd_bench_test ابتدا محدودیتها را بررسی میکند. اندازهی بلوک نباید از ۶۴MiB بیشتر باشد. برای بلوکهای کوچک، کل حجم به ۱۰۰ IOPS ضرب در ۳۰ ثانیه محدود است و برای بلوکهای بزرگ به ۱۰۰MiB/s ضرب در ۳۰ ثانیه، تا آزمون OSD را در محیط عملیاتی از کار نیندازد. سپس کلاس OSDBenchTest (تعریفشده در OSD.h) تراکنشهای نوشتن را مستقیم روی ObjectStore در collection متادیتا اجرا میکند؛ نه شبکه در کار است، نه PG و نه تکثیر. در ردیف میانی، rados bench از کلاس ObjBencher در common/obj_bencher.cc استفاده میکند: اشیای benchmark_data_<host>_<pid>_objectN را با ۱۶ عملیات همزمان پیشفرض مینویسد، متادیتای اجرا را در benchmark_last_metadata ذخیره میکند و status_printer هر ثانیه پهنای باند و تأخیر لحظهای را چاپ میکند. در ردیف پایین، OSD هنگام بوت (اگر benchmark غیرفعال نشده باشد) یک آزمون کوتاه ۴K اجرا میکند، نتیجه را با آستانههای معقول نوع دستگاه مقایسه میکند و در صورت معتبر بودن، osd_mclock_max_capacity_iops_hdd یا _ssd را در config MON ثبت میکند |
| ۱۰-۵ | سقف count پذیرفتنی برای osd bench برحسب اندازهی بلوک، هر دو محور لگاریتمی. زیر یک مگابایت سقف با فرض ۱۰۰ IOPS در ۳۰ ثانیه خطی رشد میکند. از یک مگابایت به بعد با فرض ۱۰۰ مگابایت بر ثانیه ثابت میماند. ستاره پارامترهای آزمون بوت mClock است که دقیقاً روی سقف قرار دارد. بالاتر از ۶۴ مگابایت هر اندازهی بلوکی رد میشود |
| ۱۰-۶ | بازهی پذیرش نتیجهی آزمون بوت برای HDD و SSD روی محور لگاریتمی. ناحیهی سبز بازهی میان آستانهی پایین و بالاست و خط آبی مقدار پیشفرض. نقطههای سبز ذخیره میشوند. نقطههای قرمز، مثل HDD با کش نوشتن یا NVMe بسیار سریع، رد میشوند و مقدار قبلی میماند |
| ۱۰-۷ | رزرو (ستون پررنگ) و سقف (ستون کمرنگ) سه کلاس سرویس در سه پروفایل داخلی mClock، برحسب op کوچک در ثانیه برای HDD با پیشفرضها. وزن هر کلاس سهم آن از ظرفیت باقیمانده پس از رزروهاست. سقف «max» یعنی محدودیتی جز ظرفیت دستگاه ندارد. اعداد از ضرب نسبتهای پروفایل در ۱۵۰ مگابایت بر ثانیه و تقسیم بر هزینهی هر IO به دست آمدهاند |
| ۱۰-۸ | چپ: IOPS و تأخیر میانگین نوشتن ۴ KiB برحسب تعداد عملیات در پرواز (rados bench -t) روی کلاستر آزمایشی. راست: حاصلضرب IOPS اندازهگیریشده در تأخیر میانگین که در همهی نقطهها T را بازمیسازد (قانون Little برای سامانهی حلقهبسته) |
| ۱۰-۹ | پهنای باند (نارنجی) و IOPS (آبی) نوشتن برحسب اندازهی شیء با ۱۶ عملیات در پرواز روی همان کلاستر آزمایشی، همراه با تأخیر میانگین هر نقطه. زیر حدود ۶۴ KiB سقف IOPS حاکم است و بالای حدود ۲۵۶ KiB سقف پهنای باند |
| ۱۰-۱۰ | کاربرد نجات با ceph-objectstore-tool: OSD متوقف، بررسی محتوا، export یک PG و import آن روی OSD دیگر |
| ۱۱-۱ | توپولوژی آزمایشگاه پیشنهادی: سه گره Ceph و یک کلاینت روی یک میزبان KVM |
| ۱۱-۲ | ابزارهای استقرار دیروز و امروز |
| ۱۱-۳ | چرخهی تطبیق در cephadm: spec وضعیت مطلوب را تعیین میکند و orchestrator اختلاف با وضعیت واقعی را برطرف میکند |
| ۱۱-۴ | یک دور از CephadmServe.serve() به ترتیبی که در کد فراخوانی میشود. ردیف اول تازهسازی کش میزبانها و daemonها است (gather-facts، فهرست daemonها، inventory دستگاهها با ceph-volume)، و پس از آن بررسی daemonهای سرگردان و اجرای اقدامهای ناهمگام درخواستی. ردیف دوم همگرایی است: _apply_all_services برای هر spec، _apply_service را صدا میزند و HostAssignment.place() در schedule.py بر اساس placement (count، label، host_pattern، hosts) تعیین میکند کدام daemon باید اضافه یا حذف شود. ردیف سوم کارهای نگهداری است: بررسی سلامت daemonها و گواهیها، پاک کردن سرویسهای حذفشده، تشخیص OSDهای جابهجاشده و تلاش دوباره برای عملیات ناموفق. در پایان حلقه میخوابد، ولی هر فرمان orch با _kick_serve_loop آن را فوراً بیدار میکند |
| ۱۱-۵ | ترتیب قطعی میزبانها برای پنج سرویس نمونه در یک کلاستر ششمیزبانه. هر سطر خروجی get_candidates است: فهرست مرتب میزبانها که با یک مولد تصادفی با seed برابر sha1 نام سرویس به هنگ ۲³² بُر خورده است. خانههای با کادر پررنگ میزبانهایی هستند که برای یک کلاستر خالی با count دادهشده انتخاب میشوند. crash بدون count روی همهی میزبانها میرود. هر رنگ یک میزبان ثابت است تا دیده شود سرویسها روی میزبانهای متفاوتی پخش میشوند |
| ۱۱-۶ | سه مرحلهی HostAssignment.place. مرحلهی اول نامزدها را از placement میسازد و با seed نام سرویس بُر میزند. مرحلهی دوم شکافها را گسترش میدهد و هر daemon موجود را به نخستین شکاف سازگار نسبت میدهد. daemonهای فعال و رتبهدار زودتر جای خود را میگیرند تا جابهجا نشوند. مرحلهی سوم کمبود یا مازاد را جبران میکند: هنگام کاهش، ابتدا daemonهایی حذف میشوند که کنار سرویس مرتبط نیستند (مثلاً ingress بدون backend). هنگام افزایش، ابتدا میزبانهای سرویس مرتبط و سپس بقیه به ترتیب shuffle انتخاب میشوند |
| ۱۱-۷ | تقسیم خودکار NVMe میان DBهای OSD در دو میزبان نمونه. چپ: ۱۲ HDD و ۲ NVMe، شش شکاف هماندازه روی هر NVMe. راست: ۱۰ HDD و ۳ NVMe، چهار شکاف ۳۷۲.۵ گیگابایتی روی هر NVMe. NVMe سوم فقط دو شکاف پر دارد و دو شکاف هاشورخورده خالی میمانند، چون batch پس از رسیدن به تعداد OSD میایستد. اندازهی ۱۴۹۰ GiB برای VG فرضی است |
| ۱۱-۸ | ترتیب انواع daemon در ارتقای cephadm، از روی CEPH_UPGRADE_ORDER در کد main. ردیف اول و دوم انواعی هستند که image خود Ceph را دارند. mgr پیش از mon میآید، چون خود orchestrator در mgr اجرا میشود و باید نسخهی تازه را بشناسد. OSDها دستهای و پس از بررسی ok-to-stop ارتقا مییابند. ردیف سوم gatewayها و ingress هستند و ردیف آخر پشتهی پایش و mgmt-gateway که imageهای جداگانهی خود را دارند |
| ۱۱-۹ | رشد مجموعهی OSDهای قابل توقف در شبیهسازی ۴ میزبان و ۳ OSD در هر میزبان (replicated size 3، min_size 2). الگوریتم از osd.0 شروع میکند و ابتدا هممیزبانهای آن را امتحان میکند (سبز: پذیرفته). در سطح root، افزودن هر OSD از میزبان دیگر تعدادی PG را زیر min_size میبرد (قرمز، با تعداد PGهای ناامن در زیر هر OSD) و رد میشود. چون حداقل یک رد وجود داشت، الگوریتم به سطح بالاتر نمیرود و همان یک میزبان را برمیگرداند |
| ۱۱-۱۰ | Rook: operator منابع سفارشی (CRD) را به daemonهای Ceph تبدیل میکند و ceph-csi volumeها را در اختیار podها میگذارد |
| ۱۱-۱۱ | چرخهی تغییر پیشنهادی: هر تغییر spec از بازبینی، dry-run و پنجرهی تغییر میگذرد و پس از اعمال بررسی میشود |
| ۱۲-۱ | مقایسهی پشتهی FileStore و BlueStore: BlueStore فایلسیستم واسط و journal جدا را حذف میکند |
| ۱۲-۲ | اجزای درونی BlueStore: allocator برای داده، RocksDB برای فراداده و BlueFS بهعنوان فایلسیستم کوچک RocksDB |
| ۱۲-۳ | دو مسیر نوشتن در BlueStore: نوشتنهای کوچک ابتدا در WAL ثبت و بعداً اعمال میشوند؛ نوشتنهای بزرگ مستقیماً در فضای تازه نوشته میشوند |
| ۱۲-۴ | چیدمان رایج گره ترکیبی: هر NVMe چند LV برای block.db چند OSD HDD فراهم میکند |
| ۱۲-۵ | مهاجرت OSD به OSD به چیدمان جدید: تخلیه یا جایگزینی، پاکسازی، ساخت دوباره و backfill |
| ۱۲-۶ | فضای تخصیصیافته برای یک blob ۶۴ KiB برحسب طول فشردهی آن. چپ: با min_alloc_size برابر ۴K، پلهها تا ۵۶ KiB صرفهجویی واقعی میدهند و بالاتر از آن blob بدون فشردهسازی ذخیره میشود. راست: با ۶۴K، همیشه ۶۴ KiB تخصیص مییابد و فشردهسازی بیاثر است. خطچین قرمز آستانهی پذیرش است |
| ۱۲-۷ | مسیر فشردهسازی در نوشتن BlueStore: فشردهسازی blob، محاسبهی want، آزمون دومرحلهای (پیش و پس از افزودن سرآیند)، پر کردن تا min_alloc و ثبت در statfs که ستونهای USED COMPR و UNDER COMPR در ceph df از آن ساخته میشوند |
| ۱۲-۸ | نسبت فضای تخصیصیافته به اندازهی منطقی شیء برای سه مقدار min_alloc_size. برای اشیای بزرگتر از چند ده کیلوبایت اختلاف ناچیز است، اما برای اشیای چندکیلوبایتی مصرف چند ده برابر میشود |
| ۱۲-۹ | اندازهی هدف سطحهای L1 تا L4 در RocksDB با گزینههای پیشفرض BlueStore (ستونها) و مجموع تجمعی آنها (خط قرمز) روی محور لگاریتمی. هر پلهی تجمعی اندازهای از block.db است که سطح بعدی را کامل جا میدهد. L0 و WAL حدود ۱ GiB هر کدام اضافه میکنند |
| ۱۲-۱۰ | پیشوندهای کلید BlueStore در RocksDB و column familyهای پیشفرض. هر نوع فراداده با یک پیشوند تکحرفی از بقیه جدا میشود؛ تنظیم bluestore_rocksdb_cfs برخی از این پیشوندها را در column familyهای جدا و shardشده قرار میدهد |
| ۱۲-۱۱ | ساختار فرادادهی یک شیء: Onode نگاشت منطقی را در ExtentMap نگه میدارد؛ هر Extent به یک Blob اشاره میکند که checksum و فشردهسازی و محل فیزیکی (pextent) را دارد؛ Blobهای مشترک بین cloneها با SharedBlob مدیریت میشوند |
| ۱۲-۱۲ | ماشین حالت TransContext در BlueStore. ردیف اول: نوشتن داده با aio؛ ردیف دوم: commit فراداده در RocksDB توسط kv_sync_thread، که در آن OSD تأیید commit را میگیرد؛ ردیف سوم: اعمال نوشتنهای deferred روی دستگاه و پاک کردن رکورد آنها |
| ۱۲-۱۳ | HybridAllocator: بازههای آزاد ابتدا در درخت AVL نگه داشته میشوند که جستوجوی سریع دارد؛ وقتی حافظهی مجاز درخت پر شود، بازههای اضافی به BitmapAllocator سرریز میشوند که حافظهی ثابت و کمی مصرف میکند |
| ۱۳-۱ | کدگذاری ۴+۲: یک stripe شانزدهکیلوبایتی به چهار shard دادهی چهارکیلوبایتی تقسیم و دو shard parity از آنها محاسبه میشود؛ هر shard روی یک OSD در دامنهی خرابی جدا قرار میگیرد |
| ۱۳-۲ | چیدمان stripeها: دادهی شیء به stripeهایی به طول k×stripe_unit تقسیم میشود؛ قطعهی i هر stripe روی shard i قرار میگیرد و parity هر stripe جداگانه محاسبه میشود |
| ۱۳-۳ | خواندن تخریبشده (degraded read): وقتی shard 1 در دسترس نیست، minimum_to_decode مجموعهای از k shard در دسترس را انتخاب و decode دادهی گمشده را بازسازی میکند |
| ۱۳-۴ | مقایسهی نوشتن کوچک در EC قدیمی و FastEC: پیادهسازی قدیمی کل stripe را میخواند و دوباره مینویسد؛ FastEC فقط shard تغییرکرده و parityها را بهروز میکند |
| ۱۳-۵ | افزونههای EC در Ceph: همه رابط مشترک ErasureCodeInterface را پیاده میکنند و بهصورت کتابخانهی پویا بارگذاری میشوند |
| ۱۳-۶ | یک نوشتن در pool از نوع EC. primary (که همیشه shard صفر نیست، ولی در این شکل shard 0 فرض شده) تراکنش را به stripeها میشکند و تصمیم میگیرد آیا RMW لازم است. در صورت نیاز، chunkهای قدیمی را میخواند، parity را محاسبه و برای هر shard یک ECSubWrite شامل chunk آن shard و ورودی pg log میفرستد. هر ورودی log یک ObjectModDesc دارد که توضیح میدهد این تغییر چگونه قابل بازگشت است. کلاینت فقط پس از commit همهی shardهای acting پاسخ میگیرد |
| ۱۳-۷ | خواندن و recovery در EC. در خواندن، OSD با minimum_to_decode کمترین مجموعهی shardهای لازم را تعیین میکند. با fast_read=false فقط همانها خوانده میشوند. با fast_read=true همهی shardها خوانده میشوند و با رسیدن اولین k پاسخ decode انجام میشود (تأخیر دم کمتر، ترافیک بیشتر). در recovery، primary از pg log فهرست اشیای گمشدهی هر shard را میسازد، از k shard سالم (یا کمتر در LRC و CLAY) میخواند، فقط chunk گمشده را decode و به OSD جدید push میکند. پایین شکل فرمول min_size پیشفرض را نشان میدهد |
| ۱۳-۸ | کدگذاری Reed-Solomon روی یک ستون بایت. چپ: ماتریس ۶×۴ ساختهشده با gf_gen_rs_matrix، شامل همانی برای داده و دو سطر parity. راست: محاسبهی P0 و P1 برای «ABCD» و بازگشایی پس از از دست رفتن d1 و d3. ضربها در GF(۲⁸) با چندجملهای ۰x11d انجام شدهاند |
| ۱۳-۹ | تعداد الگوهای از دست رفتنِ حداکثر m shard که با ماتریس Vandermonde قابل بازگشایی نیستند. سبز یعنی MDS است و قرمز یعنی شکست دارد. بررسی کامل با اسکریپت ما بر پایهی gf_gen_rs_matrix انجام شده است |
| ۱۳-۱۰ | گامهای بهروزرسانی دلتای parity برای تغییر یک بایت در d2: XOR قدیم و جدید، ضرب در ضریب ستون همان shard و XOR در هر parity. سطر آخر تطبیق با کدگذاری دوبارهی کامل را نشان میدهد. خط بنفش شمار shardهای خواندنی در دو مسیر را برای سه پروفایل مقایسه میکند |
| ۱۳-۱۱ | دادهی خواندهشده برای بازسازی یک chunk ازدسترفته با RS، LRC و CLAY در دو پیکربندی، همراه سربار خام هر طرح. LRC خواندن را با فضای بیشتر کم میکند و CLAY با همان فضای RS |
| ۱۳-۱۲ | مثال کامنت کد برای pool بهینهی ۴+۲: shard 0 از دست رفته است؛ pg_temp طوری مرتب میشود که shardهای parity (که همیشه بهروزند) جلو بیایند و osd.9 primary شود |
| ۱۴-۱ | جایگاه librados: کتابخانههای بالادستی و برنامهی شما از طریق IoCtx با librados کار میکنند؛ Objecter هدف هر عملیات را محاسبه میکند و MonClient نقشهها و احراز هویت را مدیریت میکند |
| ۱۴-۲ | ساختار یک عملیات مرکب: guardها، تغییرها و فراخوانی کلاس در یک تراکنش اتمی روی یک شیء اجرا میشوند |
| ۱۴-۳ | کنترل همزمانی خوشبینانه با assert_version: کلاینت B که نسخهی قدیمی را دیده، خطا میگیرد و پس از خواندن دوباره تلاش میکند |
| ۱۴-۴ | watch/notify: watcherها روی یک شیء ثبتنام میکنند؛ notify به همه میرسد و notifier پس از دریافت ackها یا timeout پاسخ تجمیعی میگیرد |
| ۱۴-۵ | ترتیب فیلدها در payload پیام MOSDOp با HEAD_VERSION=9 (COMPAT_VERSION=۳). ابتدا pgid و hash شیء، epoch نقشه و flags، شناسهی درخواست (reqid)، client_inc و mtime، سپس object_locator و نام شیء، تعداد و ساختار هر op، و در پایان اطلاعات snapshot، retry_attempt و features میآیند. در msgr2 همهی اینها در segment از نوع front هستند و دادهی ورودی همهی opها پشت سر هم در segment data قرار میگیرد |
| ۱۴-۶ | سازوکار backoff. وقتی PG در حال peering است یا شیء unfound است، OSD بهجای نگه داشتن op در صف خودش (که حافظه و thread مصرف میکند)، یک MOSDBackoff با عمل BLOCK برای یک بازهی hobject به کلاینت میفرستد. کلاینت تأیید میکند و opهای آن بازه را نزد خودش نگه میدارد. پس از فعال شدن PG، OSD با UNBLOCK آزادسازی را اعلام میکند و کلاینت opها را دوباره میفرستد |
| ۱۴-۷ | چپ: تعداد op همزمان مجاز برحسب اندازهی op. تا ۱۰۰ KiB سقف ۱۰۲۴ حاکم است و بالاتر از آن ⌊۱۰۰ MiB / s⌋. بالاتر از ۱۰۰ MiB فقط یک op در جریان است. راست: سقف throughput یک نمونهی Objecter با قانون Little، برای سه تأخیر نمونه. تأخیرها فرضیاند و فرمول از کد است |
| ۱۴-۸ | زمانبندی زندهبودن watch. pingها هر ۵ ثانیه فرستاده میشوند. پس از قطع شبکه در ثانیهی ۱۲، دیگر هیچ pingی نمیرسد. OSD حدود ثانیهی ۴۰ watch را حذف میکند (۳۰ ثانیه پس از آخرین ping). Objecter زودتر، پس از ۱۰ ثانیه، session را laggy میداند و نقشهی تازه درخواست میکند |
| ۱۴-۹ | کار هر tick در Objecter: شمردن opهای laggy، ping برای watchهای ثبتشده، و در صورت وجود op بیخانمان یا session مشکوک، درخواست osdmap تازه از MON و فرستادن MPing. پاسخ OSD سیاست lossy دارد، پس ping لازم است تا ریست شدن session کشف شود |
| ۱۴-۱۰ | timeout مؤثر notify برحسب timeout_ms ورودی. صفر به client_notify_timeout (۱۰ ثانیه) میرسد. ۱ تا ۹۹۹ میلیثانیه پس از تقسیم صحیح صفر میشود و OSD پیشفرض ۳۰ ثانیه را به کار میبرد. از ۱۰۰۰ به بعد، ثانیههای کامل اعمال میشوند |
| ۱۴-۱۱ | گامهای _calc_target: وجود pool، نگاشت شیء به PG، محاسبهی up/acting با CRUSH و pg_temp و upmap، و تصمیم به ارسال دوباره در صورت تغییر interval |
| ۱۵-۱ | مقایسهی دو روش افزایش شمارنده: در روش سمت کلاینت، دو کلاینت مقدار ۱۰ را میخوانند و هر دو ۱۱ مینویسند و یک بهروزرسانی گم میشود؛ در روش کلاس numops.add، OSD درخواستها را بهصورت سریال درون PG اجرا میکند و مقدار نهایی ۱۳ است |
| ۱۵-۲ | نقشهی مصرفکنندگان کلاسهای داخلی: librbd برای سرآیند تصویر و object map به cls_rbd، RGW برای شاخص bucket به cls_rgw و برای GC و صف به cls_rgw_gc و cls_2pc_queue و cls_fifo تکیه دارد؛ cls_lock و cls_version و cls_refcount عمومیاند |
| ۱۵-۳ | سه مرحلهی اعتبارسنجی یک CALL. ردیف اول هنگام رسیدن پیام اجرا میشود و کلاس و متد را مییابد. ردیف دوم پرچمهای ثبتشدهی متد را به پرچمهای op تبدیل میکند تا OSDCap بتواند مجوز را بسنجد و PG بداند op خواندنی است یا نوشتنی. ردیف سوم پس از اجرای متد شمارندههای خواندن و نوشتن را مقایسه میکند. متدی که برخلاف پرچمش رفتار کند، با EIO رد میشود |
| ۱۵-۴ | خط زمانی شش درخواست قفل از دو کلاینت روی یک شیء. نقطههای سبز موفق و قرمز ناموفقاند. نوارهای سایهدار بازهی نگهداشتن قفل هستند. تمدید A در ثانیهی ۲۵ انقضا را به ۵۵ میبرد. چون هیچکس پیش از ثانیهی ۶۰ به قفل دست نمیزند، ورودی A تا آن لحظه در xattr میماند و تنها در درخواست B پاک میشود |
| ۱۵-۵ | سه فراخوانی پیاپی cls_cxx_map_get_vals روی ۲۵۰۰ کلید با پیشوند u_. هر فراخوانی از آخرین کلید فراخوانی قبلی ادامه میدهد (UPPER_BOUND، یعنی کلید بعد از start_after). خط قرمز سقف ۱۰۲۴ کلید در هر درخواست است که روی max_to_get اعمال میشود. پرچم more فقط در صفحهی آخر false است |
| ۱۵-۶ | چپ: مقدار نهایی شمارنده پس از ۳۰۰ افزایش در هر رشته، با cls_numops (سبز) و با خواندن omap، افزودن یک و نوشتن از سمت کلاینت (قرمز). خط سیاه مقدار مورد انتظار است. راست: میانهی زمان هر افزایش روی کلاستر آزمایشی Ceph 18.2.7 |
| ۱۵-۷ | چپ: میانهی زمان شمردن خطها در شیءهای ۶۴ KiB تا ۱۶ MiB، با cls_lua درون OSD (بنفش) و با خواندن کل شیء در کلاینت (آبی) روی loopback. خطچینها همان زمان کلاینت را بهعلاوهی زمان انتقال روی ۱۰ و ۱ گیگابیت نشان میدهند (محاسبهشده). راست: پهنای باند سربهسر که زیر آن اجرای Lua زودتر تمام میشود |
| ۱۵-۸ | مسیر کامل یک فراخوانی کلاس در سه ردیف: کلاینت با Objecter درخواست CEPH_OSD_OP_CALL را به primary میفرستد؛ PrimaryLogPG::do_osd_ops نام کلاس و متد را decode و با ClassHandler کلاس را باز میکند؛ سپس متد اجرا و تعداد خواندن و نوشتن با پرچمهای اعلامشده مقایسه میشود و تغییرات در تراکنش PG ثبت میشود |
| ۱۵-۹ | دو مرحلهی بارگذاری کلاس در ClassHandler: بالا، نخستین فراخوانی با بررسی osd_class_load_list و dlopen فایل libcls_X.so از osd_class_dir و اجرای __cls_init؛ پایین، ثبت متدها با نام و پرچم و تعیین وضعیت allowed بر اساس osd_class_default_list که روی تفسیر capهای class-read و class-write اثر دارد |
| ۱۶-۱ | معماری cache tiering در سه ردیف: کلاینت درخواست pool پایه را به دلیل overlay به pool کش میفرستد؛ OSD کش در maybe_handle_cache_detail بررسی میکند شیء موجود است یا با توجه به HitSet باید promote شود؛ عامل کش در پسزمینه اشیای dirty را flush و اشیای clean سرد را evict میکند |
| ۱۶-۲ | ماشین حالت عامل کش: سه حالت flush (IDLE، LOW، HIGH) بر اساس نسبت اشیای dirty به هدف و سه حالت evict (IDLE، SOME، FULL) بر اساس پری؛ هیسترزیس با osd_agent_slop از نوسان سریع بین حالتها جلوگیری میکند چون آستانهی ورود کمی بالاتر و آستانهی خروج کمی پایینتر از هدف است |
| ۱۶-۳ | روال حذف cache tier حالت writeback: تغییر به proxy برای توقف promote، تخلیه با cache-flush-evict-all و تکرار تا خالی شدن pool کش، سپس حذف overlay و جدا کردن tier |
| ۱۶-۴ | شیء S3 دهمگابایتی در RADOS. شیء HEAD فراداده (manifest، ACL، etag و سایر xattrها) و در storage class پیشفرض، اولین تکهی داده (تا rgw_max_chunk_size=4M) را بهصورت inline نگه میدارد. بقیهی داده در اشیای tail با اندازهی rgw_obj_stripe_size=4M قرار دارد. پس از transition به یک storage class دیگر، HEAD در pool پیشفرض میماند ولی manifest آن به tailهای تازه در data_pool کلاس جدید اشاره میکند و دادهی قدیمی به garbage collection سپرده میشود. پایین شکل poolهای یک placement target را نشان میدهد |
| ۱۶-۵ | اجرای lifecycle در RGW. فهرست bucketهای دارای قاعده در rgw_lc_max_objs=32 شیء RADOS (shardهای lc) نگه داشته میشود. فقط در پنجرهی rgw_lifecycle_work_time (پیشفرض ۰۰:۰۰ تا ۰۶:۰۰ به وقت محلی) کار انجام میشود. هر shard با قفلی با عمر rgw_lc_lock_max_time=90 ثانیه برداشته میشود. rgw_lc_max_worker=3 worker موازی، هرکدام با ۱۲۸ coroutine (rgw_lc_max_wp_worker)، bucket index را پیمایش و expire، transition یا abort multipart را اجرا میکنند |
| ۱۶-۶ | اندازهی فیلتر Bloom یک HitSet برحسب تعداد اشیای یکتای مجموعهی قبلی، برای دو مقدار hit_set_period. اندازهی هدف به نسبت دورهی تازه به مدت واقعی مجموعهی قبلی مقیاس میشود و میان کف و سقف OSD بریده میشود. خطچینها همین دو حد هستند. هر PG کش در هر دوره یک فیلتر میسازد و آن را بهصورت شیء در namespace جداگانهی همان pool بایگانی میکند |
| ۱۶-۷ | شرط evict در حالت SOME برای هیستوگرام دهنمونهای مثال. محور افقی bin توانی دما و ستونها تعداد نمونهها در هر bin هستند. سمت چپ تصمیم کد main است، با شرط ۱۰⁶ − upper ≥ effort برای نگه داشتن: فقط bin سرد صفر میماند (سبز) و همهی binهای داغ بیرون میروند (قرمز). سمت راست تصمیم با اصلاح پیشنهادی ceph-devel در ۲۰۱۷ است، با شرط lower ≥ effort برای نگه داشتن: سردها بیرون میروند و داغها میمانند. خطچین در هر دو نیمه مرز تصمیم روی محور سهم تجمعی است: در کد main مرز upper = ۰.۶ (یعنی ۱ − ۰.۴) و در اصلاح پیشنهادی مرز lower = ۰.۴ است |
| ۱۶-۸ | سمت چپ جدول درجهی HitSetهای بایگانیشده برای دو نرخ کاهش است: با decay 20 وزن قدیمیترین مجموعه هنوز ۴۱٪ است و با decay 50 فقط ۶٪. سمت راست دمای چهار شیء نمونه است: حضور در مجموعهی جاری همیشه یک میلیون واحد میگیرد و با search_last_n = ۱ فقط نخستین مجموعهی بایگانی که شیء در آن پیدا شود، سهمش را اضافه میکند |
| ۱۶-۹ | evict_effort برحسب نسبت پری برای دو حالت هیسترزیس. منحنی آبی وقتی است که عامل بیکار بوده و آستانهی ورود ۰.۸۱۶ است. منحنی قرمز وقتی است که عامل در حال بیرون کردن است و آستانه به ۰.۷۸۴ پایین میآید. پلهها ناشی از کوانتش ۰.۱ هستند و بالای پری ۱۰۰٪ تلاش یکباره به ۱.۰ میپرد |
| ۱۶-۱۰ | مسیر یک قانون lifecycle از ثبت تا اجرا. کلید bucket با hash سادهی لینوکسی به یکی از ۳۲ شیء lc.N نگاشت میشود. در پنجرهی کاری (پیشفرض ۰۰:۰۰ تا ۰۶:۰۰ به وقت محلی سرور)، هر کارگر شاردها را به ترتیب تصادفی قفل میکند و bucket بعدی را برمیدارد. سپس اشیای bucket را با استخری از coroutineها بررسی میکند و برای هر نسخه انقضا یا انتقال را اعمال میکند |
| ۱۶-۱۱ | خط زمانی انقضای شیئی با Days = ۳۰. شیء در میانهی روز ۰ نوشته شده است. obj_has_expired زمان مبنا را به نیمهشب گرد میکند، پس اجرای روز ۳۰ فاصلهی ۲۹.۴ روز میبیند و شیء را نگه میدارد. نخستین اجرایی که حذف میکند، در پنجرهی روز ۳۱ است |
| ۱۶-۱۲ | فضای DB که سیاست پیشفرض use_some_extra برای فایلهای سطحهای بالاتر RocksDB مجاز میداند، برحسب اندازهی دستگاه DB. منحنی دندانهای است: پس از هر مرز سطح، فرمول دو برابر اندازهی سطح جاری را رزرو میکند و فضای اضافه به صفر میافتد. نقطههای قرمز چهار اندازهی مثال متناند. خطچین سقف ۹۵ درصدی دستگاه است |
| ۱۶-۱۳ | سه سطح لایهبندی امروزی در Ceph: در سطح pool با device class و قانونهای CRUSH جدا برای nvme و hdd، درون OSD با قرار دادن block.db و WAL روی NVMe کنار دادهی HDD، و در سطح شیء با storage class و قانون lifecycle در RGW که اشیا را پس از مدتی به pool ظرفیتی یا فضای ابری منتقل میکند |
| ۱۷-۱ | روش عیبیابی لایهای: از خلاصهی ceph -s و ceph health detail شروع کنید، لایهی درگیر (MON، OSD، PG یا کارایی) را تشخیص دهید، با فرمانهای همان لایه شواهد جمع کنید و سپس با چرخهی فرضیه، آزمون و اصلاح کوچک پیش بروید و همهچیز را در لاگ حادثه ثبت کنید |
| ۱۷-۲ | ماشین حالت Peering در سه ردیف: مسیر اصلی GetInfo، GetLog، GetMissing و WaitUpThru زیر Started/Primary/Peering؛ شاخههای شکست Down، Incomplete و WaitActingChange؛ و پس از فعالسازی، مسیر Active شامل رزرو و Recovering، رزرو و Backfilling، و در نهایت Recovered و Clean |
| ۱۷-۳ | اجرای find_best_info روی چهار info نمونه. ردیف قرمز به خاطر last_epoch_started قدیمیتر حذف میشود. از میان دو ردیف با جدیدترین last_update، آنکه log_tail کوچکتر و در نتیجه log بلندتری دارد (سبز) برنده است. ترجیح primary فعلی فقط وقتی اعمال میشود که همهی معیارهای قبلی برابر باشند. در pool EC جهت مقایسهی last_update برعکس است، چون نسخههای جلوتر باید rollback شوند |
| ۱۷-۴ | خط زمانی یک op که ۴۰۰ ثانیه گیر مانده است. از ثانیهی ۳۰ به بعد در شمار opهای کند است. اما خط لاگ «slow request» فقط وقتی نوشته میشود که سن op از ۳۰ × multiplier بگذرد و پس از هر لاگ ضریب دو برابر میشود. پس بازهی لاگها ۳۰، ۶۰ و ۱۲۰ ثانیه است و لاگ OSD در یک رخداد طولانی سیلآسا نمیشود |
| ۱۷-۵ | توزیع زمان scrub بعدی با پیشفرضها، از شبیهسازی همان فرمولهای کد. سمت چپ scrub سطحی است: توزیع یکنواخت در یک تا یکونیم روز. سمت راست scrub عمیق است: توزیع نرمال حول هفت روز با انحراف معیار ۱.۴ روز که در ±۲σ بریده شده است. دو ستون بلند لبهها، نمونههای بریدهشده هستند. ناحیهی زرد بازهی ۰.۸ تا ۱.۲ برابر است |
| ۱۷-۶ | حالت ۳۲ PG در pool t17 (size ۲) روی کلاستر آزمایشی سهOSDی BlueStore با Ceph 18.2.7، نمونهبرداریشده هر حدود یک ثانیه. خطچینها رخدادها را نشان میدهند: out و in کردن osd.0، و توقف و راهاندازی دوبارهی osd.1. سبز active+clean است، آبی recovery، زرد peering، خاکستری stale و قرمز undersized+degraded |
| ۱۷-۷ | چپ: درصد PGهای degraded، inactive و بینسخه برای از کار افتادن ۱، ۲ یا ۳ میزبان از ۱۰، با شمارش دقیق روی همهی ترکیبها از نگاشت osdmaptool (ستون میانگین است و خطها کمینه تا بیشینه). راست: احتمال اینکه کلاینتی که N شیء را لمس میکند در وضعیت دو میزبان ازکارافتاده به PG inactive برخورد کند |
| ۱۷-۸ | چرخهی عمر یک عملیات در OSD در سه ردیف: ورود و قرار گرفتن در صف شارد و رسیدن به PG؛ انتظارهای رایج که با رویدادهای waiting for ثبت میشوند؛ و اجرا، ارسال زیرعملیات به replicaها، دریافت ack و ارسال پاسخ. آخرین رویداد ثبتشدهی یک عملیات کند نشان میدهد کجا گیر کرده است |
| ۱۷-۹ | ماشین حالت scrub در سه بخش: primary پس از رزرو replicaها (محدود به osd_max_scrubs) اشیا را تکهبهتکه انتخاب، منتظر نوشتنهای در جریان میماند، نقشهی محلی میسازد و نقشهی replicaها را دریافت میکند؛ replica نقشهی خود را میسازد و میفرستد؛ scrub_backend نسخهی معتبر را انتخاب و ناسازگاریها را در ScrubStore ثبت میکند |
| ۱۸-۱ | چهار لایهی دفاعی در برابر فاجعه: درون کلاستر با تکثیر و recovery خودکار، بین دو سایت با یک کلاستر کشیده (stretch mode) و RPO صفر، بین کلاسترهای مستقل با تکثیر ناهمزمان rbd-mirror، RGW multisite و cephfs-mirror، و در نهایت نسخهی پشتیبان مستقل با object lock و پشتیبان پیکربندی |
| ۱۸-۲ | یک دور همگامسازی snapshot-based: سایت A طبق زمانبندی snapshot اولیهی S2 میسازد؛ rbd-mirror در سایت B snapshotهای دو طرف را اسکن میکند، snapshot ناقص S2' را در مقصد میسازد، تفاوت S1 تا S2 را با deep-copy منتقل میکند، وضعیت تصویر را اعمال و snapshot را کامل علامت میزند و در پایان پیوند peer را از S1 برمیدارد تا قابل حذف شود |
| ۱۸-۳ | دو سناریوی failover: در حالت برنامهریزیشده برنامه متوقف، تصویر در A demote، پس از همگام شدن در B promote و برنامه در B راهاندازی میشود و هیچ دادهای از دست نمیرود؛ در حالت اضطراری B با promote --force فعال میشود و پس از بازگشت A، تصویر A demote و با resync از B دوباره همگام میشود |
| ۱۸-۴ | چیدمان journal یک تصویر RBD. سرآیند در omap شیء journal.<id> قرار دارد. داده در setهایی از splay_width شیء است و هر entry بر اساس entry_tid % splay_width در یکی از اشیای set فعال append میشود. وقتی یکی از اشیا به اندازهی 2order برسد، کل set بسته و set بعدی فعال میشود. setهایی که همهی clientها از آنها گذشتهاند حذف میشوند |
| ۱۸-۵ | سه حالت stretch mode و مقادیر pg_pool_t در هرکدام (منطق تابعهای trigger_degraded_stretch_mode، trigger_recovery_stretch_mode و trigger_healthy_stretch_mode در OSDMonitor.cc نسخهی Squid). مقادیر min_size برای pool پیشفرض stretch با size=4 است |
| ۱۸-۶ | امتیاز یک همتا در ConnectionTracker. قرمز: اتصالی که از امتیاز کامل قطع میشود و پس از ۱۶.۶ ساعت به صفر میرسد. سبز: بازگشت از صفر. شیب هر دو با نیمهعمر ۴۳۲۰۰ ثانیه تعیین میشود. نقطهها مقدارهای محاسبهشده با گامهای ۲ ثانیهای هستند |
| ۱۸-۷ | امتیاز انتخاباتی پنج MON در سناریوی قطع پیوند a با DC2. چپ: هنگام قطعی، b با امتیاز ۴ برنده است. راست: پس از وصل شدن، تاریخچهی امتیاز a هنوز آسیبدیده است و b، که در تساوی با c و d rank پایینتری دارد، رهبر میماند. e بهعنوان tiebreaker امتیاز −۱ دارد |
| ۱۸-۸ | منطق رسیدگی به پیشنهاد در راهبرد connectivity. ردیف اول نادیده گرفتن پیشنهاد MON down از سوی tiebreaker و محاسبهی سه امتیاز است. ردیف دوم تصمیم بین پیشنهاد خود و ACK به دیگری است. ردیف سوم دو قاعدهی جانبی را نشان میدهد: امتیاز −۱ برای رهبران ممنوع و حاشیهی ۰.۰۰۰۵ برای جلوگیری از رأیگیری نوسانی |
| ۱۸-۹ | توزیع ۱۰۰ تصویر بین daemonهای rbd-mirror با SimplePolicy در پنج رویداد. ستون سوم اثر migration_throttle را نشان میدهد: تصویرهای تازهمنتقلشدهی B تا ۳۰۰ ثانیه قابل جابهجایی نیستند و C فقط ۱۷ تصویر میگیرد. ستون چهارم فقط وقتی رخ میدهد که rebalance_timeout غیرصفر باشد. شبیهسازی ما بر پایهی SimplePolicy.cc |
| ۱۸-۱۰ | توپولوژی stretch mode: دو datacenter هر کدام با دو MON و OSDهایی که دو نسخه از هر PG را نگه میدارند، و یک MON داور بدون داده در سایت سوم؛ هر نوشتن پیش از ack در هر دو سایت ثبت میشود و داور در صورت قطع لینک بین دو سایت، سایت برنده را مشخص میکند |
| ۱۸-۱۱ | روال بازسازی store مانیتور از روی OSDها در دو مرحله: ابتدا روی هر میزبان OSD با OSDهای متوقف، ceph-objectstore-tool با عملیات update-mon-db نقشهها را در یک پوشهی store مشترک جمع میکند و این پوشه از میزبانی به میزبان دیگر منتقل میشود؛ سپس ceph-monstore-tool با کلید admin و شناسهی MONها store را بازسازی میکند و store.db روی MONها جایگزین میشود |
| ۱۹-۱ | روال ارتقا در سه مرحله: آمادگی شامل سالم بودن کلاستر، خواندن یادداشتهای انتشار و اطمینان از دسترسی میزبانها به image؛ ترتیب cephadm از mgr و mon تا OSDها با بررسی ok-to-stop و سپس سایر daemonها؛ و پایان با یکسان بودن نسخهها و بررسی require_osd_release |
| ۱۹-۲ | جایگزینی دیسک خراب با حفظ شناسهی OSD در دو مرحله: خارج کردن با orch osd rm --replace که PGها را تخلیه و OSD را destroyed علامت میزند، و جایگزینی که پس از تعویض فیزیکی و در صورت نیاز zap، مشخصات سرویس OSD دیسک جدید را میگیرد و OSD با همان شناسه ساخته و backfill میشود |
| ۱۹-۳ | مراحل محاسبهی pg_autoscaler: ابتدا بودجهی PG هر ریشهی CRUSH از تعداد OSDها ضرب در mon_target_pg_per_osd، سپس سهم هر pool از مصرف واقعی یا نسبت هدف و تبدیل آن به pg_target با در نظر گرفتن bias و size، و در نهایت گرد کردن به توان ۲ و تغییر فقط وقتی تفاوت از آستانه بیشتر باشد |
| ۱۹-۴ | عملکرد balancer در حالت upmap: ماژول mgr در هر بازهی sleep_interval (پیشفرض ۶۰ ثانیه) و در صورت آرام بودن کلاستر do_upmap را اجرا میکند؛ OSDMap::calc_pg_upmaps تعداد PG هر OSD را با مقدار هدف وزندار مقایسه و برای OSDهای با انحراف بیش از max_deviation، ورودیهای pg_upmap_items با رعایت قانون CRUSH میسازد؛ نتیجه backfill فقط برای PGهای منتخب است |
| ۱۹-۵ | سمت چپ تابع پلهای nearest_power_of_two است: هدف هر pool به نزدیکترین توان دو گرد میشود و مرز تصمیم (نقطهچین نارنجی) ۱.۵ برابر توان پایینتر است. نقطهی قرمز مثال متن است. سمت راست قاعدهی آستانه با threshold = ۳ و pg_num فعلی ۳۲ است: فقط مقادیر سبز، یعنی بیش از ۹۶ یا کمتر از ۱۰.۷، تغییر واقعی ایجاد میکنند |
| ۱۹-۶ | چهار انتخاب ممکن برای گرد کردن دو pool در مثال متن. ارتفاع هر ستون مجموع فاصلهی مقادیر گردشده از هدف است. گزینهی خاکستری از بودجهی ۴۲۴ نسخهی PG اضافه بیشتر است و حذف میشود. گزینهی سبز کمترین هزینه را دارد و انتخاب میشود. برنامهریزی پویا بهجای امتحان همهی ۲ⁿ ترکیب، فقط مجموعهای ممکن PG اضافه را نگه میدارد |
| ۱۹-۷ | انحراف تعداد PG هر OSD از هدف وزندارش در مثال متن، پیش از upmap و پس از هر upmap. خطچینها مرز upmap_max_deviation = ۵ هستند. هر upmap یک واحد از پرترین OSD کم و به کمبارترین اضافه میکند. امتیاز bal_score که مجموع وزندار مربع انحراف نسبی است، با هر حرکت کمتر میشود. OSDهای وزن ۲ با انحراف ±۴ دست نخورده میمانند، چون درون مرزند |
| ۱۹-۸ | توزیع تعداد PG روی ۱۲۰ OSD پیش از upmap (نارنجی، فقط CRUSH) و پس از upmap (آبی)، برای ۱۰۲۴ و ۸۱۹۲ PG، با osdmaptool --upmap و انحراف مجاز ۱. عنوان هر نمودار تعداد ورودیهای pg-upmap-items و سهم replicaهای جابهجاشده را نشان میدهد |
| ۱۹-۹ | چپ: پس از دو برابر شدن pg_num و pgp_num، چند درصد PGهای فرزند روی ۰، ۱، ۲ یا ۳ OSD تازه مینشینند، و سهم کل دادهی جابهجاشده (حدود ۴۸.۷٪)، اندازهگیریشده با osdmaptool روی ۱۲۰ OSD. راست: مدل کنترلگر adjust_pgs در mgr برای ۱۰۲۴ به ۲۰۴۸، با سقف فاصلهی ۱۲۸ بین pg_num و pgp_num و گام pgp برابر pg_num × min(۰.۰۵ − misplaced, 0.025)، با فرض پایان backfill پیش از هر دور |
| ۲۰-۱ | معماری پشتهی پایش در سه ردیف: روی هر میزبان ceph-exporter شمارندههای عملکرد daemonها را از admin socket میخواند و node-exporter متریکهای سیستمعامل را منتشر میکند؛ mgr فعال با ماژول prometheus متریکهای سطح کلاستر و نقطهی service discovery را ارائه میدهد؛ Prometheus همه را scrape و قواعد هشدار را ارزیابی میکند، Alertmanager هشدارها را مسیریابی و Grafana داشبوردها را نمایش میدهد |
| ۲۰-۲ | مسیر یک شمارندهی عملکرد از تولید تا مصرف: OSD شمارندهای مثل op_w_latency را با PerfCountersBuilder ثبت میکند، admin socket آن را با فرمانهای counter dump و counter schema عرضه میکند، ceph-exporter آن را با برچسب ceph_daemon به فرمت Prometheus تبدیل میکند و Prometheus آن را ذخیره و با PromQL در قواعد هشدار و پنلها به کار میبرد |
| ۲۰-۳ | چرخهی هشدار در سه مرحله: سیگنالها از ceph_health_detail و متریکهای وضعیت و تأخیر؛ قاعده در Prometheus با expr و مدت پایداری for، برچسب severity و توضیح همراه لینک runbook؛ و تحویل در Alertmanager با گروهبندی بر اساس alertname و cluster، inhibit برای خاموش کردن هشدارهای فرعی و ارسال به on-call |
| ۲۰-۴ | پروتکل admin socket. یک اتصال UNIX برای هر فرمان باز میشود. درخواست رشتهی JSON است که با ۰x00 یا ۰x0A تمام میشود. پاسخ با یک طول ۴ بایتی big-endian و سپس payload میآید. اگر اولین بایت درخواست ۰x00 باشد، پروتکل قدیمی ۴ بایتی فرض میشود (کد ۱ یعنی perfcounters_dump و کد ۲ یعنی perfcounters_schema) |
| ۲۰-۵ | دو مسیر رسیدن یک شمارنده به Prometheus. مسیر mgr از MgrClient و پیام MMgrReport (هر mgr_stats_period=5 ثانیه) استفاده میکند و ماژول prometheus روی پورت ۹۲۸۳ آن را منتشر میکند. مسیر ceph-exporter روی هر میزبان مستقیماً از admin socket هر daemon میخواند (counter schema و counter dump هر exporter_stats_period=5 ثانیه) و روی پورت ۹۹۲۶ منتشر میکند. شمارندههای LONGRUNAVG به دو سری _sum و _count تبدیل میشوند |
| ۲۰-۶ | سمت چپ: نمای هیستوگرام دوبعدی نوشتن برای یک بار مصنوعی با سه اندازهی درخواست (۴ KiB، ۶۴ KiB و ۴ MiB) و توزیع لگنرمال تأخیر. رنگ لگاریتم تعداد است. هر سطر یک اندازهی درخواست و هر ستون یک بازهی تأخیر است. سمت راست: محدودهی bucketهای محور تأخیر روی محور لگاریتمی. هر bucket دو برابر قبلی است و سه مقدار نمونه (۱۵۰ µs، ۱.۵ ms و ۳۰ ms) روی آن نشان داده شدهاند. عددهای سمت چپ از اندازهگیری نیامدهاند و فقط شکل داده را نشان میدهند |
| ۲۰-۷ | خط زمانی cache ماژول prometheus با scrape_interval ۱۵ ثانیه. ردیف بالا مدت هر collect() است. جمعآوریهای نارنجی بیش از ۱۵ ثانیه طول کشیدهاند. دو ردیف پایین پاسخ scrapeهای Prometheus را در دو سیاست نشان میدهند. در fail (که مقدار پیشفرض log هم به آن تبدیل میشود)، scrapeهایی که آخرین جمعآوری کاملشدهشان کند بوده است ۵۰۳ میگیرند. در return همان scrapeها دادهی کهنه با کد ۲۰۰ میگیرند. مدتها فرضی هستند و برای نشان دادن منطق انتخاب شدهاند |
| ۲۰-۸ | بالا: نمونههای ceph_osd_up هر ۱۵ ثانیه برای یک OSD که در پنج دقیقهی آخر هفت بار down و up میشود. ناحیهی زرد پنجرهی ۵ دقیقهای ارزیابی در ثانیهی ۵۸۵ است. پایین: مقدار rate(ceph_osd_up[۵m]) × ۶۰ در هر ارزیابی، با پیادهسازی تقریبی برونیابی Prometheus. منحنی با هر flap یک پله بالا میرود و پس از هفتمین flap از خطچین آستانهی ۱ میگذرد |
| ۲۰-۹ | تعداد PG ده OSD در یک کلاس دستگاه، با میانگین ۱۰۵ (خط سیاه) و باند ±۳۰٪ (سبز). درصد بالای هر ستون انحراف آن OSD از میانگین است. فقط osd.9 با ۳۴.۳٪ بیرون از باند است (قرمز) و اگر پنج دقیقه همینطور بماند هشدار CephPGImbalance فعال میشود. عددها مثال هستند |
| ۲۰-۱۰ | نمونههای مصنوعی ceph_pool_percent_used در ۴۸ ساعت (نقطههای آبی)، با رشد خطی از ۰.۷۰ به ۰.۷۸ و کمی نویز. خطچین قرمز خط کمترین مربعات روی همین پنجره است که ۵ روز به جلو امتداد یافته. پیشبینی در آن نقطه ۰.۹۸۷ است که از خط ۰.۹۵ میگذرد، پس هشدار فعال میشود. خط عمودی نقطهی ارزیابی را نشان میدهد |
| ۲۰-۱۱ | mgmt-gateway بهعنوان ورودی واحد: مرورگر کاربر به gateway مبتنی بر nginx با TLS و نشانی مجازی وصل میشود که در صورت فعال بودن احراز هویت، کاربر را از oauth2-proxy و IdP بیرونی عبور میدهد؛ پشت gateway، Dashboard، Grafana، Prometheus و Alertmanager قرار دارند و دیگر مستقیماً در معرض شبکه نیستند |
| ۲۱-۱ | چرخهی تنظیم کارایی: تعریف هدف قابل اندازهگیری، گرفتن خط پایه با ابزارهای سنجش، اعمال تنها یک تغییر، سنجش دوباره با همان بار و همان مدت، و ثبت تغییر در صورت بهبود یا بازگشت با ceph config reset در غیر این صورت |
| ۲۱-۲ | چهار لایهی کارایی از پایین به بالا: سختافزار و شبکه، سیستمعامل، تنظیمات OSD شامل حافظه و شاردها و mClock، و در بالاترین سطح طراحی pool و تنظیمات کلاینت؛ بیشترین اثر معمولاً از لایههای پایینتر میآید |
| ۲۱-۳ | مسیر درخواست درون OSD: Messenger پیام را دریافت میکند و بر اساس hash شناسهی PG یکی از شاردها انتخاب میشود؛ هر شارد صف mClock خود را دارد و threadهای همان شارد درخواست را با گرفتن قفل PG در PrimaryLogPG::do_op اجرا و تراکنش را به BlueStore میسپارند |
| ۲۱-۴ | الگوریتم dmClock در سه بخش: هر درخواست بر اساس پارامترهای کلاس خود سه برچسب R، P و L میگیرد؛ زمانبند ابتدا در مرحلهی رزرو درخواستی را اجرا میکند که برچسب R آن سررسید شده، و در غیر این صورت میان درخواستهایی که از سقف خود عبور نکردهاند کمترین برچسب P را انتخاب میکند؛ درخواستهای immediate و پراولویت مانند peering پیش از صف mClock پردازش میشوند |
| ۲۱-۵ | حلقهی کنترل حافظهی OSD با مقادیر پیشفرض. مرزهای min و max از target، fragmentation و base محاسبه میشوند. tune_memory هر ثانیه بودجه را بر اساس فاصلهی حافظهی mapped از target کم یا زیاد میکند. بودجهی حاصل هر ۵ ثانیه بر اساس اولویت بین cacheهای RocksDB، onode و داده تقسیم میشود |
| ۲۱-۶ | محاسبهی هزینهی تراکنش و ظرفیت همزمانی BlueStore برای نوشتنهای ۴K. روی HDD حدود ۵۰ تراکنش مستقیم یا ۱۰۰ تراکنش deferred در صف جا میشود و روی SSD بیش از ۵۵۰۰. صف کوتاه در BlueStore باعث میشود درخواستها بیشتر در صف mClock منتظر بمانند، جایی که زمانبند هنوز میتواند بین کلاینت و بازیابی اولویتبندی کند |
| ۲۱-۷ | سمت چپ: تعداد PGهای یک OSD SSD نمونه در هر یک از هشت شارد صف، با ۱۰۰ PG و ps تصادفی. خطچین میانگین ۱۲.۵ است. سمت راست: توزیع نسبت پرترین شارد به میانگین در ۲۰۰۰ OSD شبیهسازیشده. حتی وقتی بار هر PG برابر است، معمولاً یک شارد ۳۰ تا ۷۰ درصد بیش از میانگین PG دارد. این عددها شبیهسازی هستند و فرض میکنند ps هر PG تصادفی و مستقل است |
| ۲۱-۸ | تقسیم حافظهی یک میزبان ۲۵۶ گیگابایتی با MemoryAutotuner. نوار بالا کل حافظه است. ۳۰ درصد هاشورخورده برای سیستمعامل و سربار RSS کنار گذاشته میشود. نوار پایین سهم daemonهای غیر OSD (با حداقلهای جدول min_size_by_type یا مصرف فعلی، هر کدام بیشتر باشد) و سپس ۲۰ سهم برابر OSDها را نشان میدهد. مصرف mon و RGW در این مثال فرضی است |
| ۲۱-۹ | پنج نوشتن نمونه روی یک OSD HDD و اینکه _do_write_data هر کدام را چگونه تقسیم میکند. شبکهی خاکستری بلوکهای ۴ KiB است. زرد بخشی است که به مسیر small میرود. قرمز بخشی از مسیر big است که چون کمتر از ۶۴ KiB است deferred میشود. آبی نوشتن مستقیم است. سطر آخر حالت مرزی ۴ KiB همتراز را نشان میدهد که با وجود قرار گرفتن در یک بلوک، بهخاطر شرط length ≠ min_alloc_size به مسیر big میرود |
| ۲۱-۱۰ | خروجی شبیهسازی dmClock برای سه کلاینت با رزرو، وزن و حد متفاوت. سمت چپ، ظرفیت ۱۰۰۰ درخواست در ثانیه: آبی سرویس در فاز reservation و نارنجی سرویس در فاز weight است. client فقط رزرو خود را میگیرد، recovery به حد ۲۰۰ میرسد و scrub بقیه را میگیرد. سمت راست، ظرفیت ۳۵۰ درخواست در ثانیه که از مجموع رزروها کمتر است: رزروها به نسبت ۳ به ۱ کوچک میشوند و scrub تقریباً صفر میگیرد. cost هر درخواست ۱ فرض شده است |
| ۲۱-۱۱ | منطق دو مرحلهای dmClock. هنگام رسیدن هر درخواست به سر صف کلاینت، سه برچسب R، P و L از برچسب قبلی همان کلاینت و زمان رسیدن ساخته میشوند. در هر فراخوانی do_next_request، ابتدا رزروهای سررسیدشده سرویس میگیرند. اگر رزروی سررسید نشده بود، از میان کلاینتهایی که از حدشان عبور نکردهاند، کمینهی P انتخاب میشود و برچسب R آن کلاینت به عقب برمیگردد. OSD با AtLimit::Wait ساخته میشود، یعنی اگر همه به حد رسیده باشند، هیچ درخواستی بیرون نمیآید |
| ۲۲-۱ | نقشهی پوشههای اصلی src/ در شاخهی main. ردیف اول کد پایهی مشترک است: انواع پایه و encoding در include، زیرساخت config و log و perf counter و thread در common، لایهی شبکه در msg/async، احراز هویت cephx در auth و الگوریتم CRUSH در crush. ردیف دوم کتابخانههای کلاینت است: Objecter و Striper در osdc، librados، librbd، کلاینت CephFS در client و کلاسهای سمت OSD در cls و objclass. ردیف سوم daemonها است: mon، osd، os (ObjectStore و BlueStore)، mds و mgr. ردیف چهارم بقیه است: rgw، crimson (OSD نسل بعد روی Seastar)، افزونههای erasure-code، ابزارهای خط فرمان در tools و کد پایتون در pybind و python-common |
| ۲۲-۲ | روابط کلاسهای اصلی در مسیر داده. در سمت کلاینت، RadosClient مالک یک Objecter است. Objecter برای هر OSD یک OSDSession نگه میدارد و از طریق AsyncMessenger اتصال (AsyncConnection با ProtocolV2) برقرار میکند. در سمت OSD، کلاس OSD که Dispatcher پیامها است N شیء OSDShard دارد که هر کدام scheduler خودش را دارد (پیشفرض mClockScheduler). هر PG بر اساس hash به یک شارد نگاشت میشود. PrimaryLogPG، PG را گسترش میدهد و PeeringState و PGLog را دارد، و از طریق PGBackend (پیادهسازیهای ReplicatedBackend یا ECBackend) تکثیر را انجام میدهد. در لایهی ذخیرهسازی، هر تغییر یک ObjectStore::Transaction است که BlueStore آن را با Collection، Onode و TransContext روی RocksDB، BlueFS و Allocator اجرا میکند |
| ۲۲-۳ | مدل thread در OSD کلاسیک. ردیف اول شبکه است: ms_async_op_threads (پیشفرض ۳) Worker دارد که هر کدام یک EventCenter مبتنی بر epoll اجرا میکند. پیامهای داده در همان thread شبکه از طریق ms_fast_dispatch تحویل میشوند تا هزینهی صف و context switch کم شود. ردیف دوم پردازش op است: op بر اساس hash شناسهی PG در scheduler یکی از OSDShardها صف میشود و ShardedThreadPool برای هر شارد threads_per_shard thread دارد که op را برمیدارند، قفل PG را میگیرند و do_request را اجرا میکنند. ردیف سوم ابزارهای کمکی است: Finisher برای اجرای callbackها در thread جدا، SafeTimer برای رویدادهای زماندار مثل tick و heartbeat، و دو thread اصلی BlueStore یعنی kv_sync_thread و kv_finalize_thread |
| ۲۲-۴ | نمودار توالی یک نوشتن replicated از librados تا ack. Objecter هدف را محاسبه میکند و MOSDOp را روی msgr2 به primary میفرستد. در primary، پیام در ms_fast_dispatch دریافت و در شارد مربوط به PG صف میشود. thread کاری آن را برمیدارد و از مسیر do_request و do_op به execute_ctx میرساند، و در آنجا prepare_transaction با do_osd_ops عملیات را به یک ObjectStore::Transaction تبدیل میکند. issue_repop تراکنش را از طریق backend به replicaها میفرستد و همزمان تراکنش محلی را به BlueStore میدهد. وقتی همهی اعضای acting commit کردند، eval_repop پاسخ را به کلاینت میفرستد |
| ۲۲-۵ | چرخهی یک TransContext در BlueStore. queue_transactions برای هر تراکنش یک TransContext میسازد و _txc_add_transaction هر عملیات را اجرا میکند؛ نوشتنها از _write به _do_write میرسند. بسته به همترازی و اندازه، یکی از _do_write_small یا _do_write_big انتخاب میشود و _do_alloc_write فضا را از Allocator میگیرد و checksum و فشردهسازی را اعمال میکند. داده یا مستقیم با aio نوشته میشود (AIO_WAIT و سپس IO_DONE)، یا اگر کوچکتر از prefer_deferred_size باشد (روی HDD 64K و روی SSD صفر یعنی هرگز)، همراه فراداده در WAL ذخیره میشود. سپس kv_sync_thread چند تراکنش را در یک batch RocksDB با یک fsync commit میکند و on_commit فراخوانی میشود. در مسیر deferred، نوشتن واقعی روی دستگاه بعداً و در پسزمینه انجام میشود |
| ۲۲-۶ | مسیر نوشتن RBD در librbd/io. درخواست از api::Io وارد ImageDispatcher میشود که لایههایی مثل QoS، exclusive lock، journal و کش را به ترتیب اجرا میکند. AbstractImageWriteRequest::send_request با Striper بازهی تصویر را به اشیای RADOS تقسیم میکند. هر شیء از ObjectDispatcher::send_dispatch و ObjectDispatch::write عبور میکند و AbstractObjectWriteRequest به ترتیب object map را بهروز میکند، شیء را مینویسد و اگر در clone شیء وجود نداشته باشد copyup اجرا میکند (فصل ۳) |
| ۲۲-۷ | مسیر PUT در RGW. frontend مبتنی بر beast درخواست HTTP را به process_request میدهد و پس از احراز هویت، rgw_process_authenticated برای op به ترتیب verify_permission، pre_exec و execute را اجرا میکند. RGWPutObj::execute داده را از طریق Writer در لایهی انتزاعی rgw::sal به AtomicObjectProcessor در driver rados میدهد که با RadosWriter::process head و stripeهای tail را مینویسد. در پایان _do_write_meta فراداده را مینویسد و index bucket را با UpdateIndex::prepare و complete (متدهای cls_rgw، فصل ۴) بهروز میکند |
| ۲۲-۸ | مسیر CephFS. در ردیف اول، open یک درخواست فراداده است: Client::_open یک MClientRequest میفرستد، MDS در Server::handle_client_open قفلهای لازم را با Locker::acquire_locks میگیرد و همراه پاسخ capهای مناسب را صادر میکند. در ردیف دوم، write مسیر داده است: Client::_write با get_caps حداقل Fw را لازم دارد و Fb را ترجیح میدهد. اگر Fb داشته باشد، داده در ObjectCacher بافر میشود و در غیر این صورت همگام نوشته میشود. داده با Striper و Objecter مستقیم به OSDها میرود و MDS در مسیر داده قرار ندارد |
| ۲۲-۹ | میانه (آبی تیره) و صدک ۹۰ (آبی روشن) زمان بین رویدادهای پیاپی OpTracker برای ۳۰۰ نوشتن ۴ KiB روی کلاستر آزمایشی Ceph 18.2.7، روی محور symlog. چپ: osd_op روی primary، فقط ترتیب غالب. راست: osd_repop روی replica |
| ۲۲-۱۰ | خط زمانی میانهی یک نوشتن replicated چهار کیلوبایتی. بالا op روی primary است و پایین op روی replica که به لحظهی ارسال subop در primary منتقل شده است. replica کار خود را حدود ۱۱۷۰ میکروثانیه پس از شروع تمام میکند، ولی primary تأیید آن را حدود ۱۶۰۰ میکروثانیه میبیند. اختلاف هزینهی مسیر بازگشت در messenger و صف است |
| ۲۳-۱ | مسیر یک نوشتن از BlueStore تا دیسک. در فضای کاربر، BlueStore یک bufferlist همتراز با بلوک میسازد و از طریق IOContext فهرستی از aio_t (offset، iovec و write hint) جمع میکند. KernelDevice::aio_submit آنها را دستهای به صف aio میدهد. در مرز syscall، libaio از io_submit و io_uring از io_uring_prep_writev و io_uring_submit استفاده میکند، روی fdی که با O_RDWR و O_DIRECT باز شده است (برای هر write hint یک fd جدا). در هسته، bio به request در blk-mq تبدیل و درایور NVMe یا SCSI داده را با DMA مستقیم از صفحههای حافظهی کاربر منتقل میکند. تکمیل از طریق io_getevents یا CQE به _aio_thread میرسد و او aio_callback را صدا میزند |
| ۲۳-۲ | توالی یک دسته I/O. thread تراکنش فقط aio_write را در IOContext ثبت میکند و سپس aio_submit همه را دستهای (تا عمق ۱۰۲۴) ارسال میکند. thread مستقل _aio_thread در حلقهای با مهلت ۲۵۰ میلیثانیه حداکثر ۱۶ تکمیل را برمیدارد و شمارندهی num_running هر IOContext را کم میکند. وقتی این شمارنده به صفر برسد، aio_callback با priv همان تراکنش فراخوانی میشود و BlueStore آن را از AIO_WAIT به IO_DONE و سپس KV_QUEUED میبرد |
| ۲۳-۳ | سه لایهی bufferlist. در بالا، list یک فهرست پیوندی از ptr_nodeها در _buffers نگه میدارد، بههمراه _carriage (اشاره به آخرین ptr که append سریع روی آن انجام میشود)، _len (کل بایتها) و _num (تعداد ptrها). در میانه، هر ptr فقط یک پنجره است: اشارهگر به یک raw همراه _off و _len. در پایین، raw مالک حافظهی واقعی است و data، len، شمارندهی ارجاع اتمیک nref، شناسهی mempool و کش آخرین crc را نگه میدارد. چند ptr در چند list میتوانند به یک raw اشاره کنند؛ کپی یک bufferlist فقط nref را افزایش میدهد |
| ۲۳-۴ | زنجیرهی انتخاب پیادهسازی crc32c و محلهای استفاده. ترتیب بررسی چنین است: روی x86 ابتدا AVX512 همراه VPCLMULQDQ، سپس SSE4.2 (با یا بدون PCLMUL برای پردازش موازی سه جریان)، روی aarch64 دستورهای CRC32 و روی ppc، s390x و riscv نسخههای مخصوص. اگر هیچکدام در دسترس نباشد، پیادهسازی جدولی نرمافزاری sctp استفاده میشود. این تابع در BlueStore برای هر chunk داده، در msgr2 حالت crc برای هر segment و preamble، و در bufferlist با کش crc روی raw به کار میرود |
| ۲۳-۵ | ساختار iocb شصت و چهار بایتی که aio_t در Ceph برای هر I/O پر میکند. نمونه یک pwritev با سه iovec و در مجموع ۱۲ KiB در آفست ۰x5A000 است. aio_data اشارهگر خود aio_t است و در io_event برمیگردد. با این اشارهگر، thread برداشت بدون جستوجو میفهمد کدام IOContext را باید کامل کند. در فرمانهای برداری، aio_nbytes تعداد iovecهاست، نه طول بایتی |
| ۲۳-۶ | انتظار پیش از هر تلاش دوباره (میلهها، مقیاس لگاریتمی) و انتظار تجمعی (خط قرمز) وقتی io_submit پیوسته EAGAIN برمیگرداند. پس از ۸ تلاش فقط ۳۲ میلیثانیه گذشته است، اما ۸ تلاش آخر بیش از ۸ ثانیه طول میکشند. پس یک اشباع کوتاه تقریباً نامرئی است و فقط اشباع طولانی به slow op تبدیل میشود |
| ۲۳-۷ | ورودی صف ارسال io_uring که init_sqe در Ceph برای همان pwritev میسازد. opcode برابر IORING_OP_WRITEV (۲) است. پرچم IOSQE_FIXED_FILE یعنی fd اندیسی در جدول فایلهای ثبتشده است و هسته هزینهی fget/fput را در هر I/O نمیپردازد. user_data همان اشارهگر aio_t است و در CQE شانزده بایتی برمیگردد |
| ۲۳-۸ | مسیر کامل io_uring در KernelDevice. ارسال زیر sq_mutex انجام میشود، چون چند thread (تراکنش، deferred، BlueFS) همزمان I/O میفرستند. برداشت در thread aio_thread زیر cq_mutex است. وقتی CQE آماده نیست، به جای مسدود شدن در io_uring_enter، روی ring_fd با epoll منتظر میماند، و این همان الگوی زمانبندی bdev_aio_poll_ms در libaio را حفظ میکند |
| ۲۳-۹ | اندازهی ده بافر raw_combined که refill_append_space هنگام append کردن یک مگابایت داده در قطعههای کوچک میسازد. اندازهها از ۴K دوبرابر میشوند تا به سقف ۲۵۶K برسند. S اندازهی سرآیند raw_combined است که از هر تخصیص کم میشود. فضای بلااستفادهی انتهای بافر آخر دلیل اختلاف ۱۲۷۶ KiB تخصیص و ۱۰۲۴ KiB داده است |
| ۲۳-۱۰ | سه کاربرد ماتریسهای جهش crc32c در Ceph. ردیف بالا الگوریتم ceph_crc32c_zeros است که طول را به توانهای دو میشکند. ردیف میانی مثال ۴۰۹۶ بایت صفر است که با یک ضرب ماتریس حل میشود. ردیف پایین تطبیق crc کششده در bufferlist است: به دلیل خطی بودن CRC، تفاوت دو seed معادل crc یک ناحیهی صفر با seed برابر XOR آن دو است |
| ۲۴-۱ | چیدمان ابتدای دستگاههای BlueStore. در دستگاه block با DB همدستگاه، بلوک ۰ label، بلوک ۱ superblock مربوط به BlueFS و بقیه فضای مشترک داده و BlueFS است. label در موقعیتهای ۱، ۱۰، ۱۰۰ و ۱۰۰۰ گیگابایت هم تکرار میشود (فقط اگر دستگاه به آن اندازه برسد). در دستگاه block.db جدا، extentهای BlueFS از ۸K (SUPER_RESERVED) شروع میشوند |
| ۲۴-۲ | ساختار BlueFS. superblock در بلوک ۱ قرار دارد و به fnode فایل لاگ (ino 1) اشاره میکند. لاگ دنبالهای از تراکنشهاست که opهایشان (ساخت دایرکتوری، link، بهروزرسانی کامل یا افزایشی fnode، حذف، پرش seq) کل فراداده را میسازند. هر fnode فهرستی از extentها {bdev، offset، length} دارد. وقتی لاگ بیش از ۱۶ مگابایت و ۵ برابر اندازهی تخمینی شود، فشردهسازی آغاز میشود |
| ۲۴-۳ | ساختار onode. کلید onode به ترتیبی کدگذاری میشود که مرتبسازی lexicographic آن با ترتیب ghobject_t یکسان باشد: shard + ۰x80، سپس pool + ۲⁶³، hash بیتمعکوس، namespace و نام escapeشده، snap، generation و در پایان کاراکتر 'o'. مقدار، bluestore_onode_t است. lextentها به blobها و blobها به pextentهای فیزیکی اشاره میکنند. extent mapهای بزرگ به shardهایی جدا با کلید «onode + u32 offset + x» شکسته میشوند |
| ۲۴-۴ | allocatorها و freelist. allocator پیشفرض hybrid است: فضای خالی در یک AVL (درخت offset و درخت size) نگه داشته میشود و اگر حافظهی آن از bluestore_hybrid_alloc_mem_cap (۶۴M) بیشتر شود، قطعههای کوچک به یک Bitmap سرریز میکنند. freelist پایدار، در صورت استفاده، در پیشوند b قرار دارد: هر کلید ۱۲۸ واحد تخصیص را با ۱۲۸ بیت پوشش میدهد و تغییرات با merge operator از نوع bitwise_xor اعمال میشوند. در حالت allocation from file (پیشفرض)، allocator هنگام shutdown تمیز در یک فایل BlueFS ذخیره و پس از crash از روی onodeها بازسازی میشود |
| ۲۴-۵ | کش BlueStore. کش buffer بهطور پیشفرض الگوریتم ۲Q را دارد: دادهی تازه به warm_in میرود. با بیرون رانده شدن، فقط سرآیند آن در warm_out میماند. اگر همان بازه دوباره خوانده شود، به hot منتقل میشود. این کار از بیرون راندن دادههای پرکاربرد توسط یک اسکن ترتیبی جلوگیری میکند. autotune هر ۵ ثانیه حافظه را بر اساس osd_memory_target میان kv، meta و data تقسیم میکند |
| ۲۴-۶ | سه نمای یک ناحیهی ۳ مگابایتی از دستگاه. سبز بازهی آزاد است و هاشور بخش تخصیصیافته. در نمای میانی، near-fit با cursor مخصوص اندازهی ۶۴K از ابتدای ناحیه جلو میرود، بازهی ۳۲K را رد میکند و ابتدای بازهی ۱۲۸K را برمیدارد. در نمای پایین، best-fit درخواست ۳۲K را دقیقاً در کوچکترین بازهی کافی میگذارد تا بازههای بزرگ برای درخواستهای بزرگ بمانند |
| ۲۴-۷ | امتیاز تکهتکه شدن برای یک گیگابایت فضای آزاد که به تکههای هماندازه تقسیم شده است. محور افقی لگاریتمی است. منحنی میان ۱ مگابایت و ۱۲۸ مگابایت سریع پایین میآید، چون ضریب ارزش هر دو برابر شدن طول در این بازه از ۱.۲ به ۱ کاهش مییابد. خطچین آبی ترکیب نمونهی متن است که با وجود هزاران تکهی کوچک امتیاز ۰.۲۶ دارد |
| ۲۴-۸ | دو مسیر hybrid allocator. ردیف بالا درج یک بازهی آزاد است: تا سقف حافظه بازه در دو درخت AVL درج میشود و پس از آن کوچکترین بازهها به bitmap رانده میشوند. ردیف پایین تخصیص است: درخواستهای کوچکتر از کوچکترین بازهی AVL اول از bitmap پاسخ میگیرند. این کار حافظهی OSD را محدود نگه میدارد، بدون اینکه بازههای بزرگ گم شوند |
| ۲۴-۹ | ۵۸۸ بایت نخست label واقعی osd.0 روی کلاستر آزمایشی Ceph 18.2.7، به رنگ میدانها: متن جادویی و uuid متنی (۶۰ بایت)، سرآیند ENCODE_START با نسخه و طول، uuid دودویی، size، btime، description، meta چهاردهجفتی و crc32c پایانی که با محاسبهی مستقل وارسی شد |
| ۲۴-۱۰ | نقشهی واقعی فضای آزاد osd.0 در سه لحظه. چپ: تعداد extentهای آزاد در هر بازهی اندازه (محور لگاریتمی). راست: مجموع بایت آزاد در هر بازه. دهها هزار حفرهی ۴ KiB تقریباً هیچ سهمی از بایتها ندارند و به همین دلیل fragmentation_rating پایین میماند |
| ۲۵-۱ | مسیرهای نوشتن و خواندن در RocksDB. در مسیر نوشتن، WriteBatch ابتدا در WAL (روی دستگاه WAL یا DB از طریق BlueFS) و سپس در memtable فعال هر column family ثبت میشود. در پسزمینه، memtableهای پر تغییرناپذیر میشوند، پس از جمع شدن حداقل ۶ عدد با هم ادغام و به L0 flush میشوند و compaction آنها را به سطوح پایینتر میبرد. خواندن به ترتیب memtable، immutableها، همهی فایلهای L0 و سپس از هر سطح دیگر حداکثر یک فایل (با کمک bloom filter) را بررسی میکند |
| ۲۵-۲ | ساختار یک فایل SST از نوع block-based. داده در بلوکهای حدود ۴K (rocksdb_block_size) با فشردهسازی پیشوند کلید و نقاط restart ذخیره میشود. پس از دادهها بلوک filter (bloom با ۲۰ بیت برای هر کلید در Ceph)، index (آخرین کلید هر بلوک و BlockHandle آن)، properties و metaindex قرار میگیرند. footer در انتها BlockHandleهای metaindex و index، format_version و عدد جادویی ۰x88e241b785f4cff7 را دارد. هر بلوک یک trailer پنجبایتی شامل نوع فشردهسازی و checksum دارد |
| ۲۵-۳ | اندازهی هدف سطوح با پیشفرضهای Ceph و جایگذاری آنها روی دستگاهها. L1 برابر ۱G است و هر سطح ۸ برابر سطح قبلی، پس L2 حدود ۸G، L3 حدود ۶۴G و L4 حدود ۵۱۲G است. سیاست use_some_extra سطوحی را که کامل جا میشوند روی block.db قرار میدهد، فضای اضافه را برای بخشی از سطح بعدی به کار میبرد و فقط بقیه را به دستگاه کند میفرستد |
| ۲۵-۴ | column family sharding در BlueStore. پیشفرض main این است: «m(3) p(۳,۰-۱۲) O(۳,۰-۱۳)=block_cache={type=binned_lru} L=min_write_buffer_number_to_merge=32 P=min_write_buffer_number_to_merge=32». هر پیشوند فهرستشده CF(های) جداگانهی خودش را دارد. برای انتخاب shard، بازهی بایت مشخصشده از کلید با ceph_str_hash_rjenkins هش و باقیماندهی آن بر تعداد shard محاسبه میشود. پیشوندهای فهرستنشده در CF پیشفرض میمانند |
| ۲۵-۵ | skiplist با ده کلید و ارتفاعهای تصادفی، و مسیر Seek(۳۸) با رنگ قرمز. جستوجو از بالاترین سطح head شروع میشود، تا وقتی کلید بعدی کوچکتر است جلو میرود و وقتی بزرگتر یا تهی است یک سطح پایین میرود. اگر در هر سطحی کلید برابر پیدا شود، همانجا برمیگردد. در InlineSkipList اشارهگرهای سطوح بالا پیش از خود گره در همان تخصیص arena قرار دارند، پس یک گره با ارتفاع h دقیقاً h اشارهگر هشت بایتی و سپس کلید و مقدار را در یک قطعهی پیوسته دارد |
| ۲۵-۶ | توالی group commit در WriteThread. نخستین نویسنده با CAS روی newest_writer_ رهبر میشود. بقیه منتظر میمانند. رهبر تا سقف اندازه و تا جایی که پرچمها سازگارند، batchها را در یک رکورد WAL ادغام میکند. یکبار Sync میکند، هر batch را با شمارهی توالی پیوسته در memtable درج میکند و سپس همه را با STATE_COMPLETED بیدار میکند |
| ۲۵-۷ | اینکه log::Writer::AddRecord یک batch هفتادهزار بایتی را چگونه در چهار بلوک ۳۲ KiB مینویسد. هر قطعه هدر ۷ بایتی دارد: crc32c چهاربایتی، طول دوبایتی و نوع یکبایتی. بلوکها مستقلاند: اگر خواننده در بازیابی به بلوک خراب برسد، میتواند از ابتدای بلوک بعدی همگام شود. اگر کمتر از ۷ بایت به پایان بلوک مانده باشد، با صفر پر میشود |
| ۲۵-۸ | چیدمان بایتبهبایت یک بلوک دادهی SST با دو ورودی نمونه. ورودی اول restart است و کلید کامل ۱۸ بایتی را نگه میدارد (۱۰ بایت کلید کاربر و ۸ بایت trailer شامل seq و type). ورودی دوم فقط ۹ بایت غیرمشترک دارد. انتهای بلوک آرایهی آفست restartها و یک u32 پانویس است که بیت ۳۱ آن نوع ایندکس درونبلوکی را نشان میدهد و بقیهی بیتها تعداد restartها را |
| ۲۵-۹ | نرخ مثبت کاذب برحسب بیت به ازای هر کلید برای Bloom استاندارد و Bloom محلی در cache line، با تعداد probe که هر پیادهسازی انتخاب میکند. نقطهی قرمز تنظیم Ceph است (۲۰ بیت، ۱۱ probe، حدود ۰.۰۱۷٪). در ۱۰ بیت تفاوت دو منحنی کم است و در ۲۰ بیت حدود ۲.۵ برابر. اما Bloom محلی همیشه یک خط ۶۴ بایتی را لمس میکند، و این برای Getهای onode که بیشترشان در L0 تا L2 دنبال کلید نبوده میگردند، تعیینکننده است |
| ۲۵-۱۰ | اندازهی واقعی و هدف هر سطح با level_compaction_dynamic_level_bytes=true. هدفها از سطح آخر به بالا با تقسیم بر ۸ به دست میآیند. L1 و L2 زیر سطح پایهاند و خالی میمانند. امتیاز برای سطح آخر حساب نمیشود، چون MaxInputLevel برابر num_levels − ۲ است |
| ۲۶-۱ | دستدهی کامل msgr2. پس از banner، هر طرف HELLO (نوع entity و آدرسی که طرف مقابل را با آن میبیند) میفرستد. سپس احراز هویت cephx با AUTH_REQUEST، رفت و برگشتهای MORE و AUTH_DONE (که global_id و حالت اتصال crc یا secure را تعیین میکند) انجام میشود. از این نقطه frameها در حالت انتخابشدهاند. AUTH_SIGNATURE (HMAC-SHA۲۵۶ روی بایتهای پیش از احراز) از دستکاری مرحلهی رمزنشده جلوگیری میکند. CLIENT_IDENT و SERVER_IDENT جلسه را با cookie و global_seq برقرار میکنند |
| ۲۶-۲ | ساختار frame در msgr2.1. preamble_block_t دقیقاً ۳۲ بایت است و طوری طراحی شده که مضرب بلوک AES (۱۶) باشد. در حالت crc، segment اول بلافاصله پس از preamble و همراه crc خودش میآید و اگر segment دیگری باشد، epilogue شامل late_status و crc بقیهی segmentهاست. در حالت secure، preamble همراه ۴۸ بایت اول segment صفر یکجا رمز میشود و یک tag مربوط به GCM میگیرد. پیامها چهار segment دارند: header2، front، middle و data (با همترازی صفحه برای کپی صفر) |
| ۲۶-۳ | همان قاب KEEPALIVE2 بالا، این بار به شکل شبکهی بایت: preamble سیودوبایتی با tag، تعداد segment و جدول چهار segment (سهتای آخر صفر)، سپس crc خود preamble، سپس segment هشتبایتی utime_t و در پایان crc آن segment. جمع ۴۴ بایت است و چون فقط یک segment هست، epilogue ندارد. این کوچکترین قاب پرتکرار روی یک اتصال بیکار است و مثلاً MonClient هر mon_client_ping_interval ثانیه یکی از آن را به mon میفرستد (send_keepalive) |
| ۲۶-۴ | ماشین وضعیت ProtocolV2. سمت connect از START_CONNECT با banner، HELLO، auth و فشردهسازی به SESSION_CONNECTING (جلسهی تازه) یا SESSION_RECONNECTING (cookie قبلی) و در نهایت READY میرسد. سمت accept مسیر مشابهی دارد. در READY دریافت هر پیام از سه throttler (تعداد پیام، بایتهای policy و صف dispatch) میگذرد. خطا در اتصال lossless به STANDBY یا بازاتصال با backoff (۰.۲ تا ۱۵ ثانیه)، و در اتصال lossy به CLOSED منجر میشود |
| ۲۶-۵ | مدل threading در AsyncMessenger. NetworkStack (پیشفرض Posix) تعداد ms_async_op_threads=3 Worker میسازد و هر Worker یک EventCenter دارد. هر اتصال هنگام ساخت به یکی از workerها سپرده میشود و تا پایان عمرش همانجا میماند. حلقهی هر worker ابتدا زمانسنجهای سررسیده را اجرا میکند، سپس در epoll_wait (edge-triggered) منتظر رویدادهای fd میماند و در پایان رویدادهای خارجیای را اجرا میکند که threadهای دیگر صف کردهاند. threadهای دیگر worker را با نوشتن در یک eventfd (یا pipe در سیستمهای بدون آن) بیدار میکنند |
| ۲۶-۶ | مقایسهی بایتهای روی سیم برای یک پیام با دادهی ۴ KiB در دو حالت crc و secure. در crc، segment 0 crc چهاربایتی خودش را دارد و epilogue سیزدهبایتی crc سه segment دیگر را. در secure، preamble جای ۴۸ بایت inline و تگ GCM شانزدهبایتی را دارد، پس هدر پیام بدون قاب جداگانه رمز میشود. front به مضرب ۱۶ گرد میشود و epilogue یک بلوک ۱۶ بایتی بهاضافهی تگ است. نوار داده برای خوانایی در ۱۰۰۰ بایت بریده شده است |
| ۲۶-۷ | چیدمان ۴۰ بایت ابتدایی connection_secret که cephx در پایان احراز هویت به هر دو طرف میدهد: ۱۶ بایت کلید AES-128 مشترک، سپس nonce دوازدهبایتی دریافت و nonce دوازدهبایتی ارسال. هر nonce یک بخش ثابت چهاربایتی و یک شمارندهی هشتبایتی دارد. سمت سرور با پرچم crossed نقش rx و tx را جابهجا میکند تا هر جهت nonce مستقل داشته باشد |
| ۲۶-۸ | ساختار process_events در EventCenter. مهلت انتظار از نزدیکترین رویداد زماندار به دست میآید و کف ۱ میلیثانیه دارد. اگر رویداد خارجی منتظر باشد، صفر میشود. پس از epoll_wait، اول callbackهای fd، سپس تایمرها و در پایان رویدادهای خارجی اجرا میشوند. ردیف پایین مسیر بیدار کردن از thread دیگر را نشان میدهد: صف زیر قفل، شمارندهی اتمی و یک write هشتبایتی روی eventfd |
| ۲۶-۹ | ردیابی get_worker برای ده اتصال و یک بستن. ستونهای سمت راست شمارندهی references هر Worker را پس از هر رویداد نشان میدهند. انتخاب همیشه کمترین شمارنده است و در تساوی، نخستین Worker برنده میشود. بستن اتصال پنجم یک جای خالی در Worker 1 باز میکند که بلافاصله اتصال بعدی آن را پر میکند |
| ۲۶-۱۰ | توالی بازاتصال یک اتصال lossless. کلاینت پس از قطع، پیامهای بیack را به سر صف برمیگرداند و پس از احراز هویت دوباره SESSION_RECONNECT میفرستد. OSD آخرین seq دریافتی را در RECONNECT_OK اعلام میکند و کلاینت هر چیزی تا آن شماره را دور میریزد. اتصالهای lossy (مثل کلاینت به OSD در Objecter) این مسیر را ندارند و به جای آن reset میشوند، و Objecter خودش opها را دوباره میفرستد |
| ۲۷-۱ | سه لایهی محاسبه. در بالا، حساب GF(۲⁸): جمع برابر XOR است و ضرب با شیفت و XOR با ۰x1D هنگام سرریز، یا با جدول log/exp انجام میشود. در وسط، ماتریس تولید سیستماتیک ISA-L برای k=4 و m=2 به روش Vandermonde: چهار سطر اول همانیاند (داده دستنخورده میماند)، سطر P0 همه ۱ است (همان XOR ساده) و سطر P1 توانهای ۲ است. در پایین، پیادهسازی SIMD: ec_init_tables برای هر ضریب دو جدول ۱۶ خانهای (nibble پایین و بالا) میسازد و دستور PSHUFB با این جدولها ۳۲ یا ۶۴ بایت را در یک دستور ضرب میکند |
| ۲۷-۲ | تقسیم یک شیء ۶۴K در pool با k=4، m=2 و stripe_unit=4K. stripe_width برابر ۱۶K است، پس شیء چهار stripe دارد. در هر stripe، چهار ناحیهی ۴K پشتسرهم به shardهای داده ۰ تا ۳ میروند و دو parity محاسبه و در shardهای ۴ و ۵ نوشته میشوند. هر OSD یک «شیء shard» با shard_id خودش و اندازهی ۱۶K نگه میدارد |
| ۲۷-۳ | نوشتن جزئی ۴K در مقایسهی دو backend. در EC قدیمی، primary کل stripe را از k shard میخواند، دادهی جدید را ادغام، دوباره encode و همهی shardها را بازنویسی میکند. در EC بهینه (Tentacle با allow_ec_optimizations)، فقط همان ۴K قدیمی و parityها خوانده میشوند، delta برابر قدیم ⊕ جدید محاسبه و هر parity با ضریب مربوط در delta بهروز میشود. خواندن کوچک هم بهجای decode از k shard، مستقیم از یک shard داده انجام میشود |
| ۲۷-۴ | ترافیک بازسازی یک shard به اندازهی S. RS با k=4 به ۴S نیاز دارد. LRC با l=3 فقط از گروه محلی (۳ chunk) میخواند. CLAY با d=5 از ۵ helper، هرکدام کسر ۱/(d−k+1) = ۱/۲ از chunk را میخواند، یعنی ۲.۵S. SHEC parityها را روی زیرمجموعههای همپوشان داده میسازد تا خرابی تکی با خواندن کمتر از k ترمیم شود |
| ۲۷-۵ | جدول ۳۲ بایتی که gf_vect_mul_init_base برای ضریب c=0x02 میسازد. ۱۶ خانهی اول حاصلضرب c در نیمبایت پایین (۰ تا F) هستند و ۱۶ خانهی دوم حاصلضرب c در نیمبایت بالا (۰x00 تا ۰xF0). ضرب هر بایت با دو جستوجو و یک XOR انجام میشود، چون ضرب در GF(۲⁸) نسبت به XOR خطی است: c·(hi ⊕ lo) = c·hi ⊕ c·lo. خانههای زرد همان دو خانهایاند که برای بایت ۰xB7 خوانده میشوند |
| ۲۷-۶ | مسیر ضرب ۳۲ بایت داده در ثابت c=2 با AVX2. داده به دو نیمبایت شکسته میشود. هر نیمبایت با VPSHUFB از جدول ۱۶ خانهای مربوطش جستوجو میشود و دو نتیجه XOR میشوند. ردیف پایین نشان میدهد این ضرب برای هر ستون داده تکرار میشود و در parity انباشته میشود. نسخههای gf_Nvect_dot_prod چند parity را در یک گذر روی داده میسازند تا داده فقط یکبار از حافظه خوانده شود |
| ۲۷-۷ | دو ماتریس کدگذاری ISA-L برای k=4، m=2 و برچسب parity حاصل برای بایت نمونهی D = [۰x37، ۰x5C، ۰xA1، ۰x0F]. چهار سطر بالا همانیاند و داده را بیتغییر به shardهای ۰ تا ۳ میبرند. دو سطر پایین ضرایب parity هستند. در Vandermonde، سطر P تماماً ۱ است، پس P همان XOR است و مسیر سریع xor_gen برای m=1 بهکار میرود. در Cauchy همهی ضرایب parity غیر بدیهیاند |
| ۲۷-۸ | نوشتن ۴K درون shard D1 در pool با k=4، m=2 و مسیر parity delta. primary فقط همان بازه را از D1 و دو parity میخواند. Δ را با encode_delta حساب میکند و با apply_delta ضریب ستون ۱ ضرب در Δ را به هر parity XOR میکند. سپس سه نوشتن را در یک تراکنش توزیعشده میفرستد. shardهای D0، D2 و D3 درگیر نمیشوند |
| ۲۷-۹ | اینکه parse_kml سه پارامتر k=4، m=2 و l=3 را چگونه به رشتههای mapping و layers تبدیل میکند. در mapping، حرف D جای داده و خط زیر جای chunk کدشده است. در هر لایه، c یعنی chunkی که آن لایه تولید میکند. لایهی سراسری parityهای ۲ و ۶ را با RS میسازد و هر لایهی محلی یک parity XOR مانند برای سه chunk گروه خود. با crush-locality=rack، قانون CRUSH دو rack و در هر rack چهار host انتخاب میکند تا ترمیم محلی از rack بیرون نرود |
| ۲۷-۱۰ | شبکهی sub-chunk در Clay با k=4، m=2 و d=5 برای سه گره گمشدهی متفاوت. سطرها گرهها با مختصات (x, y) در شبکهی q×t هستند و ستونها شاخص sub-chunk. سطر قرمز گره گمشده است و خانههای آبی sub-chunkهاییاند که هر helper میفرستد. الگو از get_repair_subchunks میآید: هرچه y بزرگتر باشد، بازهها کوتاهتر و پراکندهترند. پس خواندن روی دیسک تکهتکهتر میشود، و این هزینهی پنهان Clay روی HDD است |
| ۲۸-۱ | خط لولهی کامل نگاشت. نام شیء با rjenkins به ۰xD1895615 hash میشود. با pg_num=128، ceph_stable_mod نتیجهی ۰x15 را میدهد، یعنی PG 3.15. با پرچم HASHPSPOOL، شمارهی pool هم در hash دخیل میشود تا PGهای poolهای مختلف روی هم نیفتند، و x = ۰x36D15C32 ورودی CRUSH است. crush_do_rule فهرست خام OSDها را برمیگرداند، سپس OSDهای ناموجود حذف و upmapها اعمال میشوند و در پایان pg_temp مجموعهی acting را تعیین میکند |
| ۲۸-۲ | اجرای واقعی straw2 برای x=0x36D15C32 در یک bucket ریشه با سه host: -۳ و -۴ هرکدام با وزن ۱۰ و -۵ با وزن ۲۰. در r=0، host -۴ با بیشترین draw (−۸.۹M) برنده است. در r=1، host -۵ برنده است. برای replica سوم، r=2 تا r=5 به hostهای تکراری میرسند و هر بار ftotal افزایش مییابد، تا اینکه r=6 به host -۳ میرسد. سقف تلاش choose_total_tries=50 است |
| ۲۸-۳ | اجرای replicated_rule. TAKE ریشه را در بردار کاری w میگذارد. CHOOSELEAF_FIRSTN با numrep=0 (یعنی برابر size) برای هر replica یک host انتخاب میکند و سپس بهصورت بازگشتی یک OSD درون آن. EMIT نتیجه را به خروجی میافزاید. در هر انتخاب، r = rep + ftotal است. اگر نتیجه تکراری باشد یا OSD با is_out رد شود، ftotal افزایش مییابد و تلاش تکرار میشود. is_out برای وزن reweight بین ۰ و ۱، با hash دیگری از (x، osd) تصمیم احتمالی میگیرد |
| ۲۸-۴ | سه ابزار اصلاح توزیع و ترتیب اعمال آنها. reweight با is_out احتمالی کار میکند و جابهجایی گسترده ایجاد میکند. choose_args (weight-set) وزنهای جایگزینی برای straw2 فراهم میکند که حتی میتواند برای هر position (replica اول، دوم و ...) متفاوت باشد و balancer در حالت crush-compat از آن استفاده میکند. upmap پس از CRUSH نتیجه را صریحاً جایگزین میکند. در OSDMap ترتیب اعمال این است: crush_do_rule با choose_args، حذف OSDهای ناموجود، pg_upmap، pg_upmap_items و pg_upmap_primary |
| ۲۸-۵ | اجرای واقعی bucket_perm_choose برای bucket با شمارهی -۵ و چهار item با x=0x1234ABCD. سه گام Fisher–Yates جایگشت [۳ ۲ ۰ ۱] را میسازند. پس r=0 به item 13، r=1 به ۱۲، r=2 به ۱۰ و r=3 به ۱۱ میرسد. جایگشت در crush_work نگه داشته میشود تا تلاشهای بعدی با همان x از نو محاسبه نشوند |
| ۲۸-۶ | اجرای واقعی bucket_list_choose برای bucket با شمارهی -۶ با وزنهای ۱، ۱ و ۲. برای r=0 مقدار مقیاسشدهی item 22 برابر ۳.۹۰ است که از وزن ۲ بیشتر است، پس رد میشود. item 21 با ۰.۳۶ < ۱ انتخاب میشود. برای r=1 و r=2 هر دو item آخر رد میشوند و item 20 انتخاب میشود |
| ۲۸-۷ | اجرای واقعی bucket_tree_choose برای چهار item با وزنهای ۱، ۲، ۳ و ۴. ریشه n4 وزن ۱۰ دارد. t=4.22 از وزن زیردرخت چپ (۳) بیشتر است، پس مسیر به راست (n6، وزن ۷) میرود. آنجا t=0.78 < ۳، پس برگ n5 یعنی item 32 انتخاب میشود. برای r=1 هم همین مسیر طی شد. توزیع روی ۴۰۰ هزار ورودی تقریباً ۱:۲:۳:۴ بود |
| ۲۸-۸ | درصد ورودیهایی که با تغییر bucket مقصد دیگری گرفتند، روی ۲۰۰ هزار مقدار x با hash واقعی. ستون آبی: افزودن item پنجم به چهار item هموزن (حداقل نظری ۲۰٪). ستون نارنجی: حذف item دوم و ساخت دوبارهی bucket (حداقل نظری ۲۵٪). فقط straw2 در هر دو حالت به حداقل نظری میرسد |
| ۲۸-۹ | ساختار OSDMap::calc_pg_upmaps. پس از محاسبهی PGهای هر OSD و انحراف هر OSD از هدف وزنی، در هر دور مجموعههای overfull و underfull ساخته میشوند. سپس از پرترین OSD، نخست حذف upmapهای قبلی و بعد افزودن یک جفت تازه آزموده میشود. هر تغییر فقط وقتی پذیرفته میشود که امتیاز توازن بدتر نشود |
| ۲۸-۱۰ | قانون MSR برای EC 4+2 روی سه host. خروجی ششخانهای به سه stride دوتایی تقسیم میشود و hostهای هر stride از strideهای دیگر جدا نگه داشته میشوند. وقتی osd.7 out است، شاخص ۳ در این دور خالی میماند و در دور بعدی نزول با شمارهی تلاش تازه از ریشه تکرار میشود. جایگزین میتواند در همان host یا در hostی باشد که در stride دیگری استفاده نشده است |
| ۲۹-۱ | مسیر یک نوشتن در krbd. برنامه روی /dev/rbd0 یا فایلسیستمی روی آن مینویسد. لایهی block درخواستها را از طریق tag_set مربوط به blk-mq (با عمق queue_depth، پیشفرض BLKDEV_DEFAULT_RQ) به rbd_queue_rq میدهد. rbd.c هر request را به یک img_request تبدیل و آن را بر اساس اندازهی شیء به obj_requestها تقسیم میکند. هر obj_request یک ceph_osd_request است که osd_client با CRUSH درون هسته به primary OSD میفرستد |
| ۲۹-۲ | ماشینهای وضعیت در rbd.c. img_request اگر exclusive-lock فعال باشد، ابتدا قفل را میگیرد، سپس همهی obj_requestها را اجرا میکند و منتظر میماند. هر نوشتن شیء در صورت فعال بودن object-map ابتدا آن را بهروز میکند، سپس روی OSD مینویسد. در clone، اگر شیء هنوز وجود نداشته باشد، copyup دادهی parent را همراه نوشتن میفرستد و در پایان object-map نهایی میشود. خواندن اگر در clone با -ENOENT روبهرو شود، به parent میرود |
| ۲۹-۳ | هماهنگی caps در CephFS هستهای. کلاینت A فایل را برای نوشتن باز میکند و چون تنها نویسنده است، caps کامل، از جمله Fc (کش خواندن) و Fb (بافر نوشتن)، میگیرد. پس نوشتنها در page cache میمانند. وقتی کلاینت B همان فایل را باز میکند، MDS از A میخواهد Fc و Fb را پس بدهد. A صفحههای dirty را مستقیم به OSDها مینویسد و اندازه و mtime را به MDS گزارش میکند. سپس B فقط Fr میگیرد و هر خواندن را مستقیم و همگام از OSD انجام میدهد |
| ۲۹-۴ | مقایسهی کلاینتهای هسته و فضای کاربر. krbd و ceph.ko روی libceph ساخته شدهاند که پیادهسازی مستقلی در C است و ویژگیهایش با نسخهی هسته محدود میشود. librbd و ceph-fuse روی librados و Objecter و AsyncMessenger ساخته شدهاند، همهی ویژگیها (journaling، mirroring، QoS، کش) را دارند و بدون reboot ارتقا مییابند |
| ۲۹-۵ | محاسبهی striping در هسته. آفست به شمارهی بلوک (su)، شمارهی stripe و مجموعهی شیء تبدیل میشود. در مثال، یک خواندن ۳MiB از آفست ۱۰.۵MiB با layout برابر ۱MiB/4/4MiB به چهار تکه در چهار شیء مختلف میشکند. پایین شکل قالب نام شیء در RBD v2، RBD v1 و CephFS را نشان میدهد |
| ۲۹-۶ | انتخاب OSD در libceph. calc_target با hash نام شیء seed خام را میسازد، با ceph_stable_mod آن را به pg_num نگاشت میدهد، با crush_hash32_2 و شناسهی pool مقدار pps را میسازد و CRUSH و سپس upmap و primary_temp را اعمال میکند. با هر osdmap جدید، scan_requests درخواستهای در جریان را دوباره ارزیابی و در صورت تغییر target دوباره ارسال میکند. درخواست بدون OSD در دسترس در homeless_osd منتظر میماند |
| ۲۹-۷ | نگاشت یک نوشتن ۲۵۶ کیلوبایتی روی دستگاه krbd با striping سفارشی. ردیف بالا تکههای درخواست را در فضای تصویر و شیء مقصد هرکدام را نشان میدهد. ردیف پایین همان تکهها را درون چهار شیء نشان میدهد. دو تکهی شیء ۰ که در فضای تصویر ۲۲۴K از هم فاصله دارند، درون شیء کنار هم میافتند و ceph_file_to_extents آنها را به یک extent تبدیل میکند. پس یک obj_request با دو bio_vec ساخته میشود |
| ۲۹-۸ | شاخههای تصمیم calc_target در libceph. ردیف بالا ورودیها را از نقشهی تازه میخواند: وجود pool، شمارندهی force resend و نگاشت تازهی PG. ردیف میانی سه نوع تغییر را میسنجد. ردیف پایین نتیجه است: هر تغییر معنادار درخواست را از session قبلی جدا و به session OSD تازه وصل میکند. تغییرهای بیربط هیچ هزینهای ندارند |
| ۲۹-۹ | خط زمانی handle_timeout در osd_client. خطچینهای آبی دورهای پنجثانیهای هستند. درخواست A بهموقع تمام شده است. درخواست B از دور دهم کند شمرده میشود و keepalive برای OSD آن فرستاده میشود. session بیکار osd.3 تا گذشتن lru_ttl نگه داشته میشود و سپس در دور بعدی handle_osds_timeout بسته میشود |
| ۲۹-۱۰ | دو شاخهی گرفتن قفل در krbd. اگر مالک فعلی watcher زنده روی rbd_header داشته باشد، krbd با notify از او درخواست رها کردن میکند و صبر میکند. اگر watcherی نباشد، مالک مرده فرض میشود: آدرسش در mon blocklist میشود تا نتواند نوشتن معلقی را تمام کند، و سپس قفل شکسته و گرفته میشود. ترتیب blocklist پیش از break_lock برای جلوگیری از نوشتن همزمان دو میزبان حیاتی است |
| ۲۹-۱۱ | چیدمان بیتهای cap در یک عدد صحیح و مثال ۰x3F55. بیت ۰ PIN است. هر کدام از AUTH، LINK و XATTR دو بیت (shared و exclusive) دارند. FILE از بیت ۸ به بعد هشت بیت دارد: s، x، c، r، w، b، a و l. خانههای روشن بیتهای فعال مثالاند. ceph_cap_string همین بیتها را به رشتهای تبدیل میکند که در debugfs و لاگهای MDS دیده میشود |
| ۳۰-۱ | مقایسهی مدل اجرای OSD کلاسیک و Crimson. در OSD کلاسیک هر درخواست از چند thread pool و چند قفل عبور میکند. در Crimson هر هسته یک reactor مستقل دارد که هم شبکه و هم PGهای خودش را اجرا میکند. ارتباط بین هستهها فقط با پیام صریح (submit_to و invoke_on) است و نتیجه بهصورت future به هستهی مبدأ برمیگردد |
| ۳۰-۲ | مسیر یک درخواست روی یک reactor. هیچ مرحلهای منتظر نمیماند. هر عمل ناهمگام future برمیگرداند و continuation پس از آماده شدن نتیجه در صف همان هسته قرار میگیرد. چون زمانبندی تعاونی است، هر task باید زود تمام شود. Crimson سهم زمانی هر task را با crimson_reactor_task_quota_ms (پیشفرض ۰.۵ میلیثانیه در main) تنظیم میکند |
| ۳۰-۳ | نگاشت PG به هسته در Crimson. اگر هستهای PG را در نقشهی محلی نیابد، از هستهی ۰ میپرسد. هستهی ۰ برای PG تازه کمبارترین هسته را با min_element روی شمار PGهای هر هسته انتخاب، شمارنده را افزایش و نقشه را به همهی هستهها پخش میکند. از آن پس همهی کارهای آن PG فقط روی همان هسته اجرا میشود |
| ۳۰-۴ | phaseهای یک ClientRequest در Crimson. سطح اتصال (منتظر فعال شدن، دریافت نقشه و یافتن هستهی PG)، سطح شارد (ساختن یا انتظار برای PG)، سطح PG (آماده بودن PG، gate نسخهی OSDMap، فعال بودن PG و گرفتن object context) و سطح شیء (پردازش، انتظار برای scrub، انتظار برای تکرار در replicaها و ارسال پاسخ). phaseهای exclusive ترتیب را تضمین میکنند و concurrentها همپوشانی را ممکن میکنند |
| ۳۰-۵ | پشتیبانهای ذخیرهسازی Crimson. SeaStore و CyanStore بومیاند و مستقیماً روی reactor اجرا میشوند. AlienStore، BlueStore یا MemStore کلاسیک را در threadهای «بیگانه» (خارج از Seastar) اجرا میکند. درخواست از reactor به thread pool فرستاده میشود و نتیجه با seastar::alien::submit_to به هستهی مبدأ برمیگردد |
| ۳۰-۶ | لایههای SeaStore. مدیرهای منطقی (onode با درخت FLTree، omap و xattr با BTree، دادهی شیء و collectionها) روی آدرس منطقی laddr کار میکنند. TransactionManager تغییرات را در یک Transaction جمع و بهصورت یک record اتمیک ثبت میکند. LBAManager نگاشت laddr به paddr را در یک B-tree نگه میدارد و BackrefManager نگاشت معکوس را برای GC. Journal، ExtentPlacementManager، Cache و AsyncCleaner روی دستگاههای segmented، RBM یا ZBD کار میکنند |
| ۳۰-۷ | ساختار ۶۴ بیتی paddr_t در seastore_types.h. هشت بیت بالایی device_id است (DEVICE_ID_BITS = digits(uint۸_t) = ۸). در دستگاه segmented، ۲۴ بیت بعدی شمارهی segment درون دستگاه و ۳۲ بیت پایینی آفست درون segment است. در RBM، ۵۶ بیت پایینی آفست مطلق است. هفت مقدار بالای device_id برای آدرسهای نمادین مثل آفست نسبی درون record یا آدرس تأخیری رزرو شدهاند |
| ۳۰-۸ | ساختار سگمنت journal و record group. هر record group با یک نوشتن روی دیسک ثبت میشود و شامل record_group_header_t (تعداد recordها، طول فراداده و داده، nonce سگمنت، committed_to و crc داده)، سپس header هر record (نوع تراکنش، شمار delta و extent، زمان تغییر)، deltaها و extentهای inline است. RecordSubmitter تا ۱۶ record را در یک batch جمع و حداکثر ۵ نوشتن همزمان روی journal نگه میدارد (پیشفرضهای main) |
| ۳۰-۹ | ماشین حالت extentها در Cache مربوط به SeaStore. extent تازه در INITIAL_WRITE_PENDING ساخته و پس از commit تمیز (CLEAN) میشود. تغییر یک extent موجود نسخهای در MUTATION_PENDING میسازد که پس از commit با ثبت delta در journal، DIRTY میشود (محتوای حافظه با دیسک متفاوت و pinned در حافظه). extentهایی که فرادادهشان هنوز ثبت نشده EXIST_CLEAN و EXIST_MUTATION_PENDING هستند. DIRTYها با بازنویسی توسط cleaner یا trim مربوط به journal دوباره CLEAN میشوند |
| ۳۱-۱ | مقایسهی دو مسیر از BlueStore تا کنترلر NVMe. در مسیر هسته، درخواست از syscall، لایهی blk-mq و درایور nvme-pci میگذرد و تکمیل با وقفهی MSI-X گزارش میشود. در مسیر SPDK، همهی این کارها در فضای کاربر انجام میشود: درایور SPDK خود SQE را در صف مینویسد، doorbell را از راه BAR نگاشتهشدهی دستگاه بهروز میکند و تکمیل را با polling پیدا میکند. هر دو مسیر روی همان ساختار سختافزاری (SQ و CQ در حافظهی میزبان) به هم میرسند |
| ۳۱-۲ | ساختار ۶۴ بایتی فرمان Read/Write در صف ارسال، با مقادیر یک خواندن ۴ KiB از آفست ۱ GiB. opcode=0x02 یعنی Read (۰x۰۱ Write است). command_id شناسهای است که درایور انتخاب میکند و کنترلر در CQE برمیگرداند. slba آدرس منطقی آغاز بر حسب بلوک است و length تعداد بلوکها بهصورت صفرمبنا. prp1 و prp2 آدرسهای فیزیکی (یا IOVA پشت IOMMU) صفحههای دادهاند |
| ۳۱-۳ | ساختار ۱۶ بایتی ورودی صف تکمیل. sq_head به میزبان میگوید کنترلر تا کجای SQ را خوانده است. sq_id و command_id با هم فرمان را یکتا مشخص میکنند. بیت صفر فیلد status همان Phase Tag است: کنترلر در دور اول صف آن را ۱ مینویسد، در دور دوم ۰، و همینطور تا آخر |
| ۳۱-۴ | توالی کامل یک فرمان: نوشتن SQE در خانهی sq_tail، بهروزرسانی SQ Tail Doorbell، واکشی و اجرای فرمان توسط کنترلر، نوشتن CQE، وقفه (یا polling)، پیمایش CQ و بهروزرسانی CQ Head Doorbell. آفست doorbellها از ثبات CAP.DSTRD به دست میآید |
| ۳۱-۵ | مسیر کد NVMEDevice در سه مرحله. در باز کردن، رشتهی transport ID از فایل خوانده و با spdk_nvme_transport_id_parse تجزیه میشود. NVMEManager یک thread جدا برای محیط DPDK و probe میسازد. در نوشتن، داده به تکههای ۱۲۸ KiB تقسیم و در یک فهرست پیوندی Task در IOContext جمع میشود. aio_submit روی یک qpair مخصوص همان thread (thread_local) فرمانها را میفرستد. تکمیل در همان thread با polling پیدا میشود و بافرهای DMA به مخزن برمیگردند |
| ۳۱-۶ | اجزای درگاه NVMe-oF. هدف SPDK (nvmf_tgt) فرمانهای NVMe/TCP را از میزبان میگیرد و هر namespace را به یک bdev از نوع rbd میدهد. این bdev با librbd ناهمگام (rbd_aio_readv و rbd_aio_writev) کار میکند. سرویس gRPC درگاه پیکربندی را در یک شیء omap در RADOS نگه میدارد. NVMeofGwMonitorClient با mon در ارتباط است و وضعیت ANA هر گروه را از NVMeofGwMap میگیرد |
| ۳۱-۷ | بایتهای NVMe/TCP. هر PDU با هدر مشترک ۸ بایتی (type، flags، hlen، pdo، plen) شروع میشود. Command Capsule همان SQE ۶۴ بایتی را حمل میکند و اگر داده از in_capsule_data_size کوچکتر باشد، داده هم درون کپسول میآید. برای دادهی بزرگتر، هدف با یک R2T اجازهی ارسال میدهد و میزبان داده را در H2CData میفرستد |
| ۳۱-۸ | ماشین حالت هر درگاه برای هر گروه ANA در gw_states_per_group_t (NVMeofGwTypes.h): IDLE، STANDBY، ACTIVE، OWNER_WAIT_FAILBACK_PREPARED، WAIT_FAILBACK_PREPARED و WAIT_BLOCKLIST_CMPL. حالت صادرشده به میزبان فقط دو مقدار دارد: OPTIMIZED برای ACTIVE و WAIT_BLOCKLIST_CMPL، و INACCESSIBLE برای بقیه |
| ۳۱-۹ | توالی failover. پس از mon_nvmeofgw_beacon_grace (پیشفرض ۷ ثانیه) بدون beacon، درگاه A ناموجود اعلام میشود. find_failover_candidate کمبارترین درگاه را پیدا میکند، A blocklist میشود و B در WAIT_BLOCKLIST_CMPL با تایمر ۳۰ ثانیه قرار میگیرد. با رسیدن OSDMap تازه به B، گروه برای B فعال و ANA میزبان بهروز میشود |
| ۳۲-۱ | توزیع زمان scrub بعدی با پیشفرضهای main. چپ: shallow با osd_scrub_min_interval=1 روز و ratio=0.5، یکنواخت میان ۲۴ و ۳۶ ساعت پس از scrub قبلی. راست: deep با osd_deep_scrub_interval=7 روز و cv=0.2، نرمال با میانگین ۷ روز و انحراف ۱.۴ روز که به [۴.۲، ۹.۸] روز بریده میشود. بریدن، دو قلهی کوچک در لبهها میسازد که هرکدام حدود ۲.۳٪ احتمال دارد. خطچین مرز هشدار PG_NOT_DEEP_SCRUBBED است |
| ۳۲-۲ | ماشین حالت ScrubMachine در scrub_machine.h. سمت primary از NotActive به PrimaryActive میرود، در Session رزرو replicaها را میگیرد و در ActiveScrubbing چرخهی chunkها را اجرا میکند: PendingTimer (خواب میان chunkها)، NewChunk (انتخاب بازه)، WaitPushes و WaitLastUpdate (صبر برای pushها و نوشتنهای در جریان)، BuildMap، WaitReplicas و مقایسه. پایان PG با WaitDigestUpdate است. سمت replica حالتهای ReplicaIdle، ReplicaWaitUpdates و ReplicaBuildingMap را دارد |
| ۳۲-۳ | توالی یک chunk. primary فهرست اشیای بازه را از PGBackend میگیرد، مرز را طوری تنظیم میکند که head و cloneهایش در یک chunk بمانند، بازه را قفل میکند و به replicaها MOSDRepScrub میفرستد. هر طرف نقشهی خود را میسازد (در deep، خواندن داده در گامهای ۴ MiB). سپس primary نقشهها را مقایسه میکند. نوشتن مشتری در بازهی قفلشده chunk را preempt میکند |
| ۳۲-۴ | منطق انتخاب نسخهی معتبر در ScrubBackend::select_auth_object. نامزدها با خود primary شروع میشوند. هر shard با خطای خواندن، خطای stat، ناهمخوانی hash یا اندازهی EC، یا object_info و snapset خراب رد میشود. از میان shardهای قابلاستفاده، نسخهی بزرگتر object_info برنده است. در تساوی، shardی که digest بیشتری ثبت کرده برنده است، و در تساوی کامل، اولی (primary) |
| ۳۲-۵ | حالتهای recovery و backfill درون Active در PeeringState.h. هر دو مسیر ابتدا رزرو محلی (روی primary) و سپس رزرو راه دور (روی هر هدف) میگیرند. اگر رزرو رد یا پس گرفته شود، PG به NotRecovering یا NotBackfilling میرود. اگر هدف پر باشد، حالت toofull میگیرد و پس از osd_recovery_retry_interval یا osd_backfill_retry_interval (هر دو ۳۰ ثانیه) دوباره تلاش میکند |
| ۳۲-۶ | بازههای اولویت در osd_types.h. هر پایه یک سقف دارد (get_max_prio_for_base) تا اولویت pool یک PG را به ردهی بالاتر نبرد: backfill عادی ۱۰۰ تا ۱۳۹، backfill با کمبود replica 140 تا ۱۷۹، recovery 180 تا ۲۱۹، و PG غیرفعال (کمتر از min_size) 220 تا ۲۵۳. مقادیر ۲۵۴ و ۲۵۵ برای force-backfill و force-recovery است. اولویتهای حذف PG (۱۷۹، ۲۱۹، ۲۵۵) هم در همین صف رقابت میکنند |
| ۳۲-۷ | توالی رزرو backfill. primary ابتدا یک خانه از local_reserver خودش و سپس با MBackfillReserve REQUEST یک خانه از remote_reserver هدف میگیرد. هر دو به osd_max_backfills محدودند. درخواست با اولویت بالاتر روی هدف، رزرو قابلpreempt کماولویتتر را پس میگیرد و primary پیام REVOKE دریافت میکند |
| ۳۲-۸ | منطق ادغام در recover_backfill. primary یک بازه از اشیای خودش (backfill_info) و از هر هدف یک بازه (peer_backfill_info) را با اسکن ۶۴ تا ۵۱۲ شیئی (osd_backfill_scan_min/max) نگه میدارد. هر دو به ترتیب hobject مرتباند. شیئی که فقط روی هدف است حذف میشود. شیئی که نسخهاش فرق دارد push میشود. شیئی که نسخهاش برابر است نگه داشته میشود. اشیای پس از last_backfill هدف بهعنوان missing push میشوند |
| ۳۲-۹ | جریان ceph pg repair. فوریت هدف scrub به must_repair میرود و deep scrub کامل اجرا میشود. برای هر شیء ناسازگار، ScrubBackend::repair_object با force_object_missing shardهای خراب را به فهرست missing اضافه میکند و فقط shardهای سالم را منبع missing_loc قرار میدهد. سپس سازوکار عادی recovery نسخهی معتبر را push میکند |
| ۳۳-۱ | مهمترین threadهای ceph-osd با نامی که در top -H یا perf دیده میشود. msgr-worker-N (از Stack.h) پیامها را میخوانند و رمزگشایی میکنند. tp_osd_tp (thread pool مربوط به OSD::osd_op_tp) opها را از ShardedOpWQ اجرا میکند. bstore_aio تکمیل I/O را از هسته میگیرد. bstore_kv_sync تراکنشها را دستهای در RocksDB ثبت میکند. bstore_kv_final و cfin callbackها را اجرا میکنند. bstore_mempool حافظه را تنظیم میکند، rocksdb:low/high فشردهسازی و flush انجام میدهند و osd_srv_heartbt ضربان را میفرستد |
| ۳۳-۲ | نمونهای از همنامی (aliasing) در نگاشت PG به shard. PGهایی که ps آنها در پیمانهی ۸ برابر است (۳، ۱۱، ۱۹، ۲۷) همگی در shard 3 قرار میگیرند. اگر این PGها پربار باشند، دو thread مربوط به shard 3 اشباع میشوند در حالی که ۱۴ thread دیگر بیکارند |
| ۳۳-۳ | ساختار یک shard_t در mempool.h. هر shard آرایهای از stats_t برای همهی poolهاست و با aligned(۱۲۸) شروع میشود تا دو shard هرگز یک cache line را به اشتراک نگذارند (توضیح کد، ۱۲۸ بایت را بزرگترین اندازهی cache line در پردازندههای شناختهشده میداند). مقدار کل یک pool جمع همهی shardهاست |
| ۳۳-۴ | سطوح tcmalloc. تخصیص کوچک از cache همان thread و بدون قفل انجام میشود. کمبود از transfer cache و central free list پر میشود و آنها از page heap span میگیرند. حافظهی آزاد page heap با madvise به سیستمعامل پس داده میشود و در شمارندهی pageheap_unmapped_bytes دیده میشود. PriorityCache مقدار «mapped» را heap_size منهای همین unmapped حساب میکند |
| ۳۳-۵ | شبیهسازی هشت دور tune_memory با پیشفرضها و مقادیر ورودی فرضی برای mapped. از ۱۵۰۰ MiB، چون mapped=3000 کمتر از هدف است، بودجه به ۱۸۲۴.۷ بالا میرود. در دور بعد mapped=4500 از هدف بیشتر است و بودجه به ۱۶۷۲.۴ کاهش مییابد. بودجه هرگز از max=2713.6 بالاتر و از min=128 پایینتر نمیرود |
| ۳۳-۶ | نتیجهی مثال balance پس از PRI0 و دو دور PRI1. kv به ۵۰۰ MiB، meta به ۷۲۳.۳، data به ۹۶.۴ و kv_onode به ۵۰ مگابایت رسیدهاند. چون cacheهای meta و data هنوز دادهی تازهی بیشتری از بودجه دارند، binهای قدیمیترشان (اولویتهای پایینتر) در commit بعدی بیرون رانده میشوند |
| ۳۳-۷ | تصمیم وابستگی NUMA در OSD::set_numa_affinity. OSD گره NUMA دستگاه ذخیرهسازی (get_numa_node) و گره رابطهای شبکهی public و cluster (get_iface_numa_node) را پیدا میکند. اگر هر سه یکسان باشند و osd_numa_auto_affinity=true باشد، همهی threadها به CPUهای همان گره محدود میشوند. osd_numa_node اگر صریحاً تنظیم شود، بر تصمیم خودکار مقدم است |
| ۳۳-۸ | ابزارها به ترتیب لایه. درون Ceph: dump_mempools، heap stats، perf dump و dump_historic_ops. نمونهبرداری CPU با perf و flame graph. ردیابی رویداد و زمان off-CPU با bpftrace. ردیابی تابعبهتابع با uftrace. و پشتهی همهی threadها با gdb یا eu-stack برای حالت گیرکرده |
| ۳۳-۹ | چپ: بیشینه و میانگین طول pg log در ۳۲ PG pool m33 برحسب تعداد نوشتن، با خطچین بودجهی هر OSD (۳۰۰۰۰ تقسیم بر تعداد PGها). راست: حجم mempool osd_pglog (خط پر) و bluestore_cache_onode (خطچین) برای هر OSD. خط نقطهچین مرز بازنویسی ۱۰۰۰ شیء و ساختن شیء تازه است |
| ۳۳-۱۰ | هزینهی واقعی هر آیتم (بایت تقسیم بر تعداد آیتم) در mempoolهای اصلی osd.0 روی کلاستر آزمایشی Ceph 18.2.7، همراه با تعداد آیتمها و حجم کل هر mempool |
| ۳۴-۱ | کلید بیتی و بازهی PGها. پنج بیت پایین hash شمارهی PG را در pg_num=32 تعیین میکنند. پس از برعکس کردن بیتها، همین پنج بیت به بالای کلید میروند و هر PG یک بازهی پیوسته در ترتیب hobject_t میشود. با دوبرابر شدن pg_num، هر بازه دقیقاً از وسط نصف میشود |
| ۳۴-۲ | مسیر op درون OSD از messenger تا commit. ms_fast_dispatch پیام را بدون قفل سراسری به shard میسپارد؛ thread همان shard قفل PG را میگیرد و do_op دروازهها را بررسی میکند. اگر شرطی برقرار نباشد، op در صف انتظار مربوط میماند. در غیر این صورت execute_ctx تراکنش را میسازد، issue_repop آن را به replicaها میفرستد و پاسخ پس از commit همهی اعضای acting ارسال میشود |
| ۳۴-۳ | توالی یک نوشتن replicated سهنسخهای. primary تراکنش را محلی ثبت و همزمان برای دو replica میفرستد. هر عضو پس از ثبت در WAL مربوط به BlueStore پاسخ ONDISK میدهد. پاسخ به کلاینت فقط پس از رسیدن همهی پاسخها فرستاده میشود؛ min_size فقط تعیین میکند PG با چند عضو اصلاً نوشتن را بپذیرد |
| ۳۴-۴ | چیدمان کلید یک ورودی pg log در omap شیء pgmeta، بایت به بایت. ده رقم epoch با صفر ابتدایی، یک نقطه و بیست رقم version. کلید dup همان قالب را با پیشوند چهاربایتی dup_ دارد (یادداشت زیر شکل). طول ثابت باعث میشود ترتیب رشتهای با ترتیب عددی یکی باشد |
| ۳۵-۱ | زمانخط intervalهای یک PG. هر تغییر در up یا acting یا min_size یک interval تازه میسازد. فقط intervalهایی که primary در آن up_thru گرفته یا PG در آن clean شده maybe_went_rw هستند و در prior set حساب میشوند. intervalی که acting آن از min_size کوچکتر بوده نادیده گرفته میشود |
| ۳۵-۲ | ادغام log با ورودی divergent. عضو A تا ۱۴۶۲'۹۵ نوشته ولی log معتبر عضو B تا ۱۴۶۲'۹۳ است و سپس در epoch جدید ۱۴۷۰'۹۴ و ۱۴۷۰'۹۵ دارد. ورودیهای ۱۴۶۲'۹۴ و ۱۴۶۲'۹۵ روی A divergent هستند، برگردانده یا حذف میشوند و شیء مربوط با نسخهی prior در missing مینشیند |
| ۳۵-۳ | backfill بهصورت ادغام دو پیمایش مرتب. بازهی primary و بازهی هدف همزمان جلو میروند. شیء موجود فقط در هدف حذف میشود، شیء فقط در primary یا با نسخهی متفاوت push میشود، و last_backfill پس از هر دور جلو میرود |
| ۳۶-۱ | تحول SnapSet یک شیء در سه گام. هر نخستین نوشتن پس از یک یا چند snapshot تازه یک clone با نام بزرگترین snap جدید میسازد. clone_snaps نشان میدهد هر clone نمایندهی کدام snapهاست و clone_overlap بخشهایی را که هنوز با نسخهی جدیدتر مشترکاند |
| ۳۶-۲ | جریان حذف snapshot. mon شماره را به removed_snaps_queue اضافه میکند، هر PG از روی SnapMapper cloneهای آن snap را پیدا و trim میکند و در پایان mon آن را purged میشمارد |
| ۳۷-۱ | چیدمان یک image با data pool جدا. pool فرادادهی replicated شامل rbd_id، rbd_directory، header و object mapهاست و pool دادهی EC اشیای rbd_data با پیشوند شناسهی pool فراداده را دارد. کلاینتها روی header watch میکنند و قفل exclusive روی xattr همان header است |
| ۳۷-۲ | striping با stripe_unit=64K و stripe_count=4. هر سطر یک stripe است و چهار ستون چهار شیء یک object set. بلوکهای ۱۵ تا ۱۹ که نوشتن مثال را میسازند رنگی شدهاند |
| ۳۷-۳ | کدگذاری object map یک image دهگیگابایتی با اشیای ۴ MiB (۲۵۶۰ شیء)، بایت به بایت. header هجدهبایتی شامل طول، struct_v و compat و تعداد عناصر است؛ سپس ۶۴۰ بایت داده که هر بایت چهار شیء را با دو بیت نگه میدارد و در پایان footer با crcها |
| ۳۸-۱ | لایههای dispatch در librbd. درخواست تصویر از نُه لایهی image عبور و در CORE با Striper به extentهای شیء شکسته میشود؛ هر extent سپس از شش لایهی شیء عبور میکند تا بهصورت op به RADOS برسد. لایههای غیرفعال (مثلاً journal یا crypto) در زنجیره نیستند |
| ۳۸-۲ | copyup برای نخستین نوشتن روی شیء ۱۲۸۰ یک clone. librbd از object map میفهمد شیء وجود ندارد، شیء والد را با خواندن پراکنده میآورد، object map را به EXISTS میبرد و copyup و نوشتن کاربر را در یک op اتمی به شیء clone میفرستد |
| ۳۸-۳ | گرفتن exclusive-lock. اگر قفل گرفته شده باشد، کلاینت B بررسی میکند مالک هنوز watch زنده دارد یا نه. در حالت زنده، با notify درخواست رها کردن میفرستد؛ در غیر این صورت ابتدا مالک را blocklist و سپس قفل را میشکند |
| ۳۹-۱ | یک رویداد journal، بایت به بایت. پیشدرآمد ثابت هشتبایتی برای بازیابی مرز رویدادها، نسخه، شناسهی رویداد و tag، طول داده، دادهی رمزگذاریشدهی EventEntry در librbd و crc32c کل بخش قبلی |
| ۳۹-۲ | دو حالت mirroring در کنار هم. در حالت journal هر نوشتن اول در journal مبدأ ثبت و rbd-mirror رویدادها را پیوسته روی مقصد replay میکند. در حالت snapshot زمانبند snapshot mirror میسازد و rbd-mirror فقط اشیای تغییرکرده میان دو snapshot را با fast-diff کپی میکند |
| ۴۰-۱ | کلیدهای ویژه در omap یک shard ایندکس. کلید instance از ۰x80، پیشوند ۱۰۰۰_، نام، بایت صفر، حرف i و شناسهی نسخه ساخته میشود؛ OLH فقط پیشوند ۱۰۰۱_ و نام دارد؛ bilog شمارهی index_ver صفرپرشدهی یازدهرقمی دارد تا ترتیب واژگانی همان ترتیب عددی باشد |
| ۴۰-۲ | توالی بهروزرسانی دومرحلهای: prepare برچسب عملیات را در pending_map میگذارد، داده نوشته میشود، complete برچسب را برمیدارد و ورودی، آمار و bilog را بهروز میکند. اگر complete نرسد، فهرستگیری بعدی وضعیت head را بررسی و با dir_suggest_changes ورودی را اصلاح میکند |
| ۴۰-۳ | چیدمان یک شیء ۱۰ MiB در دو حالت. در STANDARD، head چهار مگابایت اول را دارد و دو tail بقیه را. در COLD، head فقط manifest و xattrها را در pool STANDARD نگه میدارد و همهی داده در سه tail در pool COLD است |
| ۴۱-۱ | خط لولهی یک درخواست در RGW: Beast، انتخاب handler و op، احراز هویت، خواندن مجوزها، بررسیها، محدودیت نرخ، اجرا و تکمیل. خطای هر مرحله مستقیم به پاسخ HTTP میرود |
| ۴۱-۲ | مشتقگیری کلید SigV4: چهار HMAC زنجیرهای از کلید محرمانه با تاریخ، region، سرویس و aws4_request، و HMAC پنجم روی string to sign که خودش هش canonical request را دارد |
| ۴۱-۳ | زمانبندی gc و lifecycle. حذف شیء زنجیرهی tail را در صف یک شیء gc.N با زمان انقضای دوساعته میگذارد و نخ gc پس از قفل کردن shard آن را پاک میکند. نخ lifecycle در پنجرهی کاری روزانه shardهای lc.N را قفل میکند و برای هر bucket قواعد را اجرا میکند |
| ۴۲-۱ | زنجیرهی سه لاگ برای یک PUT در zone مبدأ. complete در ایندکس یک ورودی bilog مینویسد، RGW کلید bucket shard را در datalog ثبت میکند و zone مقصد datalog را میخواند، bilog همان shard را از marker خود به بعد میگیرد و شیء را با fetch_remote_obj کپی میکند. تغییرهای فراداده جداگانه از mdlog period جاری خوانده میشوند |
| ۴۲-۲ | انتقال میان نسلهای bilog پس از reshard: نسل ۰ با ۱۱ shard و نسل ۱ با ۲۳ shard. zone مقصد همهی shardهای نسل ۰ را تا انتها همگام و در shards_done_with_gen علامت میزند و سپس incremental_gen را به ۱ میبرد؛ لاگ نسل ۰ فقط پس از اتمام همهی peerها trim میشود |