WINDOW JOIN در QuestDB: ۲۵ برابر سریعتر از ClickHouse
خلاصهٔ کاملتر
یه سناریوی رایج در دسکهای معاملاتی اینه که برای هر معاملهای که انجام شده، میانگین bid و ask در یه بازهی ۱ ثانیهای اطراف اون معامله رو محاسبه کنیم. قبلاً این کار با دو JOIN جداگانه — یه ASOF JOIN برای قیمت ابتدای پنجره و یه range JOIN برای ردیفهای داخل پنجره — همراه با UNION ALL و GROUP BY نهایی انجام میشد. این روش پیچیدهست، بهینهسازی روش سختیه، و بهخاطر hash aggregation روی ۵۰ میلیون گروه، بسیار کند.
WINDOW JOIN سینتکس اختصاصی QuestDB برای همین کاره. بهجای اون کوئری چندلایه، میشه نوشت:
SELECT t.*, avg(p.bid) avg_bid, avg(p.ask) avg_ask
FROM trades t
WINDOW JOIN prices p
ON p.sym = t.symbol
RANGE BETWEEN 1 second PRECEDING AND 1 second FOLLOWING;این اپراتور دقیقاً میدونه داره چیکار میکنه: بهازای هر ردیف سمت چپ (جدول trades)، ردیفهایی از سمت راست (جدول prices) رو که timestampشون در بازهی [lo, hi] قرار داره پیدا میکنه، فیلتر key میزنه، و اونها رو با توابع aggregate کاهش میده.
سرعت این اپراتور به دو چیز بستگی داره. اول، موازیسازی در سطح داده: QuestDB داده رو در قالب page frame (تکههای ستونی پیوسته در حافظه) نگه میداره. WINDOW JOIN جدول چپ رو به frame تقسیم میکنه و هر worker یه frame میگیره. چون هر دو جدول بر اساس timestamp مرتب روی دیسک ذخیره شدن، پیدا کردن slice مربوط به RHS برای هر worker فقط یه binary search لازه، نه اسکن کامل.
دوم، مسیر سریع برای key های کمکاردینالیتی: وقتی join روی یه ستون symbol با مقادیر کم (مثل نماد سهام) باشه و aggregate ها از نوع sum / avg / min / max / count باشن، worker مقادیر RHS رو در بافرهای پیوسته per-key کپی میکنه. این بافرها دقیقاً همون فرمتی هستن که کرنلهای SIMD (AVX2) انتظار دارن — هشت double در هر iteration، بدون branch، بدون scatter. نتیجه اینه که aggregate روی slice پنجره با همون کرنلهای بهینهای اجرا میشه که SAMPLE BY هم ازشون استفاده میکنه.
برای بنچمارک روی یه workstation با AMD Ryzen 9 7900 (12 هسته)، ۶۴ گیگ رم و NVMe SSD نتایج اینطوری بود: QuestDB موازی + SIMD در ۱۳.۶۹ ثانیه تموم کرد، QuestDB تکthread در ۶۷.۹۷ ثانیه (یعنی موازیسازی ~۵ برابر سرعت میده)، و ClickHouse در ۳۴۷.۶۳ ثانیه — یعنی ۲۵ برابر کندتر. DuckDB و TimescaleDB اصلاً تو سقف ۳۰ دقیقهای تموم نکردن؛ DuckDB به خاطر tail serialization با ۱۰۰۰ symbol و TimescaleDB چون temp file هاش تمام دیسک (بیش از ۵۴۱ گیگ!) رو پر کردن.
دلیل برتری QuestDB ترکیب سه چیزه: اپراتور اختصاصی که ساختار پنجره رو میفهمه و نیازی به materialization میانی نداره، موازیسازی در سطح data frame، و SIMD روی slice های پیوسته. میتونی با EXPLAIN بفهمی کوئریت از مسیر سریع (Async Window Fast Join با vectorized: true) استفاده میکنه یا از fallback اسکالر.
نکات کلیدی:
- WINDOW JOIN سینتکس جدید QuestDB برای aggregate گرفتن از یه جدول در بازهی زمانی اطراف هر ردیف جدول دیگهست
- پردازش موازی روی page frame ها ~۵ برابر سرعت میده نسبت به حالت تکthread
- مسیر سریع AVX2 برای key های کمکاردینالیتی و aggregate های استاندارد (sum/avg/min/max/count) فعاله
- در بنچمارک ۵۰M trades در برابر ۱۵۰M prices: ClickHouse 25x کندتر، DuckDB و TimescaleDB اصلاً تموم نکردن
- با
EXPLAINمیشه دید آیا کوئری از مسیر vectorized استفاده میکنه یا نه




