الانتقال إلى المحتوى الرئيسي
تتيح ذاكرة التخزين المؤقت للاستعلامات تنفيذ استعلامات SELECT مرة واحدة فقط، ثم تقديم عمليات التنفيذ اللاحقة للاستعلام نفسه مباشرةً من ذاكرة التخزين المؤقت. واعتمادًا على نوع الاستعلامات، يمكن أن يؤدي ذلك إلى تقليل زمن الوصول واستهلاك الموارد على ClickHouse server بشكل كبير.

الخلفية والتصميم والقيود

يمكن عمومًا النظر إلى ذاكرات التخزين المؤقت للاستعلامات على أنها متسقة أو غير متسقة من ناحية المعاملات.
  • في ذاكرات التخزين المؤقت المتسقة من ناحية المعاملات، تُبطِل قاعدة البيانات (تستبعد) نتائج الاستعلامات المخزنة مؤقتًا إذا تغيّرت نتيجة استعلام SELECT أو كان من المحتمل أن تتغيّر. في ClickHouse، تشمل العمليات التي تغيّر البيانات عمليات insert/update/delete في الجداول أو عمليات الدمج من نوع collapsing. ويُعدّ التخزين المؤقت المتسق من ناحية المعاملات مناسبًا بشكل خاص لقواعد بيانات OLTP، مثل MySQL (الذي أزال ذاكرة التخزين المؤقت للاستعلامات بعد v8.0) و Oracle.
  • في ذاكرات التخزين المؤقت غير المتسقة من ناحية المعاملات، يُقبل وجود فروق طفيفة في نتائج الاستعلامات على افتراض أن جميع عناصر cache تُمنح فترة صلاحية تنتهي بعدها (مثلًا دقيقة واحدة)، وأن البيانات الأساسية لا تتغير إلا بقدر محدود خلال هذه الفترة. ويُعد هذا النهج، عمومًا، أنسب لقواعد بيانات OLAP. وكمثال على حالة يكون فيها التخزين المؤقت غير المتسق من ناحية المعاملات كافيًا، تخيّل تقرير مبيعات بالساعة في أداة لإعداد التقارير يصل إليه عدة مستخدمين في الوقت نفسه. فعادةً ما تتغير بيانات المبيعات ببطء يكفي لأن تحتاج قاعدة البيانات إلى compute التقرير مرة واحدة فقط (ويمثل ذلك أول استعلام SELECT). وبعد ذلك يمكن تقديم الاستعلامات اللاحقة مباشرةً من ذاكرة التخزين المؤقت للاستعلامات. وفي هذا المثال، قد تكون فترة صلاحية معقولة 30 دقيقة.
يُوفَّر التخزين المؤقت غير المتسق من ناحية المعاملات تقليديًا عبر أدوات client أو حزم proxy (مثل chproxy) التي تتفاعل مع قاعدة البيانات. ونتيجةً لذلك، غالبًا ما يتكرر منطق التخزين المؤقت نفسه و الإعداد نفسه. ومع ذاكرة التخزين المؤقت للاستعلامات في ClickHouse، ينتقل منطق التخزين المؤقت إلى جهة server. وهذا يقلل جهد الصيانة ويجنب الازدواجية.

إعدادات التكوين والاستخدام

في ClickHouse Cloud، يجب استخدام إعدادات على مستوى الاستعلام لتعديل إعدادات ذاكرة التخزين المؤقت للاستعلامات. لا تتوفر حاليًا إمكانية تعديل إعدادات على مستوى config.
يشغّل clickhouse-local استعلامًا واحدًا فقط في كل مرة. ونظرًا إلى أن تخزين نتيجة الاستعلام مؤقتًا لا يكون ذا جدوى، فإن ذاكرة التخزين المؤقت لنتيجة الاستعلام تكون معطّلة في clickhouse-local.
يمكن استخدام الإعداد use_query_cache للتحكم في ما إذا كان ينبغي لاستعلام معيّن أو لجميع استعلامات الجلسة الحالية استخدام ذاكرة التخزين المؤقت للاستعلامات. على سبيل المثال، أول تنفيذ للاستعلام
SELECT some_expensive_calculation(column_1, column_2)
FROM table
SETTINGS use_query_cache = true;
سيُخزِّن نتيجة الاستعلام في ذاكرة التخزين المؤقت للاستعلامات. وستقرأ عمليات التنفيذ اللاحقة للاستعلام نفسه (أيضًا مع المعلَمة use_query_cache = true) النتيجة المحسوبة من ذاكرة التخزين المؤقت وتُعيدها فورًا.
لا تسري use_query_cache وجميع الإعدادات الأخرى المرتبطة بذاكرة التخزين المؤقت للاستعلامات إلا على عبارات SELECT المستقلة. وعلى وجه الخصوص، لا تُخزَّن نتائج عبارات SELECT الموجَّهة إلى العروض المُنشأة باستخدام CREATE VIEW AS SELECT [...] SETTINGS use_query_cache = true في ذاكرة التخزين المؤقت ما لم تُنفَّذ عبارة SELECT مع SETTINGS use_query_cache = true.
يمكن ضبط طريقة استخدام ذاكرة التخزين المؤقت بمزيد من التفصيل باستخدام الإعدادين enable_writes_to_query_cache وenable_reads_from_query_cache (وكلاهما true افتراضيًا). يتحكم الإعداد الأول في ما إذا كانت نتائج الاستعلامات ستُخزَّن في ذاكرة التخزين المؤقت، بينما يحدد الإعداد الثاني ما إذا كان ينبغي على قاعدة البيانات محاولة استرجاع نتائج الاستعلامات من ذاكرة التخزين المؤقت. على سبيل المثال، سيستخدم الاستعلام التالي ذاكرة التخزين المؤقت بشكل سلبي فقط، أي سيحاول القراءة منها دون تخزين نتيجته فيها:
SELECT some_expensive_calculation(column_1, column_2)
FROM table
SETTINGS use_query_cache = true, enable_writes_to_query_cache = false;
لأقصى قدر من التحكم، يُوصى عمومًا بتوفير الإعدادات use_query_cache وenable_writes_to_query_cache و enable_reads_from_query_cache فقط مع استعلامات محددة. ويمكن أيضًا تمكين التخزين المؤقت على مستوى المستخدم أو الملف الشخصي (مثلًا عبر SET use_query_cache = true)، لكن ينبغي الانتباه إلى أن جميع استعلامات SELECT قد تُرجع عندئذٍ نتائج مخزنة مؤقتًا. يمكن مسح ذاكرة التخزين المؤقت للاستعلامات باستخدام التعليمة SYSTEM CLEAR QUERY CACHE. ويُعرض محتوى ذاكرة التخزين المؤقت للاستعلامات في جدول النظام system.query_cache. ويظهر عدد مرات الإصابة والإخفاق في ذاكرة التخزين المؤقت للاستعلامات منذ بدء تشغيل قاعدة البيانات كحدثين “QueryCacheHits” و”QueryCacheMisses” في جدول النظام system.events. ولا يُحدَّث هذان العدادان إلا من أجل استعلامات SELECT التي تعمل مع الإعداد use_query_cache = true، أما الاستعلامات الأخرى فلا تؤثر في “QueryCacheMisses”. ويُظهر الحقل query_cache_usage في جدول النظام system.query_log لكل استعلام مُنفَّذ ما إذا كانت نتيجة الاستعلام قد كُتبت إلى ذاكرة التخزين المؤقت للاستعلامات أو قُرئت منها. وتُظهر المقاييس QueryCacheEntries وQueryCacheBytes في جدول النظام system.metrics عدد الإدخالات / البايتات التي تحتوي عليها ذاكرة التخزين المؤقت للاستعلامات حاليًا. توجد ذاكرة التخزين المؤقت للاستعلامات مرة واحدة لكل عملية خادم ClickHouse. ومع ذلك، لا تكون النتائج المخزنة مؤقتًا مشتركة بين المستخدمين افتراضيًا. ويمكن تغيير ذلك (انظر أدناه)، لكن لا يُنصح به لأسباب أمنية. تُشار نتائج الاستعلامات في ذاكرة التخزين المؤقت للاستعلامات بواسطة شجرة البنية المجردة (AST) الخاصة باستعلاماتها. وهذا يعني أن التخزين المؤقت لا يتأثر بالأحرف الكبيرة أو الصغيرة، فعلى سبيل المثال يُعامَل SELECT 1 وselect 1 على أنهما الاستعلام نفسه. ولجعل المطابقة أكثر طبيعية، تُزال من AST جميع الإعدادات على مستوى الاستعلام المتعلقة بذاكرة التخزين المؤقت للاستعلامات وتنسيق المخرجات) . إذا أُجهض الاستعلام بسبب استثناء أو إلغاء من المستخدم، فلن يُكتَب أي إدخال في ذاكرة التخزين المؤقت للاستعلامات. يمكن تهيئة حجم ذاكرة التخزين المؤقت للاستعلامات بالبايتات، والحد الأقصى لعدد إدخالات التخزين المؤقت، والحد الأقصى لحجم كل إدخال من إدخالات التخزين المؤقت (بالبايتات وبالسجلات) باستخدام خيارات مختلفة من خيارات تهيئة الخادم.
<query_cache>
    <max_size_in_bytes>1073741824</max_size_in_bytes>
    <max_entries>1024</max_entries>
    <max_entry_size_in_bytes>1048576</max_entry_size_in_bytes>
    <max_entry_size_in_rows>30000000</max_entry_size_in_rows>
</query_cache>
يمكن أيضًا تقييد استخدام cache للمستخدمين الفرديين باستخدام ملفات تعريف الإعدادات وقيود الإعدادات. وبشكل أكثر تحديدًا، يمكنك تقييد الحد الأقصى لذاكرة (بالبايت) التي يمكن للمستخدم تخصيصها في query cache، والحد الأقصى لعدد نتائج الاستعلام المخزّنة. ولتنفيذ ذلك، حدِّد أولًا الإعدادين query_cache_max_size_in_bytes و query_cache_max_entries في ملف تعريف مستخدم داخل users.xml، ثم اجعل كلا الإعدادين للقراءة فقط:
<profiles>
    <default>
        <!-- The maximum cache size in bytes for user/profile 'default' -->
        <query_cache_max_size_in_bytes>10000</query_cache_max_size_in_bytes>
        <!-- The maximum number of SELECT query results stored in the cache for user/profile 'default' -->
        <query_cache_max_entries>100</query_cache_max_entries>
        <!-- Make both settings read-only so the user cannot change them -->
        <constraints>
            <query_cache_max_size_in_bytes>
                <readonly/>
            </query_cache_max_size_in_bytes>
            <query_cache_max_entries>
                <readonly/>
            <query_cache_max_entries>
        </constraints>
    </default>
</profiles>
لتحديد الحد الأدنى لمدة تشغيل الاستعلام بحيث يمكن تخزين نتيجته مؤقتًا، يمكنك استخدام الإعداد query_cache_min_query_duration. على سبيل المثال، نتيجة الاستعلام
SELECT some_expensive_calculation(column_1, column_2)
FROM table
SETTINGS use_query_cache = true, query_cache_min_query_duration = 5000;
يُخزَّن مؤقتًا فقط إذا استغرق الاستعلام أكثر من 5 ثوانٍ. ويمكن أيضًا تحديد عدد المرات التي يجب أن يُنفَّذ فيها الاستعلام قبل أن تُخزَّن نتيجته مؤقتًا — ولهذا استخدم الإعداد query_cache_min_query_runs. تصبح العناصر في ذاكرة التخزين المؤقت للاستعلام قديمة بعد فترة زمنية معينة (time-to-live). وتكون هذه الفترة افتراضيًا 60 ثانية، ولكن يمكن تحديد قيمة مختلفة على مستوى الجلسة أو ملف التعريف أو الاستعلام باستخدام الإعداد query_cache_ttl. وتُخرج ذاكرة التخزين المؤقت للاستعلام العناصر بشكل “كسول”، أي عندما يصبح أحد العناصر قديمًا، لا يُزال فورًا من ذاكرة التخزين المؤقت. وبدلًا من ذلك، عندما يُراد إدراج عنصر جديد في ذاكرة التخزين المؤقت للاستعلام، تتحقق قاعدة البيانات مما إذا كانت ذاكرة التخزين المؤقت تحتوي على مساحة خالية كافية للعنصر الجديد. وإذا لم تكن هذه هي الحالة، تحاول قاعدة البيانات إزالة جميع العناصر القديمة. وإذا ظلت ذاكرة التخزين المؤقت لا تحتوي على مساحة خالية كافية، فلن يُدرج العنصر الجديد. إذا جرى تنفيذ الاستعلام عبر HTTP، فإن ClickHouse يضبط ترويستَي Age وExpires مع عمر العنصر المخزَّن مؤقتًا (بالثواني) والطابع الزمني لانتهاء صلاحيته. تكون العناصر في ذاكرة التخزين المؤقت للاستعلام مضغوطة افتراضيًا. وهذا يقلل إجمالي استهلاك الذاكرة على حساب بطء عمليات الكتابة إلى / والقراءة من ذاكرة التخزين المؤقت للاستعلام. لتعطيل الضغط، استخدم الإعداد query_cache_compress_entries. أحيانًا يكون من المفيد الاحتفاظ بعدة نتائج مخزَّنة مؤقتًا للاستعلام نفسه. ويمكن تحقيق ذلك باستخدام الإعداد query_cache_tag الذي يعمل كتسمية (أو مساحة اسم) لعناصر ذاكرة التخزين المؤقت للاستعلام. وتتعامل ذاكرة التخزين المؤقت للاستعلام مع نتائج الاستعلام نفسه ذات الوسوم المختلفة على أنها مختلفة. مثال على إنشاء ثلاثة عناصر مختلفة في ذاكرة التخزين المؤقت للاستعلام للاستعلام نفسه:
SELECT 1 SETTINGS use_query_cache = true; -- query_cache_tag is implicitly '' (empty string)
SELECT 1 SETTINGS use_query_cache = true, query_cache_tag = 'tag 1';
SELECT 1 SETTINGS use_query_cache = true, query_cache_tag = 'tag 2';
لإزالة العناصر ذات الوسم tag فقط من ذاكرة التخزين المؤقت للاستعلامات، يمكنك استخدام العبارة SYSTEM CLEAR QUERY CACHE TAG 'tag'.

التخزين المؤقت للاستعلامات الفرعية

لا ينتقل use_query_cache في الاستعلام الخارجي، افتراضيًا، إلى الاستعلامات الفرعية. وهذا يعني أن على كل استعلام فرعي تفعيل التخزين المؤقت صراحةً:
SELECT *
FROM (SELECT number FROM system.numbers LIMIT 1000 SETTINGS use_query_cache = true)
WHERE number > 500;
في هذا المثال، لا تُخزَّن مؤقتًا إلا نتيجة الاستعلام الفرعي الداخلي. أما الاستعلام الخارجي فلا يُخزَّن مؤقتًا. لتمكين التخزين المؤقت لجميع الاستعلامات الفرعية دفعة واحدة، استخدم الإعداد query_cache_for_subqueries:
SELECT *
FROM (SELECT number FROM system.numbers LIMIT 1000)
WHERE number > 500
SETTINGS use_query_cache = true, query_cache_for_subqueries = true;
لتعطيل التخزين المؤقت صراحةً لاستعلام فرعي معيّن مع تفعيل النشر المجمّع، اضبط use_query_cache = false لهذا الاستعلام الفرعي:
SELECT *
FROM (SELECT number FROM system.numbers LIMIT 1000 SETTINGS use_query_cache = false)
WHERE number > 500
SETTINGS use_query_cache = true, query_cache_for_subqueries = true;
تظهر إدخالات ذاكرة التخزين المؤقت للاستعلامات الفرعية في system.query_cache مع is_subquery = 1. وينطبق الإعداد query_cache_ttl أيضًا على إدخالات ذاكرة التخزين المؤقت الخاصة بالاستعلامات الفرعية، ويمكن ضبطه لكل استعلام فرعي على حدة. يقرأ ClickHouse بيانات الجدول في blocks من max_block_size rows. وبسبب التصفية وaggregation وما إلى ذلك، تكون blocks النتائج عادةً أصغر بكثير من ‘max_block_size’، ولكن توجد أيضًا حالات تكون فيها أكبر بكثير. يتحكم الإعداد query_cache_squash_partial_results (ممكّن افتراضيًا) في ما إذا كانت blocks النتائج تُدمج (إذا كانت صغيرة جدًا) أو تُقسَّم (إذا كانت كبيرة) إلى blocks بحجم ‘max_block_size’ قبل إدراجها في ذاكرة التخزين المؤقت لنتيجة الاستعلام. ويؤدي ذلك إلى تقليل أداء عمليات الكتابة إلى ذاكرة التخزين المؤقت للاستعلامات، لكنه يحسّن معدل Compression لإدخالات ذاكرة التخزين المؤقت ويوفر granularity أكثر طبيعية للـ block عند تقديم query results لاحقًا من ذاكرة التخزين المؤقت للاستعلامات. ونتيجة لذلك، يخزّن ذاكرة التخزين المؤقت للاستعلامات لكل query عدة blocks نتائج (جزئية). ورغم أن هذا السلوك يُعد خيارًا افتراضيًا جيدًا، يمكن تعطيله باستخدام الإعداد query_cache_squash_partial_results. كذلك، لا تُخزَّن نتائج queries التي تحتوي على دوال nondeterministic مؤقتًا افتراضيًا. وتشمل هذه الدوال: لفرض تخزين نتائج queries التي تحتوي على دوال nondeterministic مؤقتًا رغم ذلك، استخدم الإعداد query_cache_nondeterministic_function_handling. لا تُخزَّن نتائج queries التي تتضمن system tables (مثل system.processes` أو information_schema.tables) مؤقتًا افتراضيًا. ولفرض تخزين نتائج queries التي تتضمن system tables مؤقتًا رغم ذلك، استخدم الإعداد query_cache_system_table_handling. أخيرًا، لا تتم مشاركة إدخالات ذاكرة التخزين المؤقت للاستعلامات بين المستخدمين لأسباب أمنية. على سبيل المثال، يجب ألا يتمكن المستخدم A من تجاوز سياسة الصفوف على جدول من خلال تشغيل الاستعلام نفسه الذي يشغّله المستخدم B، والذي لا تنطبق عليه مثل هذه السياسة. ومع ذلك، إذا لزم الأمر، يمكن تمييز إدخالات ذاكرة التخزين المؤقت بحيث تصبح متاحة لمستخدمين آخرين (أي مشتركة) عبر توفير الإعداد query_cache_share_between_users.
آخر تعديل في ٢٩ يونيو ٢٠٢٦