فیلاوور امن PostgreSQL روی Kubernetes
خلاصهٔ کاملتر
تیم مهندسی Datadog میگه گیمدیها یکی از بهترین راههاشون برای پیداکردن ضعفهای پنهان سیستمن. تو یکی از همین تمرینها، شبیهسازی قطعی یه availability zone با تزریق تأخیر شبکه یه ضعف جدی رو لو داد: چند کلاستر PostgreSQL نود writer اصلیشون تو ناحیهٔ آسیبدیده بود، لگ replication سریع بالا رفت، نوشتنها گیر کرد و اپها دیتای قدیمی سرو کردن. چون هیچ رپلیکایی بهروز نبود، فیلاوور امن نبود و کلاسترها عملاً قفل شدن.
به گفتهٔ نویسنده، این تجربه یه حقیقت ناراحتکننده رو نشون داد: معماریشون تو بعضی خرابیهای شبکه، «دردسترسبودن» رو به «دوام داده» ترجیح میداد. Primary همچنان نوشتن رو قبول میکرد ولی رپلیکاها عقبتر میافتادن، و در نهایت هیچ کاندیدای امنی برای پروموت نمیموند. تنها گزینه این بود که صبر کنن تا تأخیر بخوابه و رپلیکاها برسن.
معماری پایهشون دو تا استخر داره: یه leader pool با یه نود writer فعال و دو تا standby، و یه read replica pool برای ترافیک فقطخواندنی که از فیلاوور کنار گذاشته میشه. مدیریت replication و انتخاب لیدر با Patroni ـه که برای هماهنگی از یه ذخیرهگاه پیکربندی توزیعشده (تو اینجا ZooKeeper) استفاده میکنه. هر نود جدید اول ZooKeeper رو چک میکنه؛ اگه لیدری نباشه با ساختن یه znode موقتی کلید لیدر رو میگیره و همین جلوی شکلگرفتن چند primary رو میگیره.
مشکل اصلی اینجا بود که Patroni قبل از پروموت، لگ replication رو با پارامتر maximum_lag_on_failover میسنجه؛ موقع گیمدی همهٔ standbyها از این حد گذشته بودن، برای همین Patroni درست تصمیم گرفت و پروموت رو رد کرد. پس مقصر Patroni نبود، نبودِ یه کاندیدای امن بود. ریشهٔ ماجرا هم به replication آسنکرون پیشفرض برمیگرده که توش لیدر منتظر تأیید رپلیکا نمیمونه؛ کمتأخیره ولی موقع خرابی، تراکنشهای ریپلیکیتنشده از دست میرن.
راهحلشون یه مدل هیبریدی بود: standbyهای leader pool تو replication سنکرون شرکت میکنن (لیدر قبل از commit منتظر تأیید یه standby سنکرون میمونه) ولی read replicaها آسنکرون میمونن. برای اینکار synchronous_mode رو true، synchronous_mode_strict رو true (که موقع نبود رپلیکا بهجای برگشتن به آسنکرون، نوشتن رو بلاک میکنه) و synchronous_commit رو روی remote_apply گذاشتن. با بنچمارک pgbench دیدن که remote_apply قویترین سازگاری ولی بیشترین هزینه رو داره: حدود ۵۳٪ افزایش تأخیر و ۳۴٪ کاهش throughput، درحالیکه local سبکتر ولی بدون تضمین بیننودی بود.
نویسنده میگه با وجود این افت در سطح دیتابیس، بعد از استقرار تدریجی remote_apply روی کلاسترهای پرنوشت، تأثیر محسوسی روی تأخیر یا throughput سطح اپلیکیشن ندیدن. سناریوهای خرابی مختلف رو هم تست کردن؛ مثلاً موقع ازدسترفتن همهٔ standbyهای سنکرون در حالت strict، تأیید تراکنشها بلاک میشه تا یه رپلیکا برگرده. نکتهٔ مهم فلسفهٔ کارشونه: بلاککردن نوشتن موقع قطعی، خرابی رو صریح به سرویسهای بالادستی نشون میده تا با retry یا صف واکنش نشون بدن، بهجای اینکه نوشتنها بیسروصدا از بین برن.
نکات کلیدی:
- گیمدی نشون داد معماری قبلی موقع تأخیر شبکه هیچ راه امنی برای فیلاوور نداشت
- Patroni با معیار maximum_lag_on_failover از پروموتکردن رپلیکای عقبافتاده جلوگیری میکنه
- مدل هیبریدی: standbyها سنکرون، read replicaها آسنکرون میمونن
- پارامترهای کلیدی: synchronous_mode، synchronous_mode_strict و synchronous_commit=remote_apply
- بنچمارک نشون داد دوام قویتر یعنی تأخیر بیشتر (تا ۵۳٪) و throughput کمتر (تا ۳۴٪)
- بلاککردن نوشتن موقع قطعی، خرابی رو صریح میکنه تا نوشتنها بیسروصدا گم نشن




