يشير TTL (مدة البقاء) إلى إمكانية نقل الصفوف أو الأعمدة أو حذفها أو تلخيصها بعد انقضاء مدة زمنية معيّنة. ورغم أن مصطلح “مدة البقاء” يوحي بأنه يقتصر على حذف البيانات القديمة، فإن لـ TTL عدة حالات استخدام:
- إزالة البيانات القديمة: لا مفاجأة هنا، إذ يمكنك حذف الصفوف أو الأعمدة بعد مدة زمنية محددة
- نقل البيانات بين الأقراص: بعد مرور فترة معيّنة، يمكنك نقل البيانات بين وحدات التخزين، وهو ما يفيد في نشر معمارية hot/warm/cold
- تلخيص البيانات: يمكنك تلخيص بياناتك الأقدم في تجميعات وعمليات احتساب مفيدة متنوعة قبل حذفها
يمكن تطبيق TTL على جداول كاملة أو على أعمدة محددة.
يمكن أن تَرِد عبارة TTL بعد تعريف عمود و/أو في نهاية تعريف الجدول. استخدم عبارة INTERVAL لتحديد فترة زمنية (ويجب أن تكون من نوع البيانات Date أو DateTime). على سبيل المثال، يحتوي الجدول التالي على عمودين
مع عبارتي TTL:
CREATE TABLE example1 (
timestamp DateTime,
x UInt32 TTL timestamp + INTERVAL 1 MONTH,
y String TTL timestamp + INTERVAL 1 DAY,
z String
)
ENGINE = MergeTree
ORDER BY tuple()
- يحتوي العمود x على مدة بقاء قدرها شهر واحد بدءًا من عمود الطابع الزمني
- يحتوي العمود y على مدة بقاء قدرها يوم واحد بدءًا من عمود الطابع الزمني
- عند انقضاء الفاصل الزمني، تنتهي صلاحية العمود. يستبدل ClickHouse قيمة العمود بالقيمة الافتراضية لنوع بياناته. وإذا انتهت صلاحية جميع قيم العمود في جزء البيانات، يحذف ClickHouse هذا العمود من جزء البيانات في نظام الملفات.
أفضل الممارساتعند استخدام TTL على مستوى الجدول لإزالة الصفوف القديمة، نوصي بأن تُقسِّم جدولك حسب التاريخ أو الشهر للحقل الزمني نفسه المستخدم في تعبير TTL.يمكن لـ ClickHouse حذف أقسام كاملة بكفاءة أعلى بكثير من حذف صفوف فردية.
وعندما يتوافق مفتاح التقسيم مع تعبير TTL، يمكن لـ ClickHouse حذف أقسام كاملة دفعة واحدة عند انتهاء صلاحيتها، بدلًا من إعادة كتابة أجزاء البيانات لإزالة الصفوف منتهية الصلاحية.اختر درجة التقسيم بناءً على فترة TTL:
- بالنسبة إلى TTL بالأيام/الأسابيع: قسّم حسب اليوم باستخدام
toYYYYMMDD(date_field)
- بالنسبة إلى TTL بالأشهر/السنوات: قسّم حسب الشهر باستخدام
toYYYYMM(date_field) أو toStartOfMonth(date_field)
إن حذف الصفوف منتهية الصلاحية أو تجميعها لا يحدث فورًا، بل لا يتم إلا أثناء عمليات دمج الجدول. وإذا كان لديك جدول لا تُجرى له عمليات دمج بشكل نشط (لأي سبب كان)، فهناك إعدادان يُشغّلان أحداث TTL:
merge_with_ttl_timeout: الحد الأدنى للتأخير بالثواني قبل تكرار عملية دمج مع TTL للحذف. القيمة الافتراضية هي 14400 ثانية (4 ساعات).
merge_with_recompression_ttl_timeout: الحد الأدنى للتأخير بالثواني قبل تكرار عملية دمج مع TTL لإعادة الضغط (القواعد التي تُجمّع البيانات قبل حذفها). القيمة الافتراضية: 14400 ثانية (4 ساعات).
لذلك، وبشكل افتراضي، ستُطبَّق قواعد TTL على جدولك مرة واحدة على الأقل كل 4 ساعات. ما عليك سوى تعديل الإعدادات أعلاه إذا كنت بحاجة إلى تطبيق قواعد TTL بوتيرة أعلى.
ليس هذا حلًا مثاليًا (ولا نوصي باستخدامه كثيرًا)، ولكن يمكنك أيضًا فرض عملية دمج باستخدام OPTIMIZE:OPTIMIZE TABLE example1 FINAL
يبدأ OPTIMIZE عملية دمج غير مجدولة لأجزاء جدولك، بينما يفرض FINAL إعادة التحسين إذا كان جدولك يتكوّن بالفعل من جزء واحد.
لإزالة صفوف كاملة من جدول بعد فترة زمنية معينة، حدِّد قاعدة TTL على مستوى الجدول:
CREATE TABLE customers (
timestamp DateTime,
name String,
balance Int32,
address String
)
ENGINE = MergeTree
ORDER BY timestamp
TTL timestamp + INTERVAL 12 HOUR
بالإضافة إلى ذلك، يمكن تعريف قاعدة TTL استنادًا إلى قيمة السجل.
ويمكن تنفيذ ذلك بسهولة عبر تحديد شرط WHERE.
ويُسمح بوجود شروط متعددة:
CREATE TABLE events
(
`event` String,
`time` DateTime,
`value` UInt64
)
ENGINE = MergeTree
ORDER BY (event, time)
TTL time + INTERVAL 1 MONTH DELETE WHERE event != 'error',
time + INTERVAL 6 MONTH DELETE WHERE event = 'error'
بدلًا من حذف الصف بأكمله، لنفترض أنك تريد فقط انتهاء صلاحية عمودي الرصيد والعنوان. لنعدّل جدول customers ونضيف قيمة TTL لكلا العمودين لمدة ساعتين:
ALTER TABLE customers
MODIFY COLUMN balance Int32 TTL timestamp + INTERVAL 2 HOUR,
MODIFY COLUMN address String TTL timestamp + INTERVAL 2 HOUR
لنفترض أننا نريد حذف الصفوف بعد مرور فترة زمنية معينة، مع الاحتفاظ ببعض البيانات لأغراض إعداد التقارير. نحن لا نحتاج إلى كل التفاصيل، بل إلى بعض النتائج المجمّعة فقط من البيانات التاريخية. يمكن تنفيذ ذلك بإضافة عبارة GROUP BY إلى تعبير TTL، إلى جانب بعض الأعمدة في جدولك لتخزين النتائج المجمّعة.
لنفترض أننا نريد، في جدول hits التالي، حذف الصفوف القديمة مع الاحتفاظ بمجموع القيم والقيمة العظمى لأعمدة hits قبل إزالة الصفوف. سنحتاج إلى حقل لتخزين هذه القيم، كما سنحتاج إلى إضافة عبارة GROUP BY إلى عبارة TTL لتنفيذ تلخيص للمجموع والقيمة العظمى:
CREATE TABLE hits (
timestamp DateTime,
id String,
hits Int32,
max_hits Int32 DEFAULT hits,
sum_hits Int64 DEFAULT hits
)
ENGINE = MergeTree
PRIMARY KEY (id, toStartOfDay(timestamp), timestamp)
TTL timestamp + INTERVAL 1 DAY
GROUP BY id, toStartOfDay(timestamp)
SET
max_hits = max(max_hits),
sum_hits = sum(sum_hits);
بعض الملاحظات حول جدول hits:
- يجب أن تكون الأعمدة في عبارة
GROUP BY ضمن عبارة TTL بادئة لـ PRIMARY KEY، ونريد تجميع نتائجنا حسب بداية اليوم. لذلك أُضيفت toStartOfDay(timestamp) إلى المفتاح الأساسي
- أضفنا حقلين لتخزين النتائج المجمّعة:
max_hits و sum_hits
- إن تعيين القيمة الافتراضية لـ
max_hits و sum_hits إلى hits ضروري لعمل هذا المنطق، استنادًا إلى كيفية تعريف عبارة SET
تنفيذ معمارية hot/warm/cold
إذا كنت تستخدم ClickHouse Cloud، فلن تنطبق الخطوات الواردة في هذا الدرس. ولا داعي للقلق بشأن نقل البيانات القديمة داخل ClickHouse Cloud.
من الممارسات الشائعة عند العمل مع كميات كبيرة من البيانات نقلها كلما تقادمت. في ما يلي خطوات تنفيذ معمارية hot/warm/cold في ClickHouse باستخدام العبارتين TO DISK وTO VOLUME في الأمر TTL. (وبالمناسبة، لا يقتصر الأمر على hot وcold فقط — إذ يمكنك استخدام TTL لنقل البيانات وفقًا لأي حالة استخدام لديك.)
- يشير الخياران
TO DISK وTO VOLUME إلى أسماء الأقراص أو وحدات التخزين المعرّفة في ملفات تكوين ClickHouse. أنشئ ملفًا جديدًا باسم my_system.xml (أو بأي اسم ملف آخر) لتعريف الأقراص، ثم عرّف وحدات تخزين تستخدم هذه الأقراص. ضع ملف XML في /etc/clickhouse-server/config.d/ لتطبيق الإعدادات على نظامك:
<clickhouse>
<storage_configuration>
<disks>
<default>
</default>
<hot_disk>
<path>./hot/</path>
</hot_disk>
<warm_disk>
<path>./warm/</path>
</warm_disk>
<cold_disk>
<path>./cold/</path>
</cold_disk>
</disks>
<policies>
<default>
<volumes>
<default>
<disk>default</disk>
</default>
<hot_volume>
<disk>hot_disk</disk>
</hot_volume>
<warm_volume>
<disk>warm_disk</disk>
</warm_volume>
<cold_volume>
<disk>cold_disk</disk>
</cold_volume>
</volumes>
</default>
</policies>
</storage_configuration>
</clickhouse>
- يشير التكوين أعلاه إلى ثلاثة أقراص تشير إلى مجلدات يمكن لـ ClickHouse القراءة منها والكتابة إليها. ويمكن أن تحتوي وحدات التخزين على قرص واحد أو أكثر - وقد عرّفنا وحدة تخزين لكل قرص من الأقراص الثلاثة. لنستعرض الأقراص:
SELECT name, path, free_space, total_space
FROM system.disks
┌─name────────┬─path───────────┬───free_space─┬──total_space─┐
│ cold_disk │ ./data/cold/ │ 179143311360 │ 494384795648 │
│ default │ ./ │ 179143311360 │ 494384795648 │
│ hot_disk │ ./data/hot/ │ 179143311360 │ 494384795648 │
│ warm_disk │ ./data/warm/ │ 179143311360 │ 494384795648 │
└─────────────┴────────────────┴──────────────┴──────────────┘
- والآن… لنتأكد من وحدات التخزين:
SELECT
volume_name,
disks
FROM system.storage_policies
┌─volume_name─┬─disks─────────┐
│ default │ ['default'] │
│ hot_volume │ ['hot_disk'] │
│ warm_volume │ ['warm_disk'] │
│ cold_volume │ ['cold_disk'] │
└─────────────┴───────────────┘
- سنضيف الآن قاعدة
TTL تنقل البيانات بين وحدات التخزين الساخنة والدافئة والباردة:
ALTER TABLE my_table
MODIFY TTL
trade_date TO VOLUME 'hot_volume',
trade_date + INTERVAL 2 YEAR TO VOLUME 'warm_volume',
trade_date + INTERVAL 4 YEAR TO VOLUME 'cold_volume';
- يجب أن تدخل قاعدة
TTL الجديدة حيّز التنفيذ، ولكن يمكنك فرض ذلك للتأكد:
ALTER TABLE my_table
MATERIALIZE TTL
- تحقّق من انتقال بياناتك إلى الأقراص المتوقعة باستخدام الجدول
system.parts:
Using the system.parts table, view which disks the parts are on for the crypto_prices table:
SELECT
name,
disk_name
FROM system.parts
WHERE (table = 'my_table') AND (active = 1)
سيكون الإخراج كما يلي:
┌─name────────┬─disk_name─┐
│ all_1_3_1_5 │ warm_disk │
│ all_2_2_0 │ hot_disk │
└─────────────┴───────────┘