پایتون و Postgres سر معنی «CET» به توافق نمیرسن
خلاصهٔ کاملتر
تو این پست، Thomas Berdy از Mergify تعریف میکنه که یه کاربر از طریق API یه scheduled freeze (یعنی بازهٔ زمانی که merge کردن تو یه ریپو متوقف میشه) ساخت و تایمزونش رو localtime گذاشت. API قبولش کرد، ولی از اون به بعد ارزیابی همهٔ pull requestهای اون ریپو شکست خورد. هر ۱۲ ثانیه یه خطا تو Sentry ثبت میشد و تو ساعتی که آلارم زد، ۳۱۳ تا از حدود ۳۴۴ خطا مال همین یه ردیف بود.
ریشهٔ ماجرا اینه که یه ستون رو دو برنامه با دو دیتابیس تایمزون متفاوت میخونن. Postgres با AT TIME ZONE تصمیم میگیره کدوم freezeها الان فعالن و کدوما باید پاک بشن، ولی پایتون با zoneinfo خود freeze رو ارزیابی میکنه. API هم فقط با zoneinfo.available_timezones() چک میکرد، یعنی فقط نظر پایتون رو میپرسید. پایتون تو image اونا ۵۹۹ اسم میشناسه و Postgres روی Cloud SQL فقط ۵۹۷ تا.
دو اسمی که Postgres قبول نمیکنه localtime (که تو Debian یه symlink به /etc/localtime هست) و Factory هستن. مشکل بیصداتر چهار اسم CET، EET، MET و WET بود. تو پایتون CET یه زون با ساعت تابستونیه، ولی Postgres اول اسم رو تو جدول abbreviationهاش میگرده و اونجا CET یه آفست ثابت +01:00 هست:
SELECT timestamp '2024-07-15 12:00' AT TIME ZONE 'CET'; -- 2024-07-15 11:00:00+00
SELECT timestamp '2024-07-15 12:00' AT TIME ZONE 'Europe/Paris'; -- 2024-07-15 10:00:00+00یعنی از اواخر مارس تا اواخر اکتبر، یه freeze با CET برای Postgres یه ساعت دیرتر شروع و تموم میشه. صف merge تو ساعت اول freeze رو نمیدید و تا یه ساعت بعد از تموم شدنش هم اولویت PRها رو پایین نگه میداشت، اونم بدون هیچ خطا یا لاگی. خوشبختانه هیچ freezeی تو production از این چهار اسم استفاده نکرده بود.
نویسنده میگه دو راهحل اولش رو دور انداخت. اولی قبل از ذخیره از خود Postgres میپرسید، ولی Postgres به CET بدون اعتراض جواب میده. دومی خوندن تایمزون رو از Postgres میگرفت، ولی فقط یه فایل رو درست میکرد. چیزی که آخر سر شیپ شد یه فیلد pydantic به اسم PostgresTimeZone بود که همهٔ اسمهای پایتون رو قبول میکنه، بهجز این شش تا:
_TIME_ZONES_POSTGRES_READS_OTHERWISE = frozenset(
{"localtime", "Factory", "CET", "EET", "MET", "WET"},
)کنارش یه parity test (یعنی تستی که خروجی دو طرف رو با هم مقایسه میکنه) برای هر اسم، ظهر ژانویه و ظهر جولای رو هم تو Postgres و هم تو پایتون تبدیل میکنه و چک میکنه هر دو یه لحظه رو بدن. Postgres تستی فقط ۴۸۷ اسم میشناسه، پس تست چک میکنه بیشتر از ۴۰۰ اسم مقایسه شده باشن. به گفتهٔ نویسنده، راهحل اساسیتر ذخیرهٔ زمانها بهصورت timestamptz به وقت UTC هست که AT TIME ZONE رو کلاً از کوئریها حذف میکنه.
نکات کلیدی:
- پایتون تو image اونا ۵۹۹ اسم تایمزون میشناسه و Postgres روی Cloud SQL ۵۹۷ تا.
- localtime و Factory تو Postgres خطا میدن و کوئریها رو برای کل ریپو یا کل جدول میخوابونن.
- CET، EET، MET و WET تو Postgres آفست ثابت دارن و تابستونها یه ساعت با پایتون فرق دارن.
- راهحل یه فیلد pydantic به اسم PostgresTimeZone به همراه یه parity test بود.
- تو production ۱۹ اسم تایمزون در ۱۵۰ freeze بود و همهشون از اعتبارسنجی جدید رد شدن.
- پکیج tzdata-legacy نگه داشته شد، چون Debian برای دیتابیسها توصیهش میکنه و Chrome هنوز برای هند Asia/Calcutta گزارش میده.




