ایجنت هوش مصنوعی یه باگ سهساله توی موتور کوئری PostHog پیدا کرد
خلاصهٔ کاملتر
تیم PostHog توی یه آفسایت، یه ایجنت هوش مصنوعی رو به موتور کوئری ClickHouseشون وصل کرد، کوئریهای کند پروداکشن رو بهش داد و یه شب ولش کرد. صبح یه چیز خجالتآور پیدا شده بود: تقریباً سه سال بود که هر کوئری با فیلتر timestamp، کلید اصلیِ ClickHouse رو درست استفاده نمیکرد. رفعش تعداد granuleهایی که ClickHouse باید اسکن میکرد رو روی کوئری بنچمارک ۶۲٪ کم کرد.
این روش که آندری کارپتی مارس ۲۰۲۶ اسمش رو گذاشت autoresearch، سادهست: به یه ایجنت یه سیستم واقعی ولی کوچیک، یه بنچمارک و یه بودجه بده و بذار توی یه حلقه بچرخه؛ یه تغییر پیشنهاد بده، بنچمارک رو اجرا کن، چیزی که کمک کرد رو نگه دار و بقیه رو دور بنداز. نکتهی جالب برای PostHog اثر مرتبهدومش بود: ایجنت سوگیری کسی که سالها توی یه کدبیس زندگی کرده رو نداره. برای اونها toTimeZone() همیشه اونجا بوده و دیگه دیده نمیشد، ولی ایجنت بدون هیچ پیشفرضی به یه عبارت سهساله هم با همون شکی نگاه میکنه که به خطی که دیروز نوشتی.
خود باگ اینجوریه: جدول events با PARTITION BY toYYYYMM(timestamp) پارتیشن شده و کلید اصلیش با toDate(timestamp) کار میکنه. ولی وقتی آوریل ۲۰۲۳ پشتیبانی از تایمزون per-team اضافه شد، هر ارجاع به timestamp رو توی toTimeZone(timestamp, team_tz) پیچیدن تا تاریخهای نمایشی درست باشه. چیزی که نفهمیده بودن این بود که پلنر کوئری ClickHouse نمیتونه از پشت toTimeZone() رو ببینه، پس نه partition pruning کار میکرد و نه کلید اصلی تا انتها.
دلیل اینکه این باگ هیچوقت آلارم نزد یه skip index از نوع MinMax روی timestamp بود که حداقل و حداکثر هر granule رو نگه میداره و باعث میشد کوئریها فاجعهبار کند نباشن، فقط محسوس کندتر از چیزی که باید. این دقیقاً همون نوع باگیه که برای همیشه قایم میمونه: کنده ولی نه اونقدر که کسی رو خبر کنه، و مدرکش هم فقط توی خروجی EXPLAIN PLAN indexes=1 معلومه که کسی اجراش نمیکنه مگه از قبل مشکوک باشه.
راهحلی که ایجنت پیدا کرد و شیپ شد، نوشتن دوبارهی مقایسه بود؛ بهجای پیچیدن فیلد، فیلد رو لخت بذار و تایمزون رو روی ثابت سوار کن:
-- قبل: پلنر از پشت toTimeZone را نمیبیند
toTimeZone(timestamp, 'US/Pacific') >= '2024-03-01'
-- بعد: فیلد لخت سمت چپ، ثابتِ تایمزوندار سمت راست
timestamp >= toDateTime64('2024-03-01', 6, 'US/Pacific')معنای دو عبارت یکیه چون toTimeZone() فقط متادیتای نمایش رو عوض میکنه و epoch زیرین دستنخورده میمونه؛ حالا پلنر یه timestamp لخت میبینه و میتونه کارش رو بکنه. روی یه فانل ۷روزهی واقعی، بهترین اجرا ۲۲٪ و میانگین حدود ۳۷٪ سریعتر شد. تیم الان داره یه pipeline میسازه که خودکار کوئریهای کند رو از system.query_log بکشه، توی سندباکس روی هرکدوم autoresearch بزنه و PRها رو برای بازبینی انسانی به Slack بفرسته.
نکات کلیدی:
- یه ایجنت autoresearch یه شبه باگی پیدا کرد که سه سال پنهان مونده بود
- پیچیدن timestamp توی toTimeZone() پلنر ClickHouse رو از partition pruning و کلید اصلی کور میکرد
- skip index از نوع MinMax باعث شده بود باگ فقط کند باشه، نه فاجعهبار، برای همین لو نمیرفت
- با لختگذاشتن فیلد و تایمزوندار کردن ثابت، اسکن granuleها ۶۲٪ و زمان حدود ۳۷٪ کم شد
- PostHog داره این فرایند را به یه pipeline خودکار با سندباکس و بازبینی انسانی تبدیل میکنه




