راهنمای جامع Ceph
Englishشروع کنید
در این صفحه
فصل ۱۳

کدگذاری پاک‌شونده (‏Erasure Coding‏) برای بهره‌وری ذخیره‌سازی

۴۱ دقیقه مطالعهویرایش این صفحه
حوصله ندارم این فصل در چند خط
  • ‏erasure coding‏ داده را به ‏k‏ تکه‌ی داده و ‏m‏ تکه‌ی ‏parity‏ می‌شکند؛ هر ‏k‏ تکه برای بازسازی کافی است، پس ‏m‏ خرابی با فضای خام بسیار کمتر از ‏replication‏ تحمل می‌شود.
  • در ‏pool‏ از نوع ‏EC‏ هر ‏OSD‏ در ‏acting set‏ یک ‏shard‏ را نگه می‌دارد؛ ‏RBD‏ و ‏CephFS‏ روی ‏EC‏ به ‏allow_ec_overwrites‏ نیاز دارند و ‏RBD‏ متادیتا را در ‏pool‏ از نوع ‏replicated‏ نگه می‌دارد.
  • در ‏Tentacle‏ افزونه‌ی پیش‌فرض کلاسترهای تازه ‏ISA-L‏ شد و ‏FastEC‏ (خواندن و نوشتن جزئی و ‏parity-delta‏) پشت ‏allow_ec_optimizations‏ آمد.
  • عدد ‏2147483647‏ در نگاشت ‏PG‏ همان ‏CRUSH_ITEM_NONE‏ است: ‏CRUSH‏ برای آن ‏shard‏ هیچ ‏OSD‏ی پیدا نکرده است.

۱۳-۱ پیش از شروع#

۱۳-۱-۱ این فصل به چه سؤال‌هایی جواب می‌دهد؟#

تکثیر سه‌تایی ساده و سریع است، اما برای هر ترابایت داده‌ی کاربر سه ترابایت دیسک مصرف می‌کند. کدگذاری پاک‌شونده (‏Erasure Coding‏ یا ‏EC‏) با همان یا حتی بیشتر از سطح تحمل خرابی، سربار را به حدود ۱.۵ برابر یا کمتر می‌رساند. بهای آن پیچیدگی بیشتر، مصرف ‏CPU‏ و تأخیر بالاتر در نوشتن‌های کوچک است. از ۲۰۱۹ تا امروز ‏EC‏ در ‏Ceph‏ دو تحول بزرگ داشته: پیش‌فرض شدن ‏ISA-L‏ به جای ‏Jerasure‏ و معرفی ‏FastEC‏ در ‏Tentacle‏.

  • ‏EC‏ چیست و پارامترهای ‏k‏ و ‏m‏ چه معنایی دارند؟
  • ‏EC‏ درون ‏Ceph‏ چطور پیاده شده است: ‏stripe‏، ‏shard‏ و ‏chunk‏؟
  • افزونه‌ها (‏ISA‏، ‏Jerasure‏، ‏LRC‏، ‏SHEC‏، ‏CLAY‏) چه تفاوتی دارند؟
  • کجا از ‏EC‏ استفاده کنیم و کجا نه؟
  • ‏pool EC‏ را چطور بسازیم و ‏overwrite‏ و ‏FastEC‏ را فعال کنیم؟
  • خطای معروف «۲۱۴۷۴۸۳۶۴۷» از کجا می‌آید؟

۱۳-۲ ‏EC‏ چیست؟#

۱۳-۲-۱ ‏k+m‏#

در ‏EC‏، داده به ‏k‏ قطعه‌ی داده تقسیم می‌شود و از آن‌ها ‏m‏ قطعه‌ی ‏parity‏ محاسبه می‌شود. هر ‏k‏ قطعه از ‏k+m‏ قطعه برای بازسازی کامل داده کافی است؛ بنابراین تا ‏m‏ خرابی هم‌زمان قابل تحمل است.

شکل ۱۳-۱: کدگذاری ۴+۲: یک stripe شانزده‌کیلوبایتی به چهار shard داده‌ی چهارکیلوبایتی تقسیم و دو shard parity از آن‌ها محاسبه می‌شود؛ هر shard روی یک OSD در دامنه‌ی خرابی جدا قرار می‌گیرد
شکل ۱۳-۱: کدگذاری ۴+۲: یک ‏stripe‏ شانزده‌کیلوبایتی به چهار ‏shard‏ داده‌ی چهارکیلوبایتی تقسیم و دو ‏shard parity‏ از آن‌ها محاسبه می‌شود؛ هر ‏shard‏ روی یک ‏OSD‏ در دامنه‌ی خرابی جدا قرار می‌گیرد
طرحسربار ذخیره‌سازیتحمل خرابیحداقل دامنه‌ی خرابی (‏host‏)
تکثیر ۳۳.۰×۲۳
‏EC 2+1‏۱.۵×۱۳
‏EC 4+2‏۱.۵×۲۶
‏EC 8+3‏۱.۳۷۵×۳۱۱
‏EC 8+4‏۱.۵×۴۱۲
⚠ هشدار

«حداقل دامنه‌ی خرابی» شوخی نیست: با ‏EC 8+3‏ و دامنه‌ی خرابی ‏host‏ به یازده میزبان نیاز دارید و برای اینکه پس از خرابی یک میزبان بتوان ‏shard‏ها را بازسازی کرد، دوازده میزبان یا بیشتر. ‏EC‏ روی کلاستر کوچک یا فشار به جای دامنه‌ی خرابی (مثلاً ‏crush-failure-domain=osd‏) دوام واقعی را از بین می‌برد.

آزمایشگاه

Replication یا erasure coding

k، m و plugin را انتخاب کنید و ظرفیت قابل‌استفاده، سربار، خرابی‌های قابل‌تحمل و هزینهٔ بازسازی یک chunk را مقایسه کنید.

۷۶۸ TBظرفیت خام
۸failure domainها

EC isa k=4 m=2

قابل‌استفاده
۵۱۲ TB (۶۶٫۷٪)
تا nearfull (۸۵٪)
۴۳۵٫۲ TB
بایت خام به ازای هر بایت مفید
۱٫۵×
از دست رفتن OSD/host بدون از دست رفتن داده
۲
size / min_size
۶ / ۵
از دست رفتن با ادامهٔ I/O
۱
خواندن برای بازسازی یک chunk
۴ chunk

تکراری، size برابر ۳

قابل‌استفاده
۲۵۶ TB (۳۳٫۳٪)
تا nearfull (۸۵٪)
۲۱۷٫۶ TB
بایت خام به ازای هر بایت مفید
۳×
از دست رفتن OSD/host بدون از دست رفتن داده
۲
size / min_size
۳ / ۲
از دست رفتن با ادامهٔ I/O
۱
خواندن برای بازسازی یک chunk
۱ chunk
usable = raw × k / (k + m) · EC min_size = k + min(1, m − 1) = 5 · replicated min_size = size − ⌊size / 2⌋ = 2 · repair read = k
ceph osd erasure-code-profile set prof-4-2 plugin=isa k=4 m=2 crush-failure-domain=host
ceph osd pool create ecpool erasure prof-4-2
ceph osd pool set ecpool allow_ec_overwrites true   # RBD, CephFS

profile پیش‌فرض در main برابر plugin=isa technique=reed_sol_van k=2 m=2 است؛ profile را پس از آن‌که poolی از آن استفاده کرد نمی‌توان تغییر داد.

مطابق با:

  • src/mon/OSDMonitor.cc :: prepare_pool_size size و min_size یک pool تازه
  • src/common/options/global.yaml.in :: osd_pool_default_erasure_code_profile, mon_osd_nearfull_ratio profile پیش‌فرض و نسبت‌های پرشدگی
  • src/erasure-code/lrc/ErasureCodeLrc.cc :: parse_kml قاعده‌های k، m و l در LRC
  • src/erasure-code/clay/ErasureCodeClay.cc :: parse بازهٔ d در CLAY
  • src/erasure-code/shec/ErasureCodeShec.cc :: parse قاعدهٔ c در SHEC
  • src/osd/OSDMap.cc :: pool_raw_used_rate فضای خام به ازای هر بایت (size یا (k+m)/k)
تصویری ثابت از شبیه‌ساز؛ نسخهٔ تعاملی در آزمایشگاه نسخهٔ وب است. آزمایشگاه تعاملی: Replication یا erasure coding

۱۳-۲-۲ ریاضیات به زبان ساده#

کدهای ‏Reed-Solomon‏ (که ‏ISA-L‏ و ‏Jerasure‏ پیاده می‌کنند) روی میدان گالوا (معمولاً ‏GF(28)\mathrm{GF}(2^8)‏) کار می‌کنند: هر بایت یک عنصر میدان است و جمع، ‏XOR‏ است. ‏k‏ بایت داده را بردار ‏d‏ در نظر بگیرید؛ ‏shard‏ها حاصل ضرب ماتریس مولد ‏G‏ با ابعاد (‏k+m‏)×‏k‏ در ‏d‏ هستند. ‏k‏ سطر اول ‏G‏ ماتریس همانی است (پس ‏shard‏های داده همان داده‌اند) و ‏m‏ سطر بعدی ضرایب ‏parity‏. برای بازسازی، از هر ‏k shard‏ در دسترس، زیرماتریس ‏k‏×‏k‏ متناظر را می‌گیریم و معکوس می‌کنیم. شرط کار این است که هر زیرماتریس ‏k‏×‏k‏ وارون‌پذیر باشد؛ ماتریس‌های ‏Vandermonde‏ (‏reed_sol_van‏) و ‏Cauchy‏ (‏cauchy‏) این ویژگی را دارند.

text
shard_i = Σ_j  G[i][j] ⊗ d_j      (⊗ ضرب در GF(2^8)، Σ همان XOR)
برای i < k:  G[i] = e_i  →  shard_i = d_i
decode:  d = (G_S)^-1 · shards_S     (S: هر k اندیس در دسترس)

۱۳-۳ ‏EC‏ درون ‏Ceph‏#

۱۳-۳-۱ ‏stripe‏، ‏shard‏ و ‏chunk‏#

شکل ۱۳-۲: چیدمان stripe ها: داده‌ی شیء به stripe هایی به طول k × stripe_unit تقسیم می‌شود؛ قطعه‌ی i هر stripe روی shard i قرار می‌گیرد و parity هر stripe جداگانه محاسبه می‌شود
شکل ۱۳-۲: چیدمان ‏stripe‏ها: داده‌ی شیء به ‏stripe‏هایی به طول ‏k‏×‏stripe_unit‏ تقسیم می‌شود؛ قطعه‌ی ‏i‏ هر ‏stripe‏ روی ‏shard i‏ قرار می‌گیرد و ‏parity‏ هر ‏stripe‏ جداگانه محاسبه می‌شود

در ‏pool EC‏، هر ‏PG‏ به جای «نسخه» ‏shard‏ دارد: ‏OSD‏ شماره‌ی ‏i‏ در ‏acting set‏، ‏shard i‏ است و فقط قطعه‌ی خودش را نگه می‌دارد. شیء به ‏stripe‏هایی به اندازه‌ی ‏k × stripe_unit‏ تقسیم می‌شود (پیش‌فرض ‏stripe_unit‏ چهار کیلوبایت). برای هر ‏stripe‏، ‏k‏ قطعه‌ی داده و ‏m‏ قطعه‌ی ‏parity‏ ساخته و قطعه‌ی ‏i‏ روی ‏shard i‏ ذخیره می‌شود. پس روی هر ‏OSD‏، شیء به شکل یک «شیء ‏shard‏» شامل همه‌ی قطعه‌های ‏i‏ آن شیء دیده می‌شود.

۱۳-۳-۲ ‏primary‏ در ‏EC‏#

‏primary‏ در ‏pool EC‏ مسئول ‏encode‏ و ‏decode‏ است: کلاینت فقط با ‏primary‏ صحبت می‌کند، ‏primary‏ داده را ‏encode‏ و قطعه‌ها را به ‏shard‏ها می‌فرستد. در خواندن، ‏primary‏ قطعه‌های لازم را از ‏shard‏ها جمع و در صورت نیاز ‏decode‏ می‌کند. این یعنی بار ‏CPU‏ و شبکه‌ی ‏EC‏ روی ‏primary‏ها متمرکز است.

شکل ۱۳-۳: خواندن تخریب‌شده ( degraded read ): وقتی shard 1 در دسترس نیست، minimum_to_decode مجموعه‌ای از k shard در دسترس را انتخاب و decode داده‌ی گم‌شده را بازسازی می‌کند
شکل ۱۳-۳: خواندن تخریب‌شده (‏degraded read‏): وقتی ‏shard 1‏ در دسترس نیست، ‏minimum_to_decode‏ مجموعه‌ای از ‏k shard‏ در دسترس را انتخاب و ‏decode‏ داده‌ی گم‌شده را بازسازی می‌کند

۱۳-۳-۳ نوشتن جزئی و ‏overwrite‏#

‏EC‏ در ابتدا (پیش از ‏Luminous‏) فقط ‏append‏ را پشتیبانی می‌کرد و برای ‏RBD‏ و ‏CephFS‏ قابل استفاده نبود. پرچم ‏allow_ec_overwrites‏ (از ‏Luminous‏، که سرفصل قدیمی آن را با ‏Kraken‏ مرتبط کرده) بازنویسی را ممکن کرد. اما در پیاده‌سازی قدیمی، بازنویسی ۴ کیلوبایت در وسط یک ‏stripe‏ یعنی ‏read-modify-write‏ کامل: خواندن بقیه‌ی ‏stripe‏، ‏encode‏ دوباره و نوشتن همه‌ی ‏shard‏ها.

شکل ۱۳-۴: مقایسه‌ی نوشتن کوچک در EC قدیمی و FastEC : پیاده‌سازی قدیمی کل stripe را می‌خواند و دوباره می‌نویسد؛ FastEC فقط shard تغییرکرده و parity ها را به‌روز می‌کند
شکل ۱۳-۴: مقایسه‌ی نوشتن کوچک در ‏EC‏ قدیمی و ‏FastEC‏: پیاده‌سازی قدیمی کل ‏stripe‏ را می‌خواند و دوباره می‌نویسد؛ ‏FastEC‏ فقط ‏shard‏ تغییرکرده و ‏parity‏ها را به‌روز می‌کند

۱۳-۴ افزونه‌ها و پروفایل‌ها#

شکل ۱۳-۵: افزونه‌های EC در Ceph : همه رابط مشترک ErasureCodeInterface را پیاده می‌کنند و به‌صورت کتابخانه‌ی پویا بارگذاری می‌شوند
شکل ۱۳-۵: افزونه‌های ‏EC‏ در ‏Ceph‏: همه رابط مشترک ‏ErasureCodeInterface‏ را پیاده می‌کنند و به‌صورت کتابخانه‌ی پویا بارگذاری می‌شوند
افزونهایدهکاربرد
‏isa‏‏Reed-Solomon‏ با کتابخانه‌ی ‏Intel ISA-L‏ (ماتریس ‏Vandermonde‏ یا ‏Cauchy‏)؛ پیش‌فرض کلاسترهای تازه از ‏Tentacle‏انتخاب پیش‌فرض؛ پشتیبانی از ‏FastEC‏
‏jerasure‏‏Reed-Solomon‏ و کدهای دیگر با کتابخانه‌ی ‏Jerasure‏؛ کتابخانه دیگر نگهداری نمی‌شودکلاسترهای قدیمی؛ ‏FastEC‏ فقط با ‏reed_sol_van‏
‏lrc‏‏Locally Repairable Code‏: گروه‌های محلی با ‏parity‏ محلیبازیابی با خواندن کمتر؛ کلاسترهای چندرکی
‏shec‏‏Shingled EC: parity‏های همپوشانکاهش هزینه‌ی بازیابی با هزینه‌ی کمی دوام
‏clay‏‏Coupled-layer‏: تقسیم ‏chunk‏ به ‏sub-chunk‏کاهش شدید ترافیک شبکه در بازیابی یک ‏shard‏
✎ یادداشت

طبق یادداشت انتشار ‏Tentacle‏، افزونه‌ی پیش‌فرض از ‏Jerasure‏ به ‏ISA-L‏ تغییر کرد. کلاسترهایی که روی ‏Tentacle‏ یا بعد ساخته شوند از ‏ISA-L‏ استفاده می‌کنند؛ کلاسترهای ارتقایافته پیش‌فرض قبلی را نگه می‌دارند. در کد جاری، ‏osd_pool_default_erasure_code_profile‏ برابر ‏plugin=isa technique=reed_sol_van k=2 m=2‏ است.

۱۳-۴-۱ ساخت پروفایل و ‏pool‏#

bashReef+
ceph osd erasure-code-profile ls
ceph osd erasure-code-profile get default
ceph osd erasure-code-profile set ec42 plugin=isa technique=reed_sol_van k=4 m=2 \
     crush-failure-domain=host crush-device-class=hdd
ceph osd pool create ecpool erasure ec42
ceph osd pool set ecpool allow_ec_overwrites true     # برای RBD و CephFS
ceph osd pool application enable ecpool rgw
⚠ هشدار

پروفایل یک ‏pool‏ پس از ساخت قابل تغییر نیست: ‏k‏، ‏m‏، افزونه و ‏stripe_unit‏ ثابت می‌مانند. برای تغییر باید ‏pool‏ تازه ساخت و داده را منتقل کرد. پیش از ساخت، رشد آینده‌ی کلاستر و تعداد دامنه‌های خرابی را در نظر بگیرید.

۱۳-۴-۲ ‏LRC‏ و ‏CLAY‏#

bashReef+
# LRC: k=4 داده، m=2 parity سراسری، l=3 → هر ۳ قطعه یک parity محلی
ceph osd erasure-code-profile set lrc42 plugin=lrc k=4 m=2 l=3 crush-failure-domain=host
# CLAY: d تعداد shardهایی که در بازیابی یک shard مشارکت می‌کنند
ceph osd erasure-code-profile set clay42 plugin=clay k=4 m=2 d=5 crush-failure-domain=host
◆ عمیق‌تر

در ‏Reed-Solomon‏ معمولی، بازسازی یک ‏shard‏ گم‌شده نیازمند خواندن ‏k shard‏ کامل است: برای ۸+۳ یعنی هشت برابر حجم داده‌ی گم‌شده روی شبکه. ‏LRC‏ با ‏parity‏ محلی این را به اندازه‌ی گروه محلی کاهش می‌دهد (با هزینه‌ی فضای بیشتر). ‏CLAY‏ هر ‏chunk‏ را به ‏sub-chunk‏ها تقسیم می‌کند و در بازیابی فقط بخشی از ‏sub-chunk‏های هر یک از ‏d‏ کمک‌کننده را می‌خواند؛ با همان سربار فضای ‏RS‏، ترافیک بازیابی کمتری دارد اما خواندن‌ها پراکنده‌ترند.

۱۳-۵ کجا ‏EC‏ و کجا تکثیر؟#

بارتوصیه
اشیای ‏RGW‏ بزرگ (آرشیو، پشتیبان، رسانه)‏EC‏؛ بهترین نسبت هزینه
‏pool‏ شاخص ‏bucket‏ و فراداده‌ی ‏RGW‏تکثیری؛ ‏omap‏ در ‏EC‏ پشتیبانی نمی‌شود
‏RBD‏ برای پایگاه داده‌ی حساس به تأخیرتکثیری
‏RBD‏ برای ‏VM‏های عمومی یا پشتیبان‏EC‏ با ‏FastEC‏ و ‏stripe_unit=16K‏ (Tentacle+)
‏CephFS‏ داده‌ی آرشیو‏pool‏ داده‌ی دوم ‏EC‏ با ‏layout‏
فراداده‌ی ‏CephFS‏ و ‏pool‏ داده‌ی پیش‌فرضتکثیری
💡 نکته

‏RBD‏ روی ‏EC‏ همیشه دو ‏pool‏ دارد: فراداده‌ی ‏image‏ (header، ‏omap‏) در ‏pool‏ تکثیری و داده در ‏pool EC‏ با گزینه‌ی ‏--data-pool‏:

bashReef+
rbd create rbd/vm01 --size 200G --data-pool ecpool
rbd info rbd/vm01 | grep data_pool

۱۳-۶ ‏FastEC‏ در ‏Tentacle‏#

۱۳-۶-۱ چه چیزی تغییر کرد؟#

‏Tentacle‏ پیاده‌سازی تازه‌ای از مسیر ‏I/O‏ در ‏EC‏ اضافه کرد که مجموعاً ‏FastEC‏ نامیده می‌شود: خواندن جزئی (فقط ‏shard‏های لازم)، نوشتن جزئی، نوشتن ‏parity delta‏، حذف ‏padding‏ صفرها و صرفه‌جویی در فضای اشیای کوچک. با پرچم هر ‏pool‏ فعال می‌شود:

bashTentacle+
ceph osd pool set ecpool allow_ec_optimizations true
ceph osd pool get ecpool allow_ec_optimizations
# پیش‌فرض برای poolهای تازه
ceph config set global osd_pool_default_flag_ec_optimizations true

محدودیت‌ها طبق مستندات:

  • همه‌ی ‏MON‏ها و ‏OSD‏ها باید ‏Tentacle‏ یا جدیدتر باشند؛ کلاینت‌ها نیازی به ارتقا ندارند.
  • پرچم پس از فعال شدن خاموش‌شدنی نیست، چون شیوه‌ی ذخیره‌ی داده‌ی جدید را تغییر می‌دهد.
  • فقط با ‏ISA-L‏ یا ‏Jerasure‏ با ‏reed_sol_van‏ کار می‌کند؛ ‏CLAY‏، ‏LRC‏ و ‏SHEC‏ رد می‌شوند.
  • ‏stripe_unit‏ باید مضرب ۴ کیلوبایت باشد؛ مستندات برای بیشتر بارها ۱۶ کیلوبایت را توصیه می‌کند و برای بارهای عمدتاً خواندنی تا ۲۵۶ کیلوبایت.
bashTentacle+
ceph osd erasure-code-profile set ec42fast plugin=isa technique=reed_sol_van k=4 m=2 \
     stripe_unit=16K crush-failure-domain=host
ceph osd pool create rbd-ec erasure ec42fast
ceph osd pool set rbd-ec allow_ec_overwrites true
ceph osd pool set rbd-ec allow_ec_optimizations true

۱۳-۷ عیب‌یابی: خطای ۲۱۴۷۴۸۳۶۴۷#

در خروجی ‏ceph pg dump‏ یا ‏ceph pg map‏ برای ‏pool EC‏ گاهی به جای شماره‌ی ‏OSD‏ عدد ۲۱۴۷۴۸۳۶۴۷ می‌بینید. این عدد همان ثابت ‏CRUSH_ITEM_NONE‏ است:

c
/* from ceph/ceph, LGPL — src/crush/crush.h */
#define CRUSH_ITEM_NONE   0x7fffffff

معنی‌اش این است که ‏CRUSH‏ نتوانسته برای آن جایگاه ‏shard‏ یک ‏OSD‏ پیدا کند. در ‏pool‏ تکثیری، جایگاه خالی ساده حذف می‌شود؛ اما در ‏EC‏ جایگاه هر ‏shard‏ معنی دارد (‏shard 3‏ باید ‏shard 3‏ بماند)، پس به جای حذف، با ‏NONE‏ پر می‌شود. علت‌های رایج:

  • تعداد دامنه‌های خرابی کمتر از ‏k+m‏ (مثلاً ۴+۲ با پنج میزبان).
  • ‏OSD‏های ‏out‏ یا ‏down‏ که دامنه‌ای را خالی کرده‌اند.
  • قانون ‏CRUSH‏ که کلاس دستگاه یا ‏root‏ اشتباهی دارد.
  • تعداد تلاش‌های ‏CRUSH‏ کم است و در سلسله‌مراتب نامتوازن نتیجه نمی‌گیرد (‏set_choose_tries‏).
bashReef+
ceph pg ls-by-pool ecpool | grep -c 2147483647
ceph osd crush rule dump ecpool
ceph osd getcrushmap -o cm && crushtool -i cm --test --rule <id> --num-rep 6 --show-bad-mappings
✚ عیب‌یابی

اگر ‏--show-bad-mappings‏ خروجی داد ولی تعداد میزبان‌ها کافی است، در قانون ‏EC‏ گام ‏set_choose_tries 100‏ را بررسی کنید (قانون‌هایی که با ‏create-erasure‏ ساخته می‌شوند معمولاً آن را دارند). اگر میزبان کافی ندارید، راه درست افزودن میزبان یا انتخاب ‏k+m‏ کوچک‌تر است، نه پایین آوردن دامنه‌ی خرابی به ‏osd‏.

۱۳-۸ درون ‏backend‏ مربوط به ‏EC‏: نوشتن، ‏rollback‏ و ‏recovery‏#

این بخش پلی است به فصل ۲۷ («ریاضیات ‏erasure coding‏»). در اینجا به‌جای ریاضیات، مکانیک ‏OSD‏ را بررسی می‌کنیم: یک نوشتن ‏EC‏ چگونه میان ‏shard‏ها هماهنگ می‌شود، اگر وسط کار خرابی رخ دهد چه می‌شود، و ‏recovery‏ دقیقاً چه چیزی را می‌خواند و می‌نویسد. کلاس‌های مرتبط در ‏src/osd/‏ شاخه‌ی ‏main‏ عبارت‌اند از ‏ECBackend‏، ‏ECCommon‏، ‏ECTransaction‏، ‏ECUtil‏ و نسخه‌های «‏L‏» (‏legacy‏) آن‌ها، که ‏ECSwitch‏ بر اساس پرچم ‏pool‏ میانشان انتخاب می‌کند.

شکل ۱۳-۶: یک نوشتن در pool از نوع EC. primary (که همیشه shard صفر نیست، ولی در این شکل shard 0 فرض شده) تراکنش را به stripe ها می‌شکند و تصمیم می‌گیرد آیا RMW لازم است. در صورت نیاز، chunk های قدیمی را می‌خواند، parity را محاسبه و برای هر shard یک ECSubWrite شامل chunk آن shard و ورودی pg log می‌فرستد. هر ورودی log یک ObjectModDesc دارد که توضیح می‌دهد این تغییر چگونه قابل بازگشت است. کلاینت فقط پس از commit همه‌ی shard های acting پاسخ می‌گیرد
شکل ۱۳-۶: یک نوشتن در ‏pool‏ از نوع ‏EC. primary‏ (که همیشه ‏shard‏ صفر نیست، ولی در این شکل ‏shard 0‏ فرض شده) تراکنش را به ‏stripe‏ها می‌شکند و تصمیم می‌گیرد آیا ‏RMW‏ لازم است. در صورت نیاز، ‏chunk‏های قدیمی را می‌خواند، ‏parity‏ را محاسبه و برای هر ‏shard‏ یک ‏ECSubWrite‏ شامل ‏chunk‏ آن ‏shard‏ و ورودی ‏pg log‏ می‌فرستد. هر ورودی ‏log‏ یک ‏ObjectModDesc‏ دارد که توضیح می‌دهد این تغییر چگونه قابل بازگشت است. کلاینت فقط پس از ‏commit‏ همه‌ی ‏shard‏های ‏acting‏ پاسخ می‌گیرد

۱۳-۸-۱ چرا ‏EC‏ به ‏rollback‏ نیاز دارد؟#

در ‏replication‏، هر ‏replica‏ یک کپی کامل است. اگر نوشتنی فقط روی برخی ‏replica‏ها ‏commit‏ شود، ‏recovery‏ می‌تواند همان نسخه‌ی جدید را از یک ‏replica‏ به بقیه کپی کند. در ‏EC‏ هیچ ‏shard‏ی کل داده را ندارد. اگر نوشتن روی ۳ ‏shard‏ از ۶ ‏shard commit‏ شده باشد و سپس ‏primary‏ بمیرد، نه نسخه‌ی قدیم و نه نسخه‌ی جدید از هیچ ‏k shard‏ سازگاری قابل بازسازی نیست. راه حل ‏Ceph‏ این است که تغییرات «برگشت‌پذیر» باشند: هر ورودی ‏pg log‏ در ‏EC‏ یک ‏ObjectModDesc‏ دارد و پس از ‏peering‏، ‏shard‏هایی که از نقطه‌ی توافق‌شده جلوترند، تغییرات را به عقب برمی‌گردانند.

‏op‏ در ‏ObjectModDesc::ModID‏مقدارچگونه برمی‌گردد
‏APPEND‏۱بریدن (‏truncate‏) به اندازه‌ی قبلی
‏SETATTRS‏۲بازگرداندن ‏xattr‏های قبلی ذخیره‌شده
‏DELETE‏۳شیء به‌جای حذف، به نسخه‌ی موقت ‏stash‏ منتقل شده و برگردانده می‌شود
‏CREATE‏۴حذف شیء تازه
‏UPDATE_SNAPS‏۵بازگرداندن ‏snapset‏ قبلی
‏TRY_DELETE‏۶حذف اختیاری
‏ROLLBACK_EXTENTS‏۷برای ‏overwrite: extent‏های قبلی در شیء موقت نگه داشته می‌شوند
‏EC_OMAP‏۸برای ‏omap‏ در ‏EC‏ (در ‏main‏)

‏pg_log_t‏ دو مرز دارد: ‏can_rollback_to‏ (ورودی‌های جدیدتر از آن قابل بازگشت‌اند) و ‏rollback_info_trimmed_to‏ (اطلاعات ‏stash‏ تا آنجا پاک شده است). وقتی ‏primary‏ مطمئن شود یک ورودی روی همه‌ی ‏shard‏ها ‏commit‏ شده، ‏can_rollback_to‏ جلو می‌رود و داده‌ی ‏stash‏ (مثلاً ‏extent‏های قدیمی ‏ROLLBACK_EXTENTS‏) می‌تواند پاک شود. این همان دلیلی است که ‏overwrite‏ در ‏EC‏ قدیمی نسبت به ‏replication‏ فضای موقت و نوشتن اضافه دارد.

◆ عمیق‌تر

هزینه‌ی پنهان ‏ROLLBACK_EXTENTS‏: برای هر ‏overwrite‏ جزئی، داده‌ی قدیمی همان بازه باید پیش از بازنویسی در یک شیء موقت (‏generation‏) کپی شود تا اگر ‏rollback‏ لازم شد، برگردانده شود. در ‏BlueStore‏ این کپی با ‏clone_range‏ و ‏blob‏ مشترک انجام می‌شود (فصل ۲۴، ‏shared blob‏)، پس داده‌ی واقعی دوباره نوشته نمی‌شود، ولی فراداده‌ی اضافه (کلیدهای ‏X‏ و ‏extent map‏) ایجاد می‌شود. ‏EC‏ بهینه در ‏Tentacle‏ بخشی از این کار را با انتخاب دقیق‌تر بازه‌ها کاهش می‌دهد.

۱۳-۸-۲ ‏min_size‏ پیش‌فرض و دلیل آن#

در ‏OSDMonitor::prepare_pool_size‏، برای ‏pool‏های ‏EC‏ مقدار پیش‌فرض ‏min_size = k + min(1, m − 1)‏ است (و ‏size = num_zones × (k + m)‏). یعنی برای ‏k=4‏، ‏m=2‏ مقدار ‏min_size‏ برابر ۵ است: ‏pool‏ تا وقتی دست‌کم ۵ ‏shard‏ در دسترس باشد، نوشتن را می‌پذیرد. بنابراین پس از از دست رفتن یک ‏shard‏، هنوز یک ‏shard‏ اضافه برای تحمل خرابی بعدی وجود دارد. برای ‏m=1‏، ‏min_size‏ = ‏k‏ است و هر خرابی دوم در حین ‏recovery‏ یعنی از دست رفتن داده. به همین دلیل ‏m=1‏ برای داده‌ی مهم توصیه نمی‌شود.

شکل ۱۳-۷: خواندن و 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 پیش‌فرض را نشان می‌دهد
شکل ۱۳-۷: خواندن و ‏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‏ پیش‌فرض را نشان می‌دهد
گزینه‌ی ‏pool‏اثر
‏fast_read‏خواندن موازی همه‌ی ‏shard‏ها؛ کاهش ‏tail latency‏ در ازای ترافیک بیشتر
‏allow_ec_overwrites‏لازم برای ‏RBD‏ و ‏CephFS‏ روی ‏EC‏؛ فعال‌سازی ‏RMW‏ و ‏ROLLBACK_EXTENTS‏
‏allow_ec_optimizations‏ (‏Tentacle‏ به بعد)‏backend‏ بهینه؛ برگشت‌ناپذیر (فصل ۲۷)
‏min_size‏حداقل ‏shard‏های در دسترس برای پذیرش ‏I/O‏
bashReef+
ceph osd pool set ecpool fast_read 1
ceph pg 5.1as2 query | jq '.info | {last_update, last_complete, log_tail}'   # shard 2 از PG 5.1a
ceph pg 5.1a list_unfound
ceph daemon osd.4 dump_ops_in_flight | jq '.ops[] | select(.description|test("ECSub"))'
✎ یادداشت

نام ‏PG‏ در ‏EC‏ پسوند ‏shard‏ دارد: ‏5.1as2‏ یعنی ‏shard 2‏ از ‏PG 5.1a‏. این پسوند در ‏ceph pg dump‏ و در نام ‏collection‏ها روی ‏OSD‏ (‏5.1as2_head‏) دیده می‌شود و همان ‏shard_id‏ است که در کلید ‏onode‏ با ۰‏x80‏ جمع می‌شود (فصل ۲۴).

۱۳-۹ کدگذاری پاک‌شونده با عدد: ماتریس ‏Reed-Solomon‏، حد ‏MDS‏، به‌روزرسانی دلتای ‏parity‏ و هزینه‌ی بازسازی#

این بخش ریاضیات پشت افزونه‌ی ‏ISA‏ را روی بایت‌های واقعی اجرا می‌کند. ارجاع‌ها به ‏src/erasure-code/isa‏ در ‏main‏ (نوامبر ۲۰۲۶) و به تابع ‏gf_gen_rs_matrix‏ در کتابخانه‌ی ‏ISA-L‏ (erasure_code/ec_base.c) است. حساب ‏GF(28)\mathrm{GF}(2^8)‏ با چندجمله‌ای ۰‏x11d‏ در پایتون بازپیاده‌سازی و با جدول ‏gff_base‏ در ‏ISA-L‏ تطبیق داده شده است.

۱۳-۹-۱ کدگذاری و بازگشایی یک ستون بایت#

∑ الگوریتم: ‏Reed-Solomon‏ با ماتریس ‏Vandermonde‏ در ‏ISA‏ (technique=reed_sol_van)

منبع در کد: src/erasure-code/isa/ErasureCodeIsa.cc :: ErasureCodeIsaDefault::prepare، isa_encode، isa_decode؛ ISA-L ec_base.c :: gf_gen_rs_matrix

پیچیدگی: کدگذاری ‏O‏(k × ‏m‏) ضرب ‏GF‏ برای هر بایت؛ بازگشایی ‏O‏(k³) برای وارون ماتریس (در ‏cache‏ جدول‌ها نگه‌داری می‌شود)

text
pseudocode
G[0..k−1] = I_k                        # systematic: shardهای داده همان داده‌اند
gen = 1
for i in k .. k+m−1: G[i][j] = gen^j ; gen = gen × 2      (در GF(2^8))
parity_i = ⊕_j G[i][j] ⊗ d_j
بازگشایی: k سطر سالم را انتخاب کن ← B ؛ d = B⁻¹ × (shardهای سالم)

مثال عددی (‏k‏ = ۴، ‏m‏ = ۲، بایت‌های «‏ABCD‏»). سطر ‏P0‏ همه یک است، پس ‏P0‏ = ۰‏x41‏ ⊕ ۰‏x42‏ ⊕ ۰‏x43‏ ⊕ ۰‏x44‏ = ۰‏x04‏. سطر ‏P1‏ برابر ۱، ۲، ۴ و ۸ است: ۲ ⊗ ۰‏x42‏ = ۰‏x84‏، ‏۴ ⊗ ۰‏x43‏ = ۰‏x11‏ و ۸ ⊗ ۰‏x44‏ = ۰‏x1a‏، پس ‏P1‏ = ۰‏xce‏. اگر ‏d1‏ و ‏d3‏ از دست بروند، سطرهای ‏d0‏، ‏d2‏، ‏P0‏ و ‏P1‏ یک ماتریس ۴×۴ می‌سازند که وارون آن ۰‏x42‏ و ۰‏x44‏ را دوباره می‌دهد. هر ۱۵ حالت از دست رفتن دو ‏shard‏ در ۴+۲ قابل بازگشایی است.

شکل ۱۳-۸: کدگذاری Reed-Solomon روی یک ستون بایت. چپ: ماتریس ۶×۴ ساخته‌شده با gf_gen_rs_matrix ، شامل همانی برای داده و دو سطر parity . راست: محاسبه‌ی P0 و P1 برای « ABCD » و بازگشایی پس از از دست رفتن d1 و d3 . ضرب‌ها در GF (۲⁸) با چندجمله‌ای ۰ x11d انجام شده‌اند
شکل ۱۳-۸: کدگذاری ‏Reed-Solomon‏ روی یک ستون بایت. چپ: ماتریس ۶×۴ ساخته‌شده با ‏gf_gen_rs_matrix‏، شامل همانی برای داده و دو سطر ‏parity‏. راست: محاسبه‌ی ‏P0‏ و ‏P1‏ برای «‏ABCD‏» و بازگشایی پس از از دست رفتن ‏d1‏ و ‏d3‏. ضرب‌ها در ‏GF‏(۲⁸) با چندجمله‌ای ۰‏x11d‏ انجام شده‌اند

۱۳-۹-۲ چرا ‏ISA‏ گاهی به ‏Cauchy‏ برمی‌گردد؟#

∑ الگوریتم: بررسی ‏MDS‏ بودن ماتریس ‏Vandermonde‏

منبع در کد: src/erasure-code/isa/ErasureCodeIsa.cc :: ErasureCodeIsaDefault::parse (m > 4، یا m = 4 و k > 21 ← cauchy)

پیچیدگی: بررسی کامل: Σ ‏C‏(k,‏e‏)·‏C‏(m,‏e‏) وارون ماتریس

text
pseudocode
برای هر e از 1 تا m، هر زیرمجموعه‌ی L از e shard داده و P از e سطر parity:
    سطرها = (داده‌های سالم) ∪ P
    if det = 0 در GF(2^8): الگوی L قابل بازگشایی نیست
کد MDS است اگر هیچ الگویی شکست نخورد

نتیجه‌ی بررسی کامل ما. برای ۴+۲ و ۲۱+۴ هیچ الگوی شکستی نیست. برای ۲۲+۴ دو الگو وجود دارد، مثلاً از دست رفتن ‏d0‏، ‏‏d10‏، ‏‏d21‏ و ‏P2‏، که با وجود ‏m‏ = ۴ داده را برنمی‌گرداند. برای ۸+۵ شش الگو هست (مثلاً ‏d0‏، ‏‏d2‏، ‏‏d5‏، ‏‏P1‏ و ‏P2‏). برای ۴+۵ مشکلی پیدا نشد. پس قاعده‌ی کد، یعنی تغییر به ‏Cauchy‏ برای ‏m‏ > ۴ یا ‏m‏ = ۴ با ‏k‏ > ۲۱، دقیقاً همان مرز شکست در ‏m‏ = ۴ است و برای ‏m‏ > ۴ محافظه‌کارانه عمل می‌کند. پیام هشدار آن هنگام ساخت پروفایل («‏switching to Cauchy technique‏») را جدی بگیرید: ‏technique‏ واقعی ‏pool‏ با چیزی که نوشته‌اید فرق می‌کند.

شکل ۱۳-۹: تعداد الگوهای از دست رفتنِ حداکثر m shard که با ماتریس Vandermonde قابل بازگشایی نیستند. سبز یعنی MDS است و قرمز یعنی شکست دارد. بررسی کامل با اسکریپت ما بر پایه‌ی gf_gen_rs_matrix انجام شده است
شکل ۱۳-۹: تعداد الگوهای از دست رفتنِ حداکثر ‏m‏ ‏‏shard‏ که با ماتریس ‏Vandermonde‏ قابل بازگشایی نیستند. سبز یعنی ‏MDS‏ است و قرمز یعنی شکست دارد. بررسی کامل با اسکریپت ما بر پایه‌ی ‏gf_gen_rs_matrix‏ انجام شده است

۱۳-۹-۳ به‌روزرسانی دلتای ‏parity‏#

∑ الگوریتم: نوشتن ‏parity-delta‏ در ‏FastEC‏

منبع در کد: src/erasure-code/isa/ErasureCodeIsa.cc :: encode_delta، apply_delta (ec_encode_data_update)؛ src/osd/ECTransaction.cc (do_parity_delta_write)

پیچیدگی: ‏O‏(m) ضرب ‏GF‏ برای هر بایت تغییرکرده

text
pseudocode
delta = old_d ⊕ new_d                       # encode_delta
for هر parity i: P_i ⊕= G[i][j] ⊗ delta      # apply_delta؛ برای m=1 فقط XOR
انتخاب در OSD (ec_pdw_write_mode = 0):
    conv = shardهایی که RMW معمولی باید بخواند ؛ pdw = shard تغییرکرده + parityها
    if |pdw| ≥ k: معمولی
    elif pdw بازسازی لازم دارد: معمولی ؛ elif فقط معمولی بازسازی لازم دارد: pdw
    else: pdw اگر |pdw| ≤ |conv|  (تساوی ← pdw)

مثال عددی. ‏d2‏ از ۰‏x43‏ به ۰‏x5a‏ تغییر می‌کند. ‏delta‏ = ۰‏x19‏ است، پس ‏P0‏ = ۰‏x04‏ ⊕ ۰‏x19‏ = ۰‏x1d‏ و ‏P1‏ = ۰‏xce‏ ⊕ (۴ ⊗ ۰‏x19‏ = ۰‏x64‏) = ۰‏xaa‏، که همان نتیجه‌ی کدگذاری دوباره‌ی کامل است. برای بازنویسی ۴‏K‏ یک ‏shard‏: در ۴+۲، ‏RMW‏ معمولی سه ‏shard‏ داده‌ی دیگر را می‌خواند و ‏PDW‏ شامل ‏d2‏ قدیمی و دو ‏parity‏ است، یعنی ۳ در برابر ۳. در تساوی ‏PDW‏ انتخاب می‌شود. در ۸+۳ مقایسه ۷ در برابر ۴ و به نفع ‏PDW‏ است. در ۲+۲ مقایسه ۱ در برابر ۳ است و مسیر معمولی برنده است. این مسیر فقط در ‏pool‏ با ‏FastEC‏ (allow_ec_optimizations، ‏Tentacle‏ به بعد) و افزونه‌ای که پرچم ‏paritydelta‏ دارد (‏ISA‏ و ‏Jerasure‏) فعال است.

شکل ۱۳-۱۰: گام‌های به‌روزرسانی دلتای parity برای تغییر یک بایت در d2: XOR قدیم و جدید، ضرب در ضریب ستون همان shard و XOR در هر parity . سطر آخر تطبیق با کدگذاری دوباره‌ی کامل را نشان می‌دهد. خط بنفش شمار shard های خواندنی در دو مسیر را برای سه پروفایل مقایسه می‌کند
شکل ۱۳-۱۰: گام‌های به‌روزرسانی دلتای ‏parity‏ برای تغییر یک بایت در ‏d2: XOR‏ قدیم و جدید، ضرب در ضریب ستون همان ‏shard‏ و ‏XOR‏ در هر ‏parity‏. سطر آخر تطبیق با کدگذاری دوباره‌ی کامل را نشان می‌دهد. خط بنفش شمار ‏shard‏های خواندنی در دو مسیر را برای سه پروفایل مقایسه می‌کند

۱۳-۹-۴ هزینه‌ی بازسازی یک ‏shard: RS‏، ‏‏LRC‏ و ‏CLAY‏#

∑ الگوریتم: حجم خواندن برای بازسازی یک ‏chunk‏

منبع در کد: src/erasure-code/clay/ErasureCodeClay.cc :: parse (q = d − k + 1، t = (k+m+nu)/q، sub_chunk_no = qᵗ)؛ src/erasure-code/lrc

پیچیدگی: ‏O‏(۱)

text
pseudocode
RS:   read = k chunk
LRC:  read = l chunk (گروه محلی) ؛ سربار خام = (k + m + (k+m)/l) / k
CLAY: q = d − k + 1 ؛ هر یک از d کمک‌کننده 1/q از chunk را می‌فرستد ؛ read = d / q chunk
      شرط: k + 1 ≤ d ≤ k + m − 1 ؛ sub-chunkها = q^t

مثال عددی. در ۴+۲، بازسازی ‏RS‏ چهار ‏chunk‏ می‌خواند. ‏LRC‏ با ‏l‏ = ۳ سه ‏chunk‏ می‌خواند، اما سربار خامش ۲.۰ است، نه ۱.۵. ‏CLAY‏ با ‏d‏ = ۵ (‏q‏ = ۲، ‏t‏ = ۳ و ۸ ‏sub-chunk‏) فقط ۲.۵ ‏chunk‏ می‌خواند و سربار ۱.۵ را حفظ می‌کند. در ۸+۴، اعداد برای ‏RS‏، ‏‏LRC‏ (l = ۴، سربار ۱.۸۷۵) و ‏CLAY‏ (d = ۱۱، ‏q‏ = ۴، ۶۴ ‏sub-chunk‏) به ترتیب ۸، ۴ و ۲.۷۵ ‏chunk‏ است. هزینه‌ی ‏CLAY‏ در خواندن‌های تکه‌تکه است: هر کمک‌کننده ‏sub-chunk‏های پراکنده می‌فرستد، پس روی ‏HDD‏ تعداد ‏seek‏ها بیشتر می‌شود.

شکل ۱۳-۱۱: داده‌ی خوانده‌شده برای بازسازی یک chunk ازدست‌رفته با RS ، LRC و CLAY در دو پیکربندی، همراه سربار خام هر طرح. LRC خواندن را با فضای بیشتر کم می‌کند و CLAY با همان فضای RS
شکل ۱۳-۱۱: داده‌ی خوانده‌شده برای بازسازی یک ‏chunk‏ ازدست‌رفته با ‏RS‏، ‏‏LRC‏ و ‏CLAY‏ در دو پیکربندی، همراه سربار خام هر طرح. ‏LRC‏ خواندن را با فضای بیشتر کم می‌کند و ‏CLAY‏ با همان فضای ‏RS‏

۱۳-۱۰ از درون کد: ‏EC‏ در ‏OSD‏#

۱۳-۱۰-۱ نقشه‌ی فایل‌ها#

مسیرنقش
‏src/erasure-code/ErasureCodeInterface.h‏رابط افزونه‌ها: ‏init‏، ‏encode‏، ‏decode‏، ‏minimum_to_decode‏، پرچم‌های بهینه‌سازی
‏src/erasure-code/isa, jerasure, lrc, shec, clay‏پیاده‌سازی افزونه‌ها
‏src/osd/ECSwitch.h‏سوییچ بین پیاده‌سازی قدیمی (‏ECBackendL‏) و بهینه (‏ECBackend‏)
‏src/osd/ECBackend.cc, ECCommon.cc‏مسیر خواندن، نوشتن و بازیابی بهینه
‏src/osd/ECTransaction.cc‏برنامه‌ریزی نوشتن: تعیین خواندن‌های لازم، ‏RMW‏ یا ‏parity delta‏
‏src/osd/ECUtil.h‏‏stripe_info_t‏ و نگاشت محدوده‌ی منطقی به ‏shard‏ها
‏src/osd/OSDMap.cc‏‏pgtemp_primaryfirst‏ برای انتخاب ‏primary‏ در ‏pool‏های بهینه

۱۳-۱۰-۲ رابط افزونه#

هر افزونه کلاسی است که ‏ErasureCodeInterface‏ را پیاده می‌کند. توابع کلیدی (امضاها خلاصه شده‌اند):

  • ‏init(profile, ss)‏: پارامترهای پروفایل را می‌خواند و ماتریس را می‌سازد.
  • ‏get_chunk_count()‏ و ‏get_data_chunk_count()‏: ‏k+m‏ و ‏k‏.
  • ‏minimum_to_decode(want_to_read, available, minimum)‏: کمترین مجموعه‌ی ‏shard‏ برای خواندن خواسته‌شده.
  • ‏encode(want_to_encode, in, encoded)‏ و ‏decode(want_to_read, chunks, decoded)‏.
  • ‏get_supported_optimizations()‏: پرچم‌هایی که ‏FastEC‏ بر اساس آن‌ها تصمیم می‌گیرد.
cpp
// from ceph/ceph, LGPL — src/erasure-code/ErasureCodeInterface.h (excerpt)
FLAG_EC_PLUGIN_PARTIAL_READ_OPTIMIZATION = 1<<0,
FLAG_EC_PLUGIN_PARTIAL_WRITE_OPTIMIZATION = 1<<1,
FLAG_EC_PLUGIN_ZERO_INPUT_ZERO_OUTPUT_OPTIMIZATION = 1<<2,
FLAG_EC_PLUGIN_ZERO_PADDING_OPTIMIZATION = 1<<3,
FLAG_EC_PLUGIN_PARITY_DELTA_OPTIMIZATION = 1<<4,

این پرچم‌ها توضیح می‌دهند چرا ‏FastEC‏ فقط با برخی افزونه‌ها کار می‌کند: هر بهینه‌سازی به ویژگی ریاضی خاصی نیاز دارد. مثلاً «‏zero in, zero out‏» یعنی ‏encode‏ بلوک تمام‌صفر، ‏parity‏ تمام‌صفر می‌دهد (برای کدهای خطی درست است) تا بتوان از نوشتن ‏padding‏ صفر صرف‌نظر کرد. ‏parity delta‏ به خطی بودن کد تکیه دارد: اگر داده‌ی ‏shard j‏ از ‏d‏ به ‏d'‏ تغییر کند، ‏parity‏ جدید برابر است با ‏parity‏ قدیم به‌علاوه‌ی ‏G[i][j]‏⊗(‏d‏ ⊕ ‏d'‏).

۱۳-۱۰-۳ ‏ECSwitch‏: گذار تدریجی#

کامنت ابتدای ‏ECSwitch.h‏ صریح است: این کلاس «سوییچر موقت» بین پیاده‌سازی ‏legacy‏ و ‏optimized‏ است تا وقتی پیاده‌سازی جدید به اندازه‌ی کافی مورد اعتماد شود. ‏ECSwitch‏ هر دو شیء ‏ECLegacy::ECBackendL legacy‏ و ‏ECBackend optimized‏ را دارد و بر اساس پرچم ‏pool‏ (‏allow_ec_optimizations‏) درخواست‌ها را به یکی هدایت می‌کند. به همین دلیل پرچم برگشت‌پذیر نیست: پس از نوشتن داده با قالب جدید، پیاده‌سازی قدیمی نمی‌تواند آن را بخواند.

۱۳-۱۰-۴ تصمیم ‏RMW‏ یا ‏parity delta‏#

در ‏ECTransaction.cc‏ برای هر نوشتن، طرح (‏plan‏) ساخته می‌شود. منطق انتخاب، ساده‌شده:

cpp
// from ceph/ceph, LGPL — src/osd/ECTransaction.cc (excerpt, simplified indentation)
if (sinfo.supports_parity_delta_writes() && !object_in_cache &&
    orig_size == projected_size && !reads.empty() && !op.truncate) {
  ...
  if (pdw_write_mode != 0) {
    do_parity_delta_write = (pdw_write_mode == 2);
  } else if (pdw_read_shards.size() >= sinfo.get_k()) {
    do_parity_delta_write = false;
  } else if (!shard_id_set::difference(pdw_read_shards, readable_shards).empty()) {
    do_parity_delta_write = false;
  } else if (!shard_id_set::difference(read_shards, readable_shards).empty()) {
    do_parity_delta_write = true;
  } else {
    do_parity_delta_write = pdw_read_shards.size() <= read_shards.size();
  }
}

خوانش: ‏parity delta‏ فقط وقتی معنا دارد که اندازه‌ی شیء تغییر نکند، ‏truncate‏ نباشد و داده در ‏cache‏ نباشد. اگر ‏parity delta‏ نیازمند ‏k‏ خواندن یا بیشتر باشد، مزیتی ندارد؛ اگر برای ‏parity delta‏ بازسازی لازم باشد، کنار گذاشته می‌شود؛ اگر برای ‏RMW‏ معمولی بازسازی لازم باشد ولی برای ‏parity delta‏ نه، ‏parity delta‏ انتخاب می‌شود؛ در غیر این صورت روشی که خواندن کمتری دارد (با ترجیح ‏parity delta‏ در تساوی). متغیر ‏pdw_write_mode‏ اجازه می‌دهد برای آزمایش یکی از دو روش را اجبار کرد.

۱۳-۱۰-۵ چرا ‏shard 0‏ همیشه ‏primary‏ نیست؟#

در ‏pool‏های بهینه، برخی ‏shard‏های داده در هر ‏I/O‏ به‌روز نمی‌شوند (نوشتن جزئی فقط ‏shard‏ تغییرکرده و ‏parity‏ها را لمس می‌کند). چنین ‏shard‏ی نمی‌تواند ‏primary‏ شود، چون ‏primary‏ باید ‏log‏ کامل ‏PG‏ را داشته باشد. اما منطق انتخاب ‏primary‏ در ‏OSDMap‏ را نمی‌توان تغییر داد بدون اینکه کلاینت‌ها ارتقا یابند. راه‌حل ‏OSDMap::pgtemp_primaryfirst()‏: ‏OSDMonitor‏ هنگام تنظیم ‏pg_temp‏ ترتیب را طوری عوض می‌کند که ‏shard‏های مجاز ‏primary‏ اول بیایند.

شکل ۱۳-۱۲: مثال کامنت کد برای pool بهینه‌ی ۴+۲: shard 0 از دست رفته است؛ pg_temp طوری مرتب می‌شود که shard های parity (که همیشه به‌روزند) جلو بیایند و osd.9 primary شود
شکل ۱۳-۱۲: مثال کامنت کد برای ‏pool‏ بهینه‌ی ۴+۲: ‏shard 0‏ از دست رفته است؛ ‏pg_temp‏ طوری مرتب می‌شود که ‏shard‏های ‏parity‏ (که همیشه به‌روزند) جلو بیایند و ‏osd.9 primary‏ شود
cpp
// from ceph/ceph, LGPL — src/osd/OSDMap.cc (comment excerpt)
 * Example: Optimized EC pool 4+2
 * acting_set = {NONE, 6, 7, 8, 9, 10}
 * non_primary_shards = {1, 2, 3} # data shards other than shard 0
 * pg_temp = {NONE, 9, 10, 6, 7, 8} # non-primary shards at end
 * primary will be OSD 9(1)

۱۳-۱۰-۶ شبه‌کد خواندن تخریب‌شده#

text
read(obj, off, len):
  want = shards_covering(off, len)                 # ECUtil: محدوده‌ی منطقی → shard extents
  avail = shards_up_and_not_missing(obj)
  if want ⊆ avail:                                  # خواندن جزئی (FastEC)
     return gather(want)
  need = ec_impl.minimum_to_decode(want, avail)     # k shard
  chunks = gather(need)
  return ec_impl.decode(want, chunks)[off:len]
نکات ‏SRE‏ این فصل
  • ‏EC‏ به دامنه‌های خرابی کافی نیاز دارد: دست‌کم ‏k+m‏، ترجیحاً ‏k+m+1‏.
  • پروفایل تغییرناپذیر است؛ از ابتدا درست انتخاب کنید.
  • ‏pool‏های تازه: ‏ISA-L‏؛ در ‏Tentacle+‏ با ‏FastEC‏ و ‏stripe_unit=16K‏ برای ‏RBD/CephFS‏.
  • فراداده، ‏omap‏ و شاخص ‏bucket‏ها روی ‏pool‏ تکثیری.
  • ۲۱۴۷۴۸۳۶۴۷ یعنی ‏CRUSH‏ جایگاه ‏shard‏ را پر نکرده؛ ‏crushtool --test‏ را اجرا کنید.
  • بازیابی ‏EC‏ پرهزینه است؛ ‏LRC‏ یا ‏CLAY‏ را برای کلاسترهای بزرگ بررسی کنید.

۱۳-۱۱ تکمیلی و به‌روز#

  • ‏Luminous‏: ‏allow_ec_overwrites‏ و پشتیبانی ‏RBD‏ و ‏CephFS‏ روی ‏EC‏.
  • ‏Tentacle‏: ‏FastEC‏ با ‏allow_ec_optimizations‏؛ ‏ISA-L‏ پیش‌فرض کلاسترهای تازه؛ رفع اشکال‌های برنامه‌ریزی ‏truncate+write‏.
  • ‏Squid‏: قانون ‏CRUSH‏ نوع ‏MSR‏ برای پیکربندی‌های انعطاف‌پذیرتر ‏EC‏.
  • کد جاری: ‏osd_pool_default_flag_ec_optimizations=false‏ (‏FastEC‏ برای ‏pool‏های تازه خودکار روشن نیست).

۱۳-۱۲ تمرین عملی#

  1. ۱. پروفایل‌های ۲+۱، ۴+۲ و ۸+۳ را بسازید و با ‏ceph osd erasure-code-profile get‏ مقایسه کنید. کدام روی کلاستر شما قابل اجراست؟
  2. ۲. یک ‏pool 4+2‏ بسازید، شیئی بنویسید و با ‏ceph osd map‏ ببینید هر ‏shard‏ روی کدام ‏OSD‏ است. یک ‏OSD‏ را متوقف کنید و شیء را بخوانید.
  3. ۳. ‏pool EC‏ را به‌عنوان ‏data-pool‏ یک ‏image RBD‏ استفاده کنید و با ‏fio‏ نوشتن تصادفی ۴ کیلوبایتی را با ‏pool‏ تکثیری مقایسه کنید.
  4. ۴. در کلاستر ‏Tentacle‏ آزمایشی، همان آزمایش را با ‏allow_ec_optimizations‏ و ‏stripe_unit=16K‏ تکرار کنید.
  5. ۵. ‏pool 4+2‏ را روی کلاستری با پنج میزبان بسازید و ۲۱۴۷۴۸۳۶۴۷ را در ‏ceph pg dump‏ مشاهده کنید؛ سپس با ‏crushtool --test‏ تأیید کنید.
  6. ۶. در کد، تابع ‏pgtemp_primaryfirst‏ را بخوانید و با مثال خودتان (۸+۳) خروجی را پیش‌بینی کنید.

۱۳-۱۳ پرسش‌های مرور#

  1. ۱. سربار و تحمل خرابی ‏EC 8+3‏ در مقایسه با تکثیر سه‌تایی چیست؟
  2. ۲. چرا هر زیرماتریس ‏k‏×‏k‏ ماتریس مولد باید وارون‌پذیر باشد؟
  3. ۳. تفاوت ‏shard‏ در ‏EC‏ و نسخه در تکثیر چیست؟
  4. ۴. چرا نوشتن کوچک در ‏EC‏ قدیمی پرهزینه بود؟
  5. ۵. ‏parity delta‏ بر چه ویژگی ریاضی تکیه دارد؟
  6. ۶. محدودیت‌های فعال‌سازی ‏FastEC‏ کدام‌اند؟
  7. ۷. چرا پرچم ‏allow_ec_optimizations‏ برگشت‌پذیر نیست؟
  8. ۸. ‏CLAY‏ و ‏LRC‏ چطور ترافیک بازیابی را کاهش می‌دهند؟
  9. ۹. عدد ۲۱۴۷۴۸۳۶۴۷ در نگاشت ‏PG‏ چه معنایی دارد و علت‌های رایج آن کدام‌اند؟
  10. ۱۰. چرا در ‏pool‏ بهینه، ‏primary‏ همیشه ‏shard 0‏ نیست؟

پاسخ این پرسش‌ها در پیوست ه: پاسخ پرسش‌ها آمده است. پاسخ پرسش‌های این فصل →

۱۳-۱۴ منابع این فصل#

راهنمای جامع Ceph · ۱٫۶ · آبان ۱۴۰۵
© 2026 Ramin Najarbashi · CC BY-NC-SA 4.0

حریم خصوصی: این سایت کوکی نمی‌گذارد. برای شمارش بازدیدها از Cloudflare Web Analytics استفاده می‌شود که کوکی ندارد و افراد را دنبال نمی‌کند.