نظرة عامة
يدعم ClickHouse بروتوكول Apache Arrow Flight — وهو إطار RPC عالي الأداء لنقل البيانات العمودية بكفاءة باستخدام تنسيق Arrow IPC عبر gRPC. يتضمن هذا التنفيذ دعم Arrow Flight SQL، مما يتيح لأدوات BI والتطبيقات التي تدعم بروتوكول Flight SQL الاستعلام من ClickHouse مباشرةً. القدرات الأساسية:- تنفيذ استعلامات SQL واسترجاع النتائج بتنسيق Apache Arrow.
- إدراج البيانات في الجداول باستخدام تنسيق Arrow.
- الاستعلام عن البيانات الوصفية (catalogs وschemas وtables وprimary keys) عبر أوامر Flight SQL.
- إنشاء العبارات المُحضّرة على جهة الخادم وربطها وتنفيذها وإغلاقها عبر Flight SQL.
- إدارة الجلسات والإعدادات عبر إجراءات Flight SQL.
- تشفير TLS والمصادقة باستخدام اسم المستخدم/كلمة المرور.
- الاسترجاع التدريجي للنتائج عبر
PollFlightInfo. - إلغاء الاستعلام عبر
CancelFlightInfo.
تمكين خادم Arrow Flight
لتمكين خادم Arrow Flight، أضِف الإعدادarrowflight_port إلى إعدادات خادم ClickHouse:
تهيئة TLS
لتمكين TLS لواجهة Arrow Flight، اضبط الإعدادات التالية:grpc+tls:// بدلًا من grpc://.
المصادقة
تدعم واجهة Arrow Flight طريقتين للمصادقة:المصادقة الأساسية
تُجري البرامج العميلة المصادقة باستخدام اسم مستخدم وكلمة مرور عبر ترويسة HTTP القياسيةAuthorization: Basic. وعند نجاح المصادقة، يُرجِع الخادم Bearer token في ترويسة الاستجابة.
مصادقة رمز Bearer
يمكن للطلبات اللاحقة استخدام رمز Bearer المُعاد من المصادقة الأساسية عبر ترويسةAuthorization: Bearer <token>. ويُجدَّد الرمز تلقائيًا عند كل استخدام، وتنتهي صلاحيته وفقًا لإعداد الخادم default_session_timeout (الافتراضي: 60 ثانية).
مثال بلغة بايثون
إدارة الجلسات
تدعم واجهة Arrow Flight جلسات ClickHouse من خلال رؤوس البيانات الوصفية المخصصة في gRPC:نظرًا إلى أن Arrow Flight يستخدم gRPC عبر HTTP/2، فإن أسماء رؤوس البيانات الوصفية حسّاسة لحالة الأحرف، ويجب كتابتها بأحرف صغيرة تمامًا كما هو موضح (على سبيل المثال،
x-clickhouse-session-id وليس X-ClickHouse-Session-Id). وهذا مطلوب بموجب RFC 9113, Section 8.2، التي تنص على أن أسماء حقول HTTP/2 يجب أن تتكون من أحرف صغيرة فقط. ويختلف هذا عن HTTP/1.1، حيث تكون أسماء الرؤوس غير حسّاسة لحالة الأحرف.SetSessionOptions (راجع DoAction).
مرجع تهيئة الخادم
طرق RPC المدعومة
GetFlightInfo
ينفّذ استعلامًا ويُرجعFlightInfo يتضمّن مخطط النتيجة، ونقاط النهاية مع التذاكر اللازمة لاسترجاع البيانات، وعدد الصفوف، وعدد البايتات.
يقبل FlightDescriptor، ويمكن أن يكون أحد ما يلي:
- واصف PATH: مسارًا أحادي المكوّن يُفسَّر على أنه اسم جدول. ويُنشئ
SELECT * FROM <table>. - واصف CMD: إمّا سلسلة استعلام SQL خام، أو أمر Flight SQL protobuf مُسلسل (راجع Flight SQL Commands).
PollFlightInfo
يتيح استرداد النتائج بشكل تدريجي للاستعلامات طويلة التشغيل. فبدلًا من انتظار اكتمال الاستعلام بالكامل (كما يفعلGetFlightInfo)، يعيد PollFlightInfo النتائج على شكل كتل، كتلةً تلو الأخرى.
عند الاستدعاء الأول، يبدأ تنفيذ الاستعلام. وتتضمن الاستجابة ما يلي:
- كائن
FlightInfoيحتوي على نقطة نهاية لأي كتل بيانات متاحة حتى تلك اللحظة. - كائن
FlightDescriptorلعملية الاستطلاع التالية (إذا كان من المتوقع توفر المزيد من النتائج).
ينتظر التنفيذ الحالي حتى تتوفر كتلة بيانات، بدلًا من أن يعيد الاستجابة فورًا من دون بيانات.
GetSchema
يُرجع مخطط Arrow لنتيجة الاستعلام دون تنفيذ الاستعلام كاملًا. ويقبل أنواع الواصف نفسها كما فيGetFlightInfo.
DoGet
يسترجع البيانات لتذكرة معيّنة. ويقبل أحد الخيارين التاليين:- تذكرة مُعادة من
GetFlightInfoأوPollFlightInfo. - سلسلة استعلام Raw SQL كقيمة للتذكرة.
DoPut
يرسل البيانات إلى ClickHouse. يقبلFlightDescriptor وتدفّقًا من دفعات سجلات Arrow.
إدراج حسب اسم الجدول (واصف PATH):
CommandStatementUpdate:
يستخدم عملاء Flight SQL الأمر CommandStatementUpdate لتنفيذ عبارات DDL/DML (CREATE، INSERT، ALTER، إلخ). وتتضمن الاستجابة عدد الصفوف المتأثرة.
الإدخال المجمّع عبر Flight SQL CommandStatementIngest:
لا يُدعَم إلا الإلحاق بالجداول الموجودة (TABLE_NOT_EXIST_OPTION_FAIL + TABLE_EXISTS_OPTION_APPEND). ولا تُدعَم الكتالوجات والجداول المؤقتة لهذا الأمر.
لا تتوفر إمكانية استخدام transaction_id مع CommandStatementUpdate أو CommandStatementIngest. وإذا تم توفيره، يعرض ClickHouse الخطأ NotImplemented.
لا يُقبل لنقل البيانات سوى التنسيق
Arrow. ويؤدي تحديد تنسيقات أخرى في SQL (مثل FORMAT JSON) إلى حدوث خطأ.DoAction
ينفّذ إجراءات مُسمّاة. الإجراءات التالية متاحة:CancelFlightInfo
يلغي استعلامًا قيد التشغيل مرتبطًا بـFlightInfo. ويُستخرَج معرّف الاستعلام من حقل app_metadata الخاص بـ FlightInfo. كما يُلغي أيضًا أي واصفات استطلاع مرتبطة بالاستعلام.
SetSessionOptions
يضبط إعدادات خادم ClickHouse للجلسة الحالية. ويتطلب ذلك تعيين معرّف جلسة عبر الترويسةx-clickhouse-session-id.
أنواع القيم المدعومة: string وboolean وinteger وdouble وقوائم من string.
إذا كان اسم الإعداد غير معروف، فسيُعاد الخطأ INVALID_NAME. وإذا تعذّر تحليل القيمة، فسيُعاد الخطأ INVALID_VALUE.
GetSessionOptions
يعيد جميع إعدادات ClickHouse الحالية وقيمها الخاصة بالجلسة. ويعيد خريطة تربط أسماء الإعدادات بقيم نصية (ويستعلم داخليًا منsystem.settings).
CreatePreparedStatement
ينشئ عبارة مُحضَّرة على جانب الخادم ويُرجع معرّفًا لها. يحتوي الطلب على نص استعلام SQL مع عناصر نائبة?.
transaction_id غير مدعوم لهذا الإجراء. وإذا تم توفيره، يعيد ClickHouse الخطأ NotImplemented.
بالنسبة إلى عبارات الاستعلام، قد تتضمن الاستجابة ما يلي:
dataset_schema: مخطط مجموعة النتائج.parameter_schema: مخطط معلمات العبارة.
NULL صالحًا لهذا الاستعلام)، فسيواصل ClickHouse إنشاء العبارة المُحضَّرة ويُرجع المعرّف من دون dataset_schema.
يمثّل dataset_schema أفضل تخمين ممكن، وهو ما تقصده مواصفة Flight SQL — إذ تنص على أن مخطط النتيجة قد يعتمد على المعلمات، وأن على الخادم تقديم أفضل تخمين لديه، وأنه يجب على العملاء عدم افتراض دقة المخطط. لا تعتمد عليه؛ نفّذ العبارة للحصول على المخطط الذي يصف البيانات. وفي ClickHouse قد يختلف عمّا يُقدَّم فعليًا لسببين:
- يستبدل الاستنتاج كل
?بـNULL، لذا فإن العنصر النائب الذي يحدّد أحد أعمدة النتيجة يُشتق نوعه من ذلكNULLوليس من القيمة التي تربطها لاحقًا. فالاستعلامSELECT ? AS xيستنتج عمودًا من النوعNothing، لكن ربط القيمة5يقدّمUInt8. أما العنصر النائب المستخدم داخل مسند فقط، كما فيSELECT id, name FROM t WHERE id = ?، فلا تنطبق عليه هذه المشكلة، لأن أنواع النتيجة تأتي من الجدول. - العمود الذي لا يوجد له مكافئ في Arrow يأخذ نوع Arrow الخاص به من
output_format_arrow_unsupported_types، والذي يُحلّ في كل استدعاء انطلاقًا من الجلسة التي تجريه. وبما أن المعرّف يعود إلى المستخدم لا إلى جلسة واحدة، فقد يحلّه استدعاء لاحق بصورة مختلفة فيقدّمbinaryحيث أُعلن عنutf8، أو العكس. وضبط الوضع داخل الاستعلام المُحضَّر نفسه يثبّته في الحالتين.
arrowflight.prepared_statements_lifetime_seconds في سلوك انتهاء الصلاحية:
> 0: استخدم القيمة المُعدّة على أنها مدة بقاء العبارة. ويُجدَّد انتهاء الصلاحية مع كل طلب لكلٍّ من العبارات المرتبطة بجلسة وغير المرتبطة بجلسة.0: لا تنتهي صلاحية العبارات المُحضَّرة تلقائيًا.-1(default): إذا أُنشئت العبارة داخل جلسة، فإن مدة بقائها تتبع مهلة تلك الجلسة ويُجدَّد مع كل طلب داخلها. وإذا أُنشئت العبارة من دون جلسة، فلا تنتهي صلاحيتها تلقائيًا.
arrowflight.max_prepared_statements_per_user.
ClosePreparedStatement
يغلق عبارة مُحضَّرة ويحرّر موارد جهة الخادم المرتبطة بها عندما يحتوي الطلب على معرّف عبارة غير فارغ. يدعم ClickHouse أيضًا الإغلاق المجمّع باستخدامClosePreparedStatement عندما يكون المعرّف فارغًا:
- إذا كان
x-clickhouse-session-idموجودًا، فسيُغلق جميع العبارات المُحضَّرة للمستخدم المُصادَق عليه ضمن تلك الجلسة. - إذا لم يكن هناك معرّف جلسة، فسيُغلق فقط العبارات المُحضَّرة غير المرتبطة بجلسة للمستخدم المُصادَق عليه.
x-clickhouse-session-id)، فستُغلق أيضًا تلقائيًا عند إغلاق تلك الجلسة.
Flight SQL Commands
عندما يحتوي الواصفCMD على رسالة Protobuf لـ Flight SQL مُسلسلة، يدعم ClickHouse الأوامر التالية:
مدعوم من خلال GetFlightInfo / GetSchema
المدعوم عبر DoPut
غير مدعوم في ClickHouse
تشير هذه الأوامر إلى ميزات لا يوفّرها ClickHouse، لذلك فهي غير مدعومة في واجهة Arrow Flight SQL.مثال متكامل
Query
Response
تنسيق البيانات
تُنقل جميع البيانات بتنسيق Apache Arrow IPC. والتنسيقArrow هو الوحيد المدعوم — أما تحديد تنسيقات ClickHouse الأخرى (مثل FORMAT JSON أو FORMAT CSV) فيؤدي إلى خطأ.
تُربط أنواع بيانات ClickHouse بأنواع Arrow أثناء التسلسل. ويستخدم Arrow Flight دائمًا الربط القياسي (canonical) الخاص بـ Arrow، وخلافًا لتنسيقي الإخراج Arrow وArrowStream، فإنه لا يتبع إعدادات output_format_arrow_* التي تغيّر طريقة تمثيل النوع — فإعدادات output_format_arrow_string_as_string وoutput_format_arrow_low_cardinality_as_dictionary وoutput_format_arrow_date_as_uint16 وoutput_format_arrow_fixed_string_as_fixed_byte_array وإعدادات فهرس الـ dictionary لا أثر لها هنا. لذلك قد يُنتج الاستعلام نفسه مخططًا عبر Arrow Flight يختلف عمّا يُنتجه عبر FORMAT Arrow، وهذا مقصود بالتصميم، لسببين:
- يُثبّت Flight SQL مخطط استجابات البيانات الوصفية الخاصة به. فعلى سبيل المثال، يجب أن يُرجع
CommandGetTablesالقيمcatalog_name: utf8, db_schema_name: utf8, table_name: utf8 not null, table_type: utf8 not null, table_schema: bytes not null. والسماح لأحد إعدادات الـ session بتحويل تلك الأعمدة منutf8إلىbinaryسيجعل ClickHouse غير متوافق مع كل مشغّل (driver) لـ Flight SQL، كما سيغيّر المخطط الخاص بكل جدول والذي يُعلن عنه ClickHouse داخلtable_schema. - يجلب عميل Flight المخطط والبيانات في نداءات منفصلة (
GetFlightInfoأوGetSchema، ثمDoGet). وأي إعداد قادر على تغيير المخطط يفتح الباب لعدم تطابق المخطط المُعلن مع الدفق المُسلّم إذا تغيّرت الـ session في الأثناء.
JSON أو Dynamic أو QBit أو AggregateFunction. فلا يوجد ربط قياسي يمكن الالتزام به، ومن ثمّ يجب على ClickHouse اختيار تمثيل ما، ويتيح لك output_format_arrow_unsupported_types تحديد أيّها:
وعمود
AggregateFunction هو النوع الوحيد الذي يبقى عمود Binary في Arrow حتى في وضع text: إذ إن شكله النصي هو aggregate state الخام، وهو ليس UTF-8 صالحًا، بينما يجب أن يحتوي عمود Utf8 في Arrow على UTF-8 صالح. استخدم finalizeAggregation إذا أردت قيمة قابلة للقراءة.
وللسبب نفسه، يستبدل ClickHouse كل تسلسل UTF-8 غير صالح في قيمة text بالمحرف U+FFFD (�) قبل كتابته في عمود Utf8. فالنوع Dynamic الذي يحمل String يُسلسل تلك البايتات حرفيًا، وقد تكون عشوائية، ولولا هذه المعالجة لخالف العمود مواصفة Arrow ولأمكن أن يرفضه عميل صارم. ولا تتغيّر سوى القيم التي هي أصلًا ليست نصًا صالحًا. استخدم وضع binary حين يتعيّن الحفاظ على البايتات كما هي تمامًا.
ولا ينطبق الإعداد output_format_arrow_string_as_string على هذه الأعمدة إطلاقًا، ولا حتى في FORMAT Arrow — فهو يحكم أعمدة String وFixedString الحقيقية فقط. لذا فإن نوع Arrow لعمود clickhouse.opaque يبيّن دائمًا الترميز الذي يحمله: Utf8 للشكل النصي، وBinary للشكل الثنائي.
ولهذا السبب يفقد aggregate state المحفوظ داخل Dynamic جزءًا من البيانات في وضع text، بخلاف عمود AggregateFunction. فنوع العمود مُشتق من Dynamic، وهو لا يقول شيئًا عمّا تحتويه صفوفه، ويُثبَّت المخطط قبل الاطلاع على أي قيمة، ولذلك لا يمكن منح الـ state عمود Binary خاصًا به. استخدم وضع binary للحفاظ عليه. أما Variant فيسرد بدائله، ومن ثمّ يحصل AggregateFunction الموجود بينها على عمود فرعي Binary خاص به ولا يتأثر.
وفيما عدا ذلك، لا يمكن تمييز مثل هذا العمود عن عمود Utf8/Binary حقيقي، ولذلك يُعلَن كنوع extension في Arrow: تحمل بيانات الحقل الوصفية ARROW:extension:name = clickhouse.opaque واسم نوع ClickHouse الأصلي في ARROW:extension:metadata. والعميل الذي لا يتعرّف على اسم الـ extension يرى نوع التخزين البسيط، كما تنص مواصفة Arrow. وتُوسم nested columns على حقلها الخاص، فيحمل العنصر الفرعي لـ Array(JSON) الوسم، وكذلك مفتاح Map(JSON, ...)، لا الحاوية نفسها.
لا يزال الإعداد المنطقي الأقدم output_format_arrow_unsupported_types_as_binary يعمل، وهو مكافئ لـ throw عند القيمة 0 ولـ binary عند القيمة 1. ولا يُؤخَذ به إلا ما دام output_format_arrow_unsupported_types على قيمته الافتراضية.
التوافق
واجهة Arrow Flight متوافقة مع أي عميل أو أداة تدعم بروتوكول Arrow Flight أو Arrow Flight SQL، بما في ذلك:- بايثون (
pyarrow) - Java (
org.apache.arrow.flight) - C++ (
arrow::flight) - Go (
apache/arrow/go) - برامج تشغيل ADBC (Arrow Database Connectivity)
- DBeaver، وأدوات أخرى تدعم Flight SQL