הרצת ClickHouse בסביבת ייצור: ingestion, מיזוגים ועלות ב‑2 מיליארד שורות
הניסיון שלנו בהרצת ClickHouse בייצור: תכנון סכימה, אופטימיזציית שאילתות ואיך אנו משיגים אנליטיקה בפחות משנייה על פני יותר מ‑100 מיליון אירועי מכשיר.
כשהתחלנו לבנות את שכבת האנליטיקה של tracio.ai, נדרש לנו מסד נתונים שיוכל להתמודד עם עומס העבודה הספציפי שלנו: לקלוט 50,000 אירועי זיהוי מכשיר בשנייה, לאחסן יותר מ‑2 מיליארד שורות ולענות על שאילתות אנליטיות בפחות משנייה אחת. הערכנו את PostgreSQL (איטי מדי לצבירות בקנה מידה כזה), את Elasticsearch (יקר מדי לאנליטיקה של סדרות זמן) ואת ClickHouse. ClickHouse ניצח באופן מוחלט.
למה ClickHouse
ClickHouse הוא מסד נתונים OLAP מבוסס‑עמודות שתוכנן לאנליטיקה בזמן אמת. היתרון המרכזי שלו עבור עומס העבודה שלנו הוא שהוא קורא רק את העמודות הנחוצות לכל שאילתה. כשאנליסט הונאה מבקש "הראה לי את שיעור ההונאה לפי מדינה ל‑7 הימים האחרונים", ClickHouse קורא רק את העמודות country, timestamp ו‑risk_score — ומתעלם מיותר מ‑40 העמודות האחרות בטבלת האירועים. על טבלה של 2 מיליארד שורות, זה מפחית את ה‑I/O ב‑95%.
ClickHouse גם דוחס נתונים היטב במיוחד. טבלת האירועים שלנו בת 2 מיליארד השורות תופסת 340 GB בדיסק — כ‑170 בייט לשורה בדחיסה, לעומת 1.2 KB לשורה ללא דחיסה. יחס הדחיסה של 7:1 פירושו שיותר נתונים נכנסים לזיכרון, מה שמיתרגם ישירות לשאילתות מהירות יותר.
תכנון סכימה
הטבלה הראשית שלנו מאחסנת שורה אחת לכל אירוע זיהוי:
הטבלה משתמשת במנוע MergeTree, מסודרת לפי (workspace_id, toDate(timestamp), visitor_hash). סידור זה קריטי — משמעותו שהשאילתות המסוננות לפי workspace וטווח תאריכים קוראות מינימום נתונים. עמודת ה‑visitor_hash מאפשרת חיפושים מהירים לפי visitor ID ללא אינדקס משני.
בחרנו ב‑LowCardinality(String) עבור country, device_type, browser_family ו‑os_family משום שלעמודות אלו יש פחות מ‑10,000 ערכים ייחודיים. ClickHouse מאחסן עמודות LowCardinality כמספרים שלמים בקידוד מילוני, מה שמפחית את האחסון ב‑80% בהשוואה למחרוזות רגילות ומזרז פעולות GROUP BY.
אסטרטגיית Sharding
אנו מפזרים (shard) את טבלת האירועים על פני 6 nodes באמצעות hash של workspace_id. זה מבטיח שכל האירועים של לקוח נתון נמצאים על אותו shard, מה שאומר שרוב השאילתות (המסוננות לפי workspace_id) פוגעות ב‑shard בודד. שאילתות חוצות‑shard נחוצות רק לאנליטיקה פנימית.
לכל shard יש 2 replicas לזמינות גבוהה. השכפול משתמש במנוע ReplicatedMergeTree המובנה של ClickHouse עם תיאום ZooKeeper. ה‑failover אוטומטי — אם shard נופל, השאילתות מנותבות ל‑replica ללא שינויים בצד הלקוח.
צינור ה‑Ingestion
האירועים זורמים מ‑Kafka topic שלנו לתוך ClickHouse דרך שירות Go מותאם אישית שמבצע אצוות של הכנסות. אנו מכניסים באצוות של 10,000 שורות כל 500ms — זה מאזן בין השהיית ingestion (מתחת לשנייה) ליעילות ההכנסה (ClickHouse מתפקד הכי טוב עם אצוות גדולות).
שירות ה‑ingestion מטפל ב‑back‑pressure באלגנטיות. אם ClickHouse איטי בקבלת הכנסות (במהלך מיזוגים או עומס שאילתות כבד), השירות מאגר עד מיליון אירועים בזיכרון ומפעיל back‑pressure על ה‑Kafka consumer. ב‑18 חודשים של ייצור, מעולם לא איבדנו אירוע.
אופטימיזציית שאילתות
Materialized Views
לשאילתות dashboard נפוצות, אנו משתמשים ב‑materialized views שמצברות נתונים מראש. לוח ההונאה שלנו, למשל, קורא מ‑materialized view שמצבר ספירות fraud_detected לפי workspace, מדינה ושעה. ה‑view מפחית את הנתונים הנסרקים לשאילתה זו מ‑2 מיליארד שורות ל‑5 מיליון שורות.
סידור Projection
projections של ClickHouse מאפשרים לנו להגדיר סדרי מיון חלופיים לטבלה ללא שכפול נתונים. הוספנו projection מסודר לפי (workspace_id, visitor_hash, timestamp) לשאילתות ציר הזמן של visitor. ללא ה‑projection, שאילתות אלו סרקו טווחי תאריכים שלמים. איתו, הן קוראות רק את ה‑blocks המכילים את ה‑visitor המבוקש.
פונקציות מקורבות
לשאילתות dashboard שבהן ספירות מדויקות אינן קריטיות, אנו משתמשים בפונקציות המקורבות של ClickHouse: uniqCombined לספירות ייחודיות (שוליי טעות של 2%, מהירה פי 10 מ‑uniqExact) ו‑quantileTDigest לחישובי אחוזונים. לוח אנליטיקת ההונאה משתמש אך ורק בפונקציות מקורבות, מה ששומר את כל שאילתות ה‑dashboard מתחת ל‑200ms.
מספרי ביצועים
הנה מדדי שאילתות מייצגים על אשכול הייצור שלנו בן 2 מיליארד השורות:
שיעור הונאה לפי מדינה, 7 ימים אחרונים: 120ms. ציר זמן של visitor (50 אירועים): 8ms. visitors ייחודיים ליום, 30 ימים אחרונים: 340ms. התפלגות risk score, 24 שעות אחרונות: 95ms. 100 המכשירים המובילים לפי ספירת אירועים, 30 ימים אחרונים: 210ms.
מספרים אלו כוללים את זמן הלוך‑ושוב ברשת משרתי האפליקציה שלנו אל אשכול ClickHouse. זמן ביצוע השאילתה הטהור נמוך בדרך כלל ב‑30‑50%.
לקחים תפעוליים
לקח 1: נטרו merge lag
מנוע ה‑MergeTree של ClickHouse ממזג ברציפות parts קטנים של נתונים לגדולים יותר. אם המיזוגים מפגרים (עקב קצב הכנסה גבוה או תחרות על I/O של הדיסק), ביצועי השאילתות מתדרדרים כי השאילתות חייבות לסרוק יותר parts. אנו מנטרים את ספירת ה‑parts לכל partition ומתריעים כשהיא עוברת את 300.
לקח 2: הימנעו מפעולות ALTER TABLE גדולות
הוספת עמודה לטבלה של 2 מיליארד שורות ב‑ClickHouse היא מיידית (זו פעולת metadata בלבד). אך שינוי טיפוס של עמודה מחייב שכתוב של כל ה‑parts של הנתונים — תהליך שלקח 6 שעות על האשכול שלנו. כעת אנו מתייחסים לסכימה כאל append‑only: עמודות חדשות נוספות בחופשיות, אך שינויי טיפוס עוברים דרך טבלת migration.
לקח 3: TTL בזהירות
ClickHouse תומך בתפוגת נתונים אוטומטית באמצעות TTL. הגדרנו TTL של 90 יום על טבלת האירועים שלנו. המלכוד: מחיקת TTL מתרחשת במהלך מיזוגים, מה שאומר שנתונים שנמחקו עשויים להישאר שעות או ימים אחרי תום תוקף ה‑TTL. למחיקה קריטית לתאימות (compliance), אנו מריצים שאילתות ALTER TABLE DELETE מפורשות לפי לוח זמנים.
עלות
אשכול ClickHouse שלנו בן 6 nodes (כל node: 32 vCPU, 128 GB RAM, 2 TB NVMe) עולה כ‑$8,400 בחודש על אחסון bare metal. הוא מאחסן 2 מיליארד שורות עם שמירה של 90 יום ומטפל ב‑50K הכנסות בשנייה בתוספת 200 שאילתות dashboard במקביל. העלות לאירוע מאוחסן היא $0.0000042 — זול בסדרי גודל מאנליטיקה דומה במסדי נתונים מנוהלים בענן.