چهار قاتل اصلی پشت قطعیهای Postgres
خلاصهٔ کاملتر
نویسنده که داره pgrust — یه پیادهسازی دوبارهٔ Postgres — رو میسازه، میگه اینها مشکل تئوری نیستن؛ از صحبت با کلی استارتاپ درشون آورده، مخصوصاً تیمهایی که آدم اختصاصی دیتابیس ندارن. به گفتهٔ او چهار حوزه بیشترین قطعی رو میسازن و همینها هدف اصلی pgrustان.
اول از همه vacuum. وقتی یه ردیف رو تو Postgres حذف میکنی، واقعاً پاک نمیشه؛ فقط علامت حذف میخوره تا مثلاً rollback تراکنش راحت باشه. یه job پسزمینه به اسم vacuum بعداً واقعاً فضاش رو آزاد میکنه، ولی چون مجبوره کل داده رو بخونه، I/O سنگینی میبره و با کوئریهای خودت سر منابع دعوا میکنه. اگه شُلش کنی عقب میمونه و ردیفهای مرده سریعتر از پاکسازی انباشته میشن — یه بازی کثیف بین «بهاندازه تهاجمی» و «نه اونقدر که کوئریها کند شن».
مشکل دوم که نویسنده میگه احتمالاً هزاران قطعی ساخته، wraparound شناسهٔ تراکنشه. Postgres برای اینکه بفهمه کدوم ردیف زندهست و کدوم مرده، ردیفها رو با یه شناسهٔ تراکنش ۳۲ بیتی برچسب میزنه؛ یعنی حداکثر حدود چهار میلیارد تراکنش تا قبل از اینکه مجبور شه شناسهها رو دوباره استفاده کنه. بازپسگیری شناسههای قدیمی هم کار vacuumه و اگه زیادی عقب بیفته، Postgres برای جلوگیری از خرابشدن داده کل دیتابیس رو میخوابونه.
راهحلهایی که برای pgrust روشون کار میکنه دوتاست: یکی وصلهٔ شناسهٔ تراکنش ۶۴ بیتی که سالهاست بهخاطر شکستن سازگاری و ۳۲ بیت سربار روی هر ردیف merge نشده و نویسنده معتقده با این حجم قطعی کاملاً ارزشش رو داره؛ دومی معماریهایی که اصلاً vacuum لازم ندارن، مثل روش undo log که Oracle استفاده میکنه.
سومین حوزه سقف کانکشن و موازیسازیه. تعداد کانکشن یه تنظیم ثابته و تغییرش ریاستارت میخواد، پس اگه اولش کم بذاری بعداً دردسر میشه؛ PgBouncer هم فقط بخشی از ماجرا رو حل میکنه. ریشهاش اینه که Postgres برای هر کانکشن یه پردازهٔ جدید میسازه و پردازهها هم از نظر CPU و هم زمان راهاندازی گرونان، برای همین موازیسازی داخل یه کوئری رو هم فقط برای کوئریهای طولانی انجام میده. pgrust از روز اول thread-based طراحی شده و چون با Rust نوشته شده، همون ایمنیای که دلیل انتخاب مدل پردازهای بود رو در زمان کامپایل میگیره.
چهارمی پلنهای اجرایی بده. planner از بین دهها الگوریتم و بر اساس آمار جدولها انتخاب میکنه و معمولاً درست میزنه، ولی وقتی اشتباه میکنه فاصلهٔ یه پلن خوب و بد میتونه فرق بین ۱۰ میلیثانیه و ۱۰ دقیقه باشه. برخلاف MySQL هم planner hint نداری و تنها کار جدی این میمونه که کل یه الگوریتم رو سراسری خاموش کنی؛ نویسنده میگه تو یه شرکت قبلی مجبور شدن nested loop join رو کاملاً غیرفعال کنن. ایدهاش برای pgrust یه planner تطبیقیه که خودش افت ناگهانی زمان اجرا رو تشخیص بده و اصلاح کنه — که هنوز ساخته نشده.
آخری jsonه. ذخیره و کوئریزدن روی دادهٔ بدون ساختار با json راحته، ولی Postgres برای ستون json آمار معناداری جمع نمیکنه: بهجای هیستوگرام و مقادیر پرتکرار، فرض میکنه فیلتر روی json حدود ۰٫۱٪ ردیفها رو برمیگردونه — یه عدد جادویی که ممکنه واقعیتش ۸۰٪ باشه یا ۰٫۰۰۰۱٪. نتیجهاش پلنهای بد تقریباً تضمینیه. نویسنده اضافه میکنه هیچ فشردهسازیای هم روی json انجام نمیشه و کلیدها و مقدارهای تکراری هر بار کامل ذخیره میشن. برنامهٔ pgrust آمار واقعی برای json و بعد فشردهسازی دیکشنریمحوره.
روی نقشهٔ راه، اولویت اولش رسیدن به سازگاری کامل با Postgresه (الان ۹۶٪ مجموعه تست رگرسیون رو رد میکنه)، بعد پایداری و رفع باگ تا آدمها بتونن بهش اعتماد کنن، و بعد شروع تغییرات معماری از شناسهٔ تراکنش ۶۴ بیتی. یه دموی WASM هم داره که تو مرورگر اجرا میشه.
نکات کلیدی:
- vacuum هم I/O میبره هم اگه عقب بمونه ردیفهای مرده انباشته میشن؛ تنظیمش یه بدهبستون دائمیه
- شناسهٔ تراکنش ۳۲ بیتی یعنی سقف حدود چهار میلیارد تراکنش و خطر خوابیدن دیتابیس بابت wraparound
- هر کانکشن یه پردازهٔ جداست؛ برای همین سقف کانکشن وجود داره و موازیسازی گرون تموم میشه
- Postgres planner hint نداره، پس پلن بد رو فقط با خاموشکردن سراسری الگوریتمها میشه دور زد
- برای json آمار جمع نمیشه و تخمین ثابت ۰٫۱٪ باعث پلنهای بد میشه؛ فشردهسازی هم در کار نیست
- pgrust با مدل thread-based، شناسهٔ ۶۴ بیتی، planner تطبیقی و آمار json سراغ همین چهار مورد رفته




