ایندکسها در ClickHouse در یک نگاه
در این پست با یک مدل ذهنی واضح جلو میرویم:
- دادهها چطور ذخیره میشوند،
- ClickHouse چطور بخشهای بزرگ را skip میکند،
- چه انواع ایندکسی داریم و کجا بهدرد میخورند،
- چطور درست انتخاب و تیون کنیم،
- چکلیست و الگوهای ضدپترن.
مدل ذهنی ذخیرهسازی در ClickHouse
Partition: دادهها به پارتیشنهای مجزا (مثلاً ماهانه) تقسیم میشوند. این خودش یک لایه pruning خیلی قوی است. اگر PARTITION BY toYYYYMM(ts) داشته باشی و روی بازهی زمانی کوئری بزنی، CH مستقیم کلی پارتیشن را کنار میگذارد.
Parts: هر بار insert/merge یک «part» میسازد. هر part شامل فایلهای ستونی (columnar) است.
Granule: هر part به «گرانول»های با اندازه ثابت (پیشفرض معمولاً 8192 ردیف) شکسته میشود. ایندکسها روی این گرانولها خلاصهسازی میشود.
هدف: وقتی شرط WHERE مینویسی، ClickHouse قبل از اسکن، میسنجه کدام گرانولها احتمالاً به شرط میخورند و باقی را skip میکند. همین کار IO را بهشدت کاهش میدهد و سرعت را میبرد بالا. پس بهجای یک Secondary Index کلاسیک که موقع lookup سطر به سطر میپرد، اینجا ایندکسها مثل نقشههای خلاصه هستند که میگویند «این تکهها اصلاً ارزش خواندن ندارند».
سه ستون اصلی Performance: Partitioning، Sorting، Data-Skipping Indexes
Partitioning (خودِ ایندکس نیست، اما…) برای pruning بلاک ها عالی عمل میکنن معمولاً بر اساس زمان (toYYYYMM(ts) یا toDate(ts)) یا tenant/keyهای مهم یا بر اساس کلید های ترکیبی و composition ها ساخته میشوند یا بر اساس توابع بالانسر پارتیشن خیلی ریز (روزانه یا ساعتی) وقتی ingestion بالاست به خاطر همین شاید pruning بلاک ها و پارت ها سخت تر اتفاق بیوفته workloadها رو بیشتر میکنه
ORDER BY (Primary Key Sparse Index)
در ClickHouse PRIMARY KEY معمولاً معادل ORDER BY است. که بعنوان ایندکس اصلی، sparse و بر پایه ترتیب دادههاست. چندستونه و lexicographic است: اگر ORDER BY (ts, user_id) داری، کوئریهایی که اول از ts استفاده میکنند بهترین بهره را میبرند.
- انتخاب کلید مرتبسازی:
- معمولاً با زمان شروع کن (ts اول بیاید)، چون بیشترین فیلتر روی زمان است.(دلخواه)
- ستون(های) بعدی را بر اساس متداولترین فیلترها و کاردینالیتی منطقی انتخاب کن (مثلاً user_id یا tenant_id).
- ستونهای خیلی متغیر (high churn) یا با توزیع خیلی یکنواخت را بیدلیل جلو نیاور.
Data-Skipping Indexes (Secondary/Skipping)
این ایندکسها روی هر گرانول خلاصه نگهداری میشوند تا بتوان تکههای نامرتبط را نخواند. مهمترینها:
- minmax: برای بازهها و مقایسهها عالی (اعداد، تاریخها)، min/max هر گرانول کمک میکند حذف شود.
- bloom_filter: برای membership سریع روی مساوی/IN
- set: وقتی در هر گرانول تعداد یکتای value ها خیلی کم است (low distinct per granule)، یک مجموعهی کوچک میسازد و membership را دقیقتر از Bloom جواب میدهد. اگر تنوع داخل هر گرانول زیاد باشد، فایدهاش کم میشود.
نکته خیلی خیلی مهم : Skipping Index داخل گرانول را سریع نمیکند؛ فقط گرانولهای بیربط را حذف میکند. پس اگر گرانول خیلی بزرگ باشد، حتی بعد از skip ممکن است هنوز زیاد بخوانی.
اشتباه رایج و یک راهنمای سریع ؟
برای aggregation سنگین روی ابعاد مشخص سعی کنیم بهجای ایندکس اضافه، گاهی Materialized View با pre-aggregation یا rollup بهتر جواب میدهد. این درسی بود که تو استفاده از کلیک هاوس و ایندکس تو پروژه بزرگ گرفتم!مرتب سازی جداول رو دقت کنیم Ordering درست column ها میتونه تا جای خوبی مشکلات ما رو حل کنه