بررسی مفهوم leader election در معماری سرویس‌های توزیع‌شده


در این مقاله به نقش leader election در هماهنگی میان اجزا و جلوگیری از رفتارهای نامتوازن سیستم اشاره می‌کنیم.

سلام، این یک شماره ویژه و رایگان از Pragmatic Engineer Newsletter است. در هر شماره از دیدگاه مهندسان ارشد و رهبران مهندسی به اتفاقات Big Tech و استارتاپ‌ها نگاه می‌کنم. امروز یکی از چهار موضوعی را که در بررسی عمیقِ قطع گسترده اخیر AWS بررسی کردیم، می‌آورم تا برای مخاطب‌های حوزه devops / SRE مفید باشد. 📬

روز دوشنبه روز جالبی بود: Signal قطع شد، Slack و Zoom با مشکل روبه‌رو شدند و بیشتر سرویس‌های Amazon همراه با هزاران وب‌سایت و اپ در سراسر جهان دچار افت شدند. مقصر اصلی یک outage چهارده ساعته در ناحیه us-east-1 بود. ⚠️

عامل اصلی این قطع، شکست در DNS مربوط به سرویس DynamoDB بود. DynamoDB یک پایگاه داده serverless NoSQL است که برای durability و high availability طراحی شده و در حالت multi-AZ SLAِی برابر با 99.99% uptime ارائه می‌دهد. به زبان ساده: در یک region، DynamoDB معمولاً عملاً همیشه در دسترس و با latency کم عمل می‌کند. همچنین مدل consistency پیش‌فرض eventual است اما می‌توانید reads را به صورت strong consistency تنظیم کنید تا همیشه وضعیت واقعی بازگردانده شود.

این ویژگی‌ها باعث شده‌اند که DynamoDB گزینه‌ای جذاب برای ذخیره‌سازی داده در خیلی از اپلیکیشن‌ها باشد و همین‌طور بسیاری از سرویس‌های داخلی AWS روی آن تکیه دارند. با این حال مواردی مثل queryهای پیچیده، مدل‌های دادهٔ بسیار پیچیده یا مقادیر بسیار زیاد داده که هزینهٔ ذخیره‌سازی را غیرقابل قبول می‌کند، ممکن است دلایلی برای انتخاب نکردن DynamoDB باشند.

در این outage، DNS آدرس dynamodb.us-east-1.amazonaws.com یک رکورد DNS خالی برگرداند — یعنی برای تمام سرویس‌ها (داخلی یا خارجی) انگار DynamoDB در آن region ناپدید شد. برای فهمیدن چرایی این اتفاق باید نگاهی به نحوهٔ مدیریت DNS در DynamoDB بیندازیم. 🔎

چطور مدیریت DNS در DynamoDB انجام می‌شود؟ ساختار کلی این‌گونه است: یک سرویس DNS planner که health بارها/Load Balancerها را پایش می‌کند و براساس آن DNS plans تولید می‌کند؛ و یک سرویس DNS enactor که مسئول اعمال آن طرح‌ها در Route 53 است. برای افزایش پایداری، در هر AZ یک DNS Enactor اجرا می‌شود (در us-east-1 سه AZ و سه DNS Enactor وجود دارد).

با چند DNS Enactor موازی، انتظار race condition می‌رود و سیستم برای مقابله با این موضوع روی eventual consistency حساب می‌کند: حتی اگر یک Enactor طرح قدیمی را اعمال کند، طرح‌ها با هم سازگار نگه داشته می‌شدند و Enactorها معمولاً آخرین planها را از DNS Planner می‌گرفتند.

ترکیبی از چند اتفاق مستقل باعث شد که DNS DynamoDB برای حدود 3 ساعت آفلاین شود: 1) یکی از DNS Enactorها (Enactor #1) در به‌روزرسانی DNS به‌طرز غیرعادی‌ای کند شد، 2) DNS Planner شروع کرد به تولید planهای جدید با سرعت بالاتر، و 3) DNS Enactor #2 با سرعت زیادی این planها را پردازش و به Route 53 نوشت و سپس planهای قدیمی را حذف کرد. 🧩

از تلاقی این عوامل دو مشکل بوجود آمد: اول اینکه Enactor #1 در حال اجرا روی یک plan قدیمی مانده بود و چون کند شده بود، چکِ وضعیتش stale شده بود. دوم اینکه Enactor #2 وقتی متوجه شد plan قدیمی هنوز استفاده می‌شود، منطق cleanupِ خود را اجرا کرد و حذف plan را به معنای پاک کردن تمام آدرس‌های IP نقاط انتهایی منطقه‌ای در Route 53 انجام داد. نتیجه این شد که رکورد DNSِ dynamodb.us-east-1.amazonaws.com خالی شود و سرویس در آن region قابل دسترس نباشد.

این قطعی DynamoDB به‌سرعت روی تمامی سرویس‌هایی که وابسته به DynamoDB در us-east-1 بودند اثر گذاشت، از مشتریان تا سرویس‌های داخلی AWS. مشتریانی که از Global Tables استفاده می‌کردند می‌توانستند به replicaهای سایر regionها وصل شوند اما replication lag شدیدی به وجود آمد. ⛔

تعمیر دستی نهایتاً DynamoDB را احیا کرد، اما بازیابی کامل زمان‌بر بود و احتمالاً شامل مدیریت thundering herd در زمان راه‌اندازی مجدد سرویس‌های بزرگ هم شده بود. در عین حال به نظر می‌رسد جزئیات کلیدی در پست‌مورتِم رسمی کم گفته شده‌اند: چرا Enactor #1 کند شد؟ چرا منطقِ cleanup سبب حذف کامل رکوردها شد؟ آیا چنین حالتِ race قبلاً هم رخ داده و چه درس‌هایی گرفته شده؟ و مهم‌تر اینکه چه اقداماتی برای جلوگیری از تکرار این نوع نقص طراحی در آینده برنامه‌ریزی شده‌اند؟ 🤔

مشکلِ DynamoDB فقط شروعِ ماجرا بود؛ با برگشت DynamoDB، Amazon EC2 همچنان مشکل داشت و اوضاع بدتر شد. برای فهم اینکه چرا، باید به زیرسیستم مدیریت سرورها نگاه کنیم: DropletWorkflow Manager (DWFM) که مدیریت فیزیکی سرورها (droplets) را بر عهده دارد — می‌توان آن را شبیه “Kubernetes for EC2” تصور کرد.

DWFM برای هر droplet یک lease نگه می‌دارد و وضعیت سرور را هر چند دقیقه بررسی می‌کند. این وضعیت‌ها در DynamoDB ذخیره می‌شوند، بنابراین وقتی DynamoDB قطع شد اتفاقات زیر رخ داد: 1) leases شروع به timeout کردند چون وضعیت‌ها ثبت نشد، 2) پیام‌های “insufficient capacity” برای مشتریان EC2 برگشت داده شد چون DWFM فکر می‌کرد سرورها در دسترس نیستند، و 3) بازگشت DynamoDB به‌تنهایی مشکل را حل نکرد زیرا تلاش‌ها برای تجدید leaseها آن‌قدر طول کشید که دوباره timeout شدند و سیستم وارد حالت congestive collapse شد. 🕳️

مهندسان برای بازگرداندن تخصیص EC2 چند ساعت وقت صرف کردند تا mitigations مناسب را پیدا کنند؛ سپس مشکل دیگری هم ظاهر شد: propagation شبکه. حتی وقتی EC2 از نظر داخلی به نظر سالم می‌آمد، instances ارتباط شبکه‌ای با بیرون نداشتند چون backlog وضعیت شبکه در یک سیستم به نام Network Manager ایجاد شده بود. افزایش latencies در network propagation باعث شد حتی بعد از اینکه EC2 داخلی خوب شده بود، ارتباطات بیرونی به تدریج و پس از کاهش بار بر Network Manager بازسازی شود. در مجموع چند مرحلهٔ رفع مشکل تا بازگشت کامل—از جمله کاهش throttleها و پاک‌سازی حالت‌های صف— زمان‌بر بود و چند ساعت دیگر طول کشید. ⏳

پرسش‌های مهمی که هنوز به‌نظر پاسخ کامل نگرفته‌اند برای ما به‌عنوان مهندسین SRE/DevOps کلیدی‌اند: چرا اختلاف عملکرد بین Enactorها پیش آمد؟ منطق حذفِ planها چرا این‌قدر قاطعانه و بدون یک مرحلهٔ بازیابی انجام شد؟ آیا طراحی coordination بین enactorها باید مبتنی بر نسخه‌بندی اتمیک، leader election یا mechanismهای قوی‌ترِ concurrency control بازبینی شود؟

چند پیشنهاد عملی که می‌تواند به کاهش ریسکِ چنین رویدادهایی کمک کند (برای تیم‌های معماری و SRE): استفاده از نسخه‌بندیِ اتمیک و مقایسه-و-تعویض (compare-and-swap) برای planها، leader election یا coordination متمرکز برای اعمال تغییرات حساس، طراحیِ cleanupها به‌صورت تدریجی و idempotent و با tombstone به جای حذف کامل ناگهانی، throttling و backoff برای اعمال تغییرات روی Route 53، و افزایش coverage در Chaos Testing و بازیابی‌های ساخت‌یافته (recovery playbooks). همچنین caching محلی وضعیتِ حیاتی و fallbackهای read-only می‌تواند تجربه کاربر را در زمان قطعی‌ها قابل‌تحمل‌تر کند. 🛠️

در نهایت، این حادثه یک یادآوری قدرتمند است که حتی اجزا و سرویس‌هایی با SLA بالا مثل DynamoDB می‌توانند با تعامل پیچیده بخش‌های مدیریتی و هماهنگی ناصحیح، باعث cascade failures شوند. برای تیم‌های SRE و مهندسی پلتفرم، تمرکز بر روی coordination، نسخه‌بندیِ قوی‌تر، تست‌های تهاجمی و playbookهای بازیابی واقعی باید اولویت باشد تا ریسک بروز مجدد چنین حوادثی را کاهش دهیم. 👩‍💻👨‍💻