Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

العمل مع نوع Map في ClickHouse

All quickstarts
ObservabilityOSS

المتطلبات الأساسية

ما الذي ستبنيه

في OpenTelemetry، يحمل كل trace span مجموعة من سمات الموارد — وهي بيانات وصفية بتنسيق مفتاح-قيمة تصف الكيان الذي أنتج بيانات telemetry (مثل اسم الخدمة، والمضيف، والمنطقة السحابية، وpod في Kubernetes، وغير ذلك). وتختلف مجموعة المفاتيح بين الخدمات والبيئات، مما يجعل نوع Map في ClickHouse مناسبًا لهذا الاستخدام بطبيعته: فالمفاتيح ديناميكية وخاصّة بالتطبيق، لكن أي صف معيّن لا يحتوي عادةً إلا على عدد قليل منها.

في هذا الدليل السريع، ستستخدم clickhouse-local لتحميل بيانات trace حقيقية من OTel من ملف CSV إلى جدول يحتوي على أعمدة Map(LowCardinality(String), String)، وستتعلّم كيفية الاستعلام عن بيانات Map وتصفيتها وتجميعها وتحسينها.

نزّل بيانات العيّنة

تتضمن مجموعة البيانات 6,120 امتداد تتبّع OTel مُصدَّرًا من تطبيق تجريبي للخدمات المصّغرة. ويتضمن كل صف العمودين ResourceAttributes وSpanAttributes، ويحتويان على أزواج مفتاح-قيمة ديناميكية على شكل خرائط JSON. احفظ الملف في دليل يسهل الرجوع إليه، على سبيل المثال ~/data/data-otel-traces.csv.

نزّل data-otel-traces.csv (2.9 MB)

إليك شكل صف واحد:

Timestamp:          2025-12-26 00:00:45.759467000
TraceId:            0da128e6e3c01bc38b6b43a33e5fa522
SpanId:             3774f759424e4006
ParentSpanId:       2fdd1e5b66605098
SpanName:           orders receive
SpanKind:           SPAN_KIND_CONSUMER
ServiceName:        accountingservice
Duration:           5361
StatusCode:         STATUS_CODE_UNSET
ResourceAttributes: {"host.name":"f19476836e47","os.type":"linux","process.pid":"1","process.command_args":"[\"./accountingservice\"]","process.executable.path":"...
SpanAttributes:     {"network.transport":"tcp","messaging.destination.name":"orders","messaging.kafka.message.offset":"232260","messaging.message.body.size":"216"...

أنشئ الجدول وحمّل البيانات

شغّل clickhouse-local وأنشئ الجدول التالي بمخطط يتوافق مع ملف CSV. عمود المفتاح هو ResourceAttributes Map(LowCardinality(String), String) — ويُستخدم LowCardinality مع نوع المفتاح لأن مفاتيح سمات OTel مأخوذة من مجموعة صغيرة نسبيًا ومتكررة.

CREATE TABLE otel_traces
(
    Timestamp          DateTime64(9),
    TraceId            String,
    SpanId             String,
    ParentSpanId       String,
    SpanName           LowCardinality(String),
    SpanKind           LowCardinality(String),
    ServiceName        LowCardinality(String),
    Duration           UInt64,
    StatusCode         LowCardinality(String),
    ResourceAttributes Map(LowCardinality(String), String),
    SpanAttributes     Map(LowCardinality(String), String)
)
ENGINE = MergeTree()
ORDER BY (ServiceName, SpanName, toUnixTimestamp(Timestamp));

الآن حمّل ملف CSV باستخدام محرّك الجدول file. عدّل المسار ليتوافق مع المكان الذي حفظت فيه الملف:

INSERT INTO otel_traces
SELECT * FROM file('~/data/data-otel-traces.csv', CSVWithNames);

تأكد من أنه تم تحميل البيانات:

SELECT count() FROM otel_traces;

من المفترض أن ترى 6,120 صفًا.

الاستعلام عن البيانات

الوصول إلى مفتاح محدد — استخدم صيغة الأقواس المربعة لاستخراج قيمة من map. إذا لم يكن المفتاح موجودًا في صف معيّن، فستحصل على القيمة الافتراضية لنوع القيمة (سلسلة فارغة من النوع String):

SELECT
    ServiceName,
    SpanName,
    ResourceAttributes['host.name']             AS host,
    ResourceAttributes['k8s.pod.name']          AS pod,
    ResourceAttributes['deployment.environment'] AS env
FROM otel_traces
LIMIT 10;

التصفية حسب قيمة في map — اعثر على جميع spans المرتبطة باسم خدمة محدد:

SELECT
    Timestamp,
    SpanName,
    Duration / 1e6 AS duration_ms
FROM otel_traces
WHERE ResourceAttributes['service.name'] = 'cartservice'
ORDER BY Timestamp
LIMIT 10;

تحقّق مما إذا كان المفتاح موجودًا — لا يحتوي كل span على بيانات Kubernetes الوصفية. استخدم mapContains لمعرفة أيّها يحتوي عليها:

SELECT
    ServiceName,
    SpanName,
    mapContains(ResourceAttributes, 'k8s.node.name') AS has_node_info
FROM otel_traces
LIMIT 10;

افحص جميع المفاتيح الموجودة في كامل مجموعة البيانات — وهذا مفيد لفهم ما الذي تنتجه أدوات الرصد المضمّنة:

SELECT DISTINCT arrayJoin(mapKeys(ResourceAttributes)) AS key
FROM otel_traces
ORDER BY key;

فكِّك Map إلى صفوف باستخدام ARRAY JOIN — حوِّل كل زوج مفتاح-قيمة إلى صف مستقل، مما يفيد في إنشاء فهرس للسمات أو في تعبئة لوحات المعلومات:

SELECT
    ServiceName,
    key,
    value
FROM otel_traces
ARRAY JOIN
    mapKeys(ResourceAttributes)  AS key,
    mapValues(ResourceAttributes) AS value
WHERE ServiceName = 'cartservice'
LIMIT 20;

رشّح قيم Map باستخدام mapFilter — استخرج فقط السمات المرتبطة بـ Kubernetes من كل Span:

SELECT
    ServiceName,
    mapFilter((k, v) -> k LIKE 'k8s.%', ResourceAttributes) AS k8s_attrs
FROM otel_traces
WHERE mapContains(ResourceAttributes, 'k8s.pod.name')
LIMIT 10;

اعثر على مقاطع التتبّع الخاصة بالأخطاء وسياق المورد المرتبط بها — اجمع بين مرشحات الأعمدة العادية والوصول إلى map:

SELECT
    Timestamp,
    ServiceName,
    SpanName,
    ResourceAttributes['host.name']    AS host,
    ResourceAttributes['k8s.pod.name'] AS pod,
    SpanAttributes['error.type']       AS error_type,
    SpanAttributes['error.message']    AS error_message
FROM otel_traces
WHERE StatusCode = 'STATUS_CODE_ERROR';

التجميع عبر خرائط `Map` باستخدام المبدِّل `-Map`

يتيح المبدِّل التجميعي -Map في ClickHouse تطبيق أي دالة aggregate على عمود Map بحيث تعمل على كل مفتاح بشكل مستقل. وتكون النتيجة أيضًا Map — مع مُدخل واحد لكل مفتاح، يتضمن القيمة المُجمَّعة. ويكون هذا مفيدًا بشكل خاص مع metrics في OTel، حيث تُخزَّن counters أو gauges كقيم في map.

للتوضيح، أنشئ table صغيرًا للـ metrics حيث يسجّل كل row أعداد رموز حالة HTTP بصيغة Map(String, UInt64):

CREATE TABLE otel_http_status_counts
(
    Timestamp    DateTime,
    ServiceName  LowCardinality(String),
    StatusCounts Map(String, UInt64)
)
ENGINE = MergeTree()
ORDER BY (ServiceName, Timestamp);

INSERT INTO otel_http_status_counts VALUES
    ('2025-12-26 10:00:00', 'cart-service',      {'2xx': 150, '4xx': 12, '5xx': 3}),
    ('2025-12-26 10:01:00', 'cart-service',      {'2xx': 200, '4xx': 8,  '5xx': 1}),
    ('2025-12-26 10:00:00', 'inventory-service', {'2xx': 90,  '4xx': 5}),
    ('2025-12-26 10:01:00', 'inventory-service', {'2xx': 110, '4xx': 3,  '5xx': 2}),
    ('2025-12-26 10:00:00', 'payment-service',   {'2xx': 50,  '5xx': 10}),
    ('2025-12-26 10:01:00', 'payment-service',   {'2xx': 45,  '4xx': 2,  '5xx': 15});

استخدم الآن sumMap لحساب إجمالي مرات الظهور لكل رمز حالة في كل خدمة:

SELECT
    ServiceName,
    sumMap(StatusCounts) AS total_by_status
FROM otel_http_status_counts
GROUP BY ServiceName;

تعمل اللاحقة -Map مع أي دالة تجميع، لذا يمكنك استخدام minMap أو maxMap أو avgMap بالقدر نفسه من السهولة:

SELECT
    ServiceName,
    avgMap(StatusCounts) AS avg_by_status,
    maxMap(StatusCounts) AS peak_by_status
FROM otel_http_status_counts
GROUP BY ServiceName;

يمكنك أيضًا دمجه مع مُركِّبات أخرى. على سبيل المثال، يتيح لك sumMapIf إجراء تجميع شرطي — إذ لا تُجمَع هنا إلا نوافذ الدقائق التي كانت الخدمة تحتوي فيها بالفعل على أخطاء:

SELECT
    ServiceName,
    sumMapIf(StatusCounts, StatusCounts['5xx'] > 0) AS totals_in_error_windows
FROM otel_http_status_counts
GROUP BY ServiceName;

لماذا يهم هذا بالنسبة إلى OTel: عندما يكتب OTel Collector تفاصيل رموز الحالة لكل دقيقة في ClickHouse، تتيح لك sumMap تجميعها في إجماليات لكل ساعة أو لكل يوم ضمن استعلام واحد — من دون ARRAY JOIN، ومن دون إلغاء التمحور، ومن دون الحاجة إلى معرفة المجموعة الكاملة للمفاتيح مسبقًا. ويُدرَج تلقائيًا في النتيجة أي مفتاح يظهر في أي صف.

حسّن الأداء للمفاتيح التي يُكثر الاستعلام عنها

إذا وجدت نفسك تُجري التصفية باستمرار على المفتاح نفسه في map — ويُعد host.name مثالًا شائعًا — فيمكنك استخراجه إلى عمود مُجسَّد. وهذا يجنّبك الفحص الخطي داخل map في كل استعلام:

ALTER TABLE otel_traces
    ADD COLUMN HostName String
    MATERIALIZED ResourceAttributes['host.name'];

بالنسبة إلى البيانات الموجودة، أجرِ تعبئةً لاحقةً للعمود:

ALTER TABLE otel_traces MATERIALIZE COLUMN HostName;

الآن يقرأ WHERE HostName = 'prod-cart-01' عمودًا مخصصًا واحدًا بدلًا من قراءة بنية Map بالكامل. وهذا هو النمط الموصى به في مخطط OTel في ClickHouse لأي سمة تستعلم عنها بشكل متكرر.

النقاط الرئيسية

  • Map(LowCardinality(String), String) هو النوع الأكثر شيوعًا لسمات OTel — مرن بما يكفي للتعامل مع مجموعات مفاتيح متغيرة، كما يحافظ LowCardinality على كفاءة تخزين المفاتيح.
  • صياغة الأقواس (map['key']) هي الطريقة الأكثر شيوعًا للوصول إلى القيم، لكن تذكّر أنها تفحص خطيًا — وهذا مناسب للخرائط التي تحتوي على عشرات المفاتيح، لكنه ليس مثاليًا عند وجود المئات.
  • الأعمدة المُجسَّدة هي الحل العملي هنا: عندما يصبح مفتاح في الخريطة هدفًا شائعًا للتصفية، رقِّه إلى عمود فعلي للوصول المفهرس والعمودي.
  • mapContains, mapKeys, mapValues, mapFilter و ARRAY JOIN توفّر لك مجموعة أدوات قوية لاستكشاف بيانات الخرائط وتحويلها دون مغادرة SQL.
  • مُركِّب التجميع -Map (sumMap, avgMap, maxMap, etc.) يجمّع كل مفتاح على نحو مستقل عبر الصفوف — وهو مثالي لتجميع عدّادات مقاييس OTel دون الحاجة إلى معرفة مجموعة المفاتيح مسبقًا. كما يمكن تركيبه مع مُركِّبات أخرى أيضًا (مثل sumMapIf).

الخطوات التالية

اطّلع بعد ذلك على أدلة البدء السريع التالية:

أو تعمّق أكثر بالرجوع إلى الوثائق المرجعية:

ClickHouse Academy — Master ClickHouse with expert-designed training for every skill level
Check out the ClickHouse academy for on-demand and live training
Navigation