كل أسبوع يسألك المدير السؤال نفسه: كم بعنا هذا الشهر؟ وأي مدينة الأعلى؟ وما المنتج الذي تراجع؟ فتفتح قاعدة البيانات وتكتب استعلام SQL وتنسخ النتيجة إلى ملف Excel وترسله، ثم يأتي السؤال التالي بعد ساعة بفلتر مختلف. والحل الذي تستخدمه الفرق لهذه المشكلة هو أداة ذكاء الأعمال Business Intelligence، واختصاراً BI، وهي أداة تتصل بقاعدة بياناتك وتسمح لغير المبرمج أن يسأل البيانات ويرى النتيجة رسماً بيانياً، وتجمع الرسوم في لوحة Dashboard تتحدث أرقامها وحدها. وهذا المقال هو أول مقال في مرحلة البيانات وذكاء الأعمال، ونستخدم فيه Metabase لأنها أسهل أداة مفتوحة المصدر يبدأ بها فريق صغير، وسوف نناقش ما يلي:
- ما هي أداة BI، ومعنى مصدر البيانات والسؤال Question والبطاقة Card واللوحة Dashboard والفلتر.
- من أين تقرأ الأداة بياناتها، ولماذا لا تربطها بقاعدة الإنتاج بمستخدم المالك.
- مكونات Metabase: قاعدة بياناتها الخاصة وقاعدة بياناتك، والرخصة والإصدارات التجارية.
- تجربة عملية: متجر تجريبي على PostgreSQL ببيانات عربية، ثم تثبيت Metabase بإصدار مثبت وقاعدة تطبيق PostgreSQL، وبناء أسئلة ولوحة بفلاتر.
- العربية في Metabase بصراحة: ما يعمل وما لا يعمل.
- النسخ الاحتياطي والتحديث والتشغيل في الإنتاج.
ما هي أداة ذكاء الأعمال BI؟
أداة BI تقف بين قاعدة البيانات والشخص الذي يريد أرقاماً، وفيها أربعة مفاهيم تتكرر في كل أداة، سواء كانت Metabase أو Superset أو Power BI:
- مصدر البيانات Data Source: قاعدة بيانات تتصل بها الأداة، مثل PostgreSQL أو MySQL، أو ملف CSV ترفعه.
- السؤال Question أو الاستعلام Query: طلب محدد مثل «مجموع المبيعات لكل مدينة»، تبنيه بالنقر في واجهة رسومية أو تكتبه بلغة SQL.
- البطاقة Card أو الرسم Chart: نتيجة السؤال مع طريقة عرضها، رقم كبير أو أعمدة أو خط زمني أو جدول.
- اللوحة Dashboard: مجموعة بطاقات في صفحة واحدة، ومعها فلاتر Filters مثل المدينة والفترة تغير كل البطاقات معاً، وكل من يفتحها يرى أرقاماً محدثة من قاعدة البيانات مباشرة.
والصورة التالية تبين الطريق من البيانات إلى اللوحة:

وقد يتساءل البعض: لماذا لا نكتفي بجدول Excel يحدث يدوياً؟ والإجابة أن الملف صورة للبيانات في لحظة تصديره، فكل سؤال جديد يحتاج تصديراً جديداً، وكل نسخة في بريد شخص مختلف قد تكون قديمة، أما اللوحة فتسأل قاعدة البيانات في كل مرة تفتح فيها، والصلاحيات فيها تحدد من يرى ماذا.
من أين تقرأ الأداة بياناتها؟
الطريقة البسيطة أن تعطي الأداة عنوان قاعدة الإنتاج وكلمة مرور المستخدم الذي يملكها، وهذه الطريقة غير مناسبة إطلاقاً لسببين: الأول أن تقريراً واحداً ثقيلاً، مثل مجموع مبيعات سنتين دون فهرس، يقرأ ملايين الصفوف ويأخذ المعالج والذاكرة من التطبيق الذي يخدم العملاء، والثاني أن مستخدم المالك يستطيع أن يعدل ويحذف، فخطأ واحد في محرر SQL داخل الأداة يغير بيانات حقيقية. والصورة التالية تبين الطريقتين:

لذلك فالقاعدة هي: مستخدم قاعدة بيانات للقراءة فقط خاص بأداة BI، وإذا كانت التقارير ثقيلة أو القاعدة مشغولة فاجعل الأداة تقرأ من نسخة للقراءة Read Replica كما شرحنا في توجيه القراءة في عناقيد قواعد البيانات، أو من مخزن بيانات Data Warehouse منفصل، وهذا موضوع مخطط له في مرحلة البيانات. وسوف نرى في التجربة كيف يرفض PostgreSQL أمر الحذف من مستخدم القراءة.
مكونات Metabase وقاعدة بياناتها الخاصة
Metabase تطبيق Java واحد يعمل على المنفذ 3000، ولكن لاحظ أن هناك قاعدتي بيانات مختلفتين في كل تركيب، كما تبين الصورة التالية:

- قاعدة التطبيق Application Database: تحفظ فيها Metabase الأسئلة واللوحات والمستخدمين والصلاحيات وبيانات الاتصال بمصادرك، وبشكل افتراضي هي ملف H2 داخل ال Container، ولكن توثيق قاعدة التطبيق يوصي ب PostgreSQL للإنتاج ويصف H2 بأنه للتجربة المحلية وينصح بتجنبه في الإنتاج، والسبب أن الملف يضيع مع ال Container إذا لم تربطه ب Volume، وأنه أضعف في الأداء والموثوقية، ولهذا توجد صفحة كاملة عن الانتقال من H2.
- قاعدة بياناتك: المصدر الذي تسأله، وتتصل به Metabase بالمستخدم الذي تعطيها إياه.
- مفتاح التشفير: بيانات الاتصال بمصادرك، ومنها كلمات المرور، تحفظ في قاعدة التطبيق، والمتغير
MB_ENCRYPTION_SECRET_KEYيجعلها مشفرة كما في توثيق التشفير، ويجب أن يكون 16 حرفاً على الأقل بحسب صفحة المتغيرات.
الرخصة والإصدارات التجارية
بحسب ملف الرخصة في المستودع فالكود خارج المجلد enterprise برخصة AGPL، وما داخله برخصة Metabase التجارية، وال Image metabase/metabase على Docker Hub هي النسخة المفتوحة برخصة AGPL، أما metabase/metabase-enterprise فبالرخصة التجارية. وتعرض صفحة الأسعار النسخة المفتوحة مجاناً إلى جانب خطط مدفوعة Starter وPro وEnterprise، وبعض الميزات التي تحتاجها الشركات الأكبر مدفوعة، فمثلاً تقييد الصفوف والأعمدة لكل مجموعة متاح بحسب صفحته في Pro وEnterprise فقط. وتوضح صفحة الرخص أن الرخصة التجارية تسمح أيضاً بتضمين Embedding اللوحات في تطبيقك دون أن يخضع تطبيقك لشروط AGPL، فإذا كنت ستضمن لوحات Metabase داخل منتج تبيعه فاقرأ الصفحة قبل أن تبدأ.
تجربة عملية: متجر تجريبي ولوحة مبيعات
الآن سوف نقوم ببناء متجر تجريبي يبيع التمور والقهوة والعسل في عشر مدن سعودية، ثم نثبت Metabase ونبني عليه لوحة مبيعات. نشغل كل شيء في Docker على المنافذ المحلية 127.0.0.1 فقط، وإذا لم يكن Docker مثبتاً لديك فابدأ من دليل تثبيت Docker.
بيانات المتجر ومستخدم القراءة
البيانات خمسة جداول: المدن والمنتجات والعملاء والطلبات وبنود الطلبات، ثم View اسمه sales يجمعها في جدول واحد مسطح يسهل السؤال منه، ثم مستخدم bi_reader لا يملك إلا القراءة. والأسماء العربية في البيانات عن قصد حتى نرى كيف تعرضها الأداة، وتصميم الجداول نفسه شرحناه في تصميم قواعد البيانات:
-- Sample data for the BI lab: a fictional online store (orders, products, cities)
SELECT setseed(0.42);
CREATE TABLE cities (
id int PRIMARY KEY,
name text NOT NULL,
region text NOT NULL
);
INSERT INTO cities VALUES
(1,'الرياض','الوسطى'),(2,'جدة','الغربية'),(3,'مكة المكرمة','الغربية'),
(4,'المدينة المنورة','الغربية'),(5,'الدمام','الشرقية'),(6,'الخبر','الشرقية'),
(7,'أبها','الجنوبية'),(8,'تبوك','الشمالية'),(9,'بريدة','الوسطى'),(10,'حائل','الشمالية');
CREATE TABLE products (
id int PRIMARY KEY,
name text NOT NULL,
category text NOT NULL,
price numeric(10,2) NOT NULL
);
INSERT INTO products VALUES
(1,'تمر سكري 1 كجم','تمور',45),(2,'تمر عجوة 1 كجم','تمور',90),(3,'تمر خلاص 1 كجم','تمور',38),
(4,'قهوة عربية شقراء 500 جم','قهوة',65),(5,'قهوة مختصة إثيوبية 250 جم','قهوة',58),(6,'هيل حب 250 جم','بهارات',72),
(7,'دلة قهوة نحاس','أواني',140),(8,'فناجين قهوة 12 قطعة','أواني',55),(9,'زعفران 5 جم','بهارات',95),
(10,'عسل سدر 500 جم','عسل',180),(11,'عسل طلح 500 جم','عسل',120),(12,'بخور عود 50 جم','بخور',210);
CREATE TABLE customers (
id int PRIMARY KEY,
city_id int NOT NULL REFERENCES cities(id),
joined_at date NOT NULL
);
INSERT INTO customers
SELECT g,
-- Riyadh and Jeddah get more customers than small cities
(ARRAY[1,1,1,1,2,2,2,3,4,5,5,6,7,8,9,10])[1 + floor(random()*16)::int],
date '2025-01-01' + floor(random()*600)::int
FROM generate_series(1, 3000) g;
CREATE TABLE orders (
id int PRIMARY KEY,
customer_id int NOT NULL REFERENCES customers(id),
created_at timestamptz NOT NULL,
status text NOT NULL CHECK (status IN ('مكتمل','ملغي','مسترجع'))
);
INSERT INTO orders
SELECT g,
1 + floor(random()*3000)::int,
timestamptz '2025-01-01 08:00+03' + (random() * (timestamptz '2026-09-30' - timestamptz '2025-01-01')),
CASE WHEN random() < 0.90 THEN 'مكتمل' WHEN random() < 0.6 THEN 'ملغي' ELSE 'مسترجع' END
FROM generate_series(1, 20000) g;
CREATE TABLE order_items (
order_id int NOT NULL REFERENCES orders(id),
product_id int NOT NULL REFERENCES products(id),
quantity int NOT NULL,
PRIMARY KEY (order_id, product_id)
);
INSERT INTO order_items
SELECT DISTINCT ON (o.id, p.pid) o.id, p.pid, 1 + floor(random()*3)::int
FROM orders o
CROSS JOIN LATERAL (SELECT 1 + floor(random()*12)::int AS pid FROM generate_series(1, 1 + (o.id % 3))) p;
-- One flat view for reports: each row is one product line in one order
CREATE VIEW sales AS
SELECT o.id AS order_id,
o.created_at,
o.status,
c.name AS city,
c.region,
p.name AS product,
p.category,
oi.quantity,
oi.quantity * p.price AS revenue
FROM orders o
JOIN customers cu ON cu.id = o.customer_id
JOIN cities c ON c.id = cu.city_id
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON p.id = oi.product_id;
-- A read-only user for the BI tool: it can read the data and nothing else
CREATE ROLE bi_reader LOGIN PASSWORD 'change-me-reader';
GRANT CONNECT ON DATABASE store TO bi_reader;
GRANT USAGE ON SCHEMA public TO bi_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO bi_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO bi_reader;نحفظ الملف باسم store.sql ونشغل قاعدة المتجر على شبكة Docker خاصة بالتجربة، دون نشر منفذها على الجهاز:
docker network create lab-bi-net
docker run -d --name lab-bi-store --network lab-bi-net \
-e POSTGRES_DB=store -e POSTGRES_USER=store_owner -e POSTGRES_PASSWORD=change-me-owner \
-v lab-bi-store-data:/var/lib/postgresql postgres:18.6
docker exec -i lab-bi-store psql -U store_owner -d store < store.sqlوالمخرج سوف يكون كما يلي:
setseed
---------
(1 row)
CREATE TABLE
INSERT 0 10
CREATE TABLE
INSERT 0 12
CREATE TABLE
INSERT 0 3000
CREATE TABLE
INSERT 0 20000
CREATE TABLE
INSERT 0 37860
CREATE VIEW
CREATE ROLE
GRANT
GRANT
GRANT
ALTER DEFAULT PRIVILEGESقبل أن نعطي المستخدم لأي أداة نتأكد أنه يقرأ ولا يكتب، فنسأل بالمستخدم bi_reader عن أعلى خمس مدن، ثم نحاول أن نحذف طلباً:
docker exec -e PGPASSWORD=change-me-reader lab-bi-store psql -h 127.0.0.1 -U bi_reader -d store \
-c "SELECT city, count(DISTINCT order_id) AS orders, round(sum(revenue)) AS revenue
FROM sales WHERE status = 'مكتمل' GROUP BY city ORDER BY revenue DESC LIMIT 5;" \
-c "DELETE FROM orders WHERE id = 1;"والمخرج سوف يكون كما يلي:
city | orders | revenue
--------+--------+---------
الرياض | 4482 | 1644749
جدة | 3281 | 1217843
الدمام | 2233 | 850585
بريدة | 1259 | 475893
تبوك | 1207 | 444478
(5 rows)
ERROR: permission denied for table ordersالقراءة تعمل والحذف مرفوض من قاعدة البيانات نفسها، وهذا هو المهم، فالحماية لا تعتمد على إعدادات Metabase ولا على حرص من يستخدمها.
تثبيت Metabase مع قاعدة تطبيق PostgreSQL
نولد كلمة مرور قاعدة التطبيق ومفتاحي التشفير والجلسات في ملف .env، ثم نكتب ملف Compose بإصدار مثبت من Metabase وPostgreSQL:
mkdir -p ~/lab-bi/metabase && cd ~/lab-bi/metabase
cat > .env <<EOF
MB_DB_PASS=$(openssl rand -hex 16)
MB_ENCRYPTION_SECRET_KEY=$(openssl rand -base64 32)
MB_SESSION_SECRET_KEY=$(openssl rand -hex 24)
EOF
chmod 600 .envname: lab-bi
services:
metabase-db:
image: postgres:18.6
container_name: lab-bi-mbdb
restart: unless-stopped
environment:
POSTGRES_DB: metabase
POSTGRES_USER: metabase
POSTGRES_PASSWORD: ${MB_DB_PASS}
volumes:
- mbdb-data:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U metabase -d metabase"]
interval: 5s
retries: 10
metabase:
image: metabase/metabase:v0.63.19.3
container_name: lab-bi-metabase
restart: unless-stopped
depends_on:
metabase-db:
condition: service_healthy
ports:
- "127.0.0.1:19300:3000"
environment:
MB_DB_TYPE: postgres
MB_DB_HOST: metabase-db
MB_DB_PORT: 5432
MB_DB_DBNAME: metabase
MB_DB_USER: metabase
MB_DB_PASS: ${MB_DB_PASS}
MB_ENCRYPTION_SECRET_KEY: ${MB_ENCRYPTION_SECRET_KEY}
MB_SESSION_SECRET_KEY: ${MB_SESSION_SECRET_KEY}
MB_SITE_URL: http://127.0.0.1:19300
JAVA_TIMEZONE: Asia/Riyadh
healthcheck:
test: ["CMD-SHELL", "curl -fs http://localhost:3000/api/health"]
interval: 15s
start_period: 300s
retries: 5
networks:
- default
- data
networks:
data:
name: lab-bi-net
external: true
volumes:
mbdb-data:
name: lab-bi-mbdb-datadocker compose up -d
docker logs -f lab-bi-metabaseوالمخرج المهم من السجل، بعد حذف بقية الأسطر:
2026-10-07 18:33:11,527 INFO util.encryption :: Saved credentials encryption is ENABLED for this Metabase instance. 🔐
2026-10-07 18:35:21,461 INFO core.core :: Starting Metabase version v0.63.19.3 (c03ae81) ...
2026-10-07 18:35:25,054 INFO core.core :: Setting up and migrating Metabase DB. Please sit tight, this may take a minute...
2026-10-07 18:35:28,435 INFO app-db.setup :: Successfully verified PostgreSQL 18.6 (Debian 18.6-1.pgdg13+2) application database connection. ✅
2026-10-07 18:38:06,769 INFO app-db.encryption :: MB_ENCRYPTION_SECRET_KEY set on a new database. Marked database as encrypted. ✅
2026-10-07 18:38:14,032 INFO core.core :: Metabase Initialization COMPLETEفي الإعداد أعلاه لاحظ التالي:
- ثبتنا ال Image على
v0.63.19.3وليسlatestالتي تستخدمها أمثلة توثيق Docker، والسبب أنlatestقد تنقلك إلى إصدار رئيسي جديد يغير قاعدة التطبيق عند أول إعادة تشغيل دون أن تقرر ذلك، وقد صدر 0.64 أثناء كتابة المقال، فالتحديث يكون بقرار وبعد نسخة احتياطية. - التشغيل الأول أخذ قرابة ثلاث دقائق لإنشاء جداول قاعدة التطبيق، لذلك جعلنا
start_periodفي فحص الصحة خمس دقائق، فلا تظن أن ال Container معطل إذا لم يفتح فوراً. - ال Container على شبكتين: الشبكة الافتراضية للمشروع مع قاعدة التطبيق، والشبكة
lab-bi-netمع قاعدة المتجر، فقاعدة التطبيق لا تصل إليها قاعدة المتجر ولا العكس، والتفصيل في شبكات Docker. JAVA_TIMEZONEتحدد المنطقة الزمنية التي تجمع عليها التقارير اليومية والشهرية، فطلب الساعة الواحدة فجراً بتوقيت الرياض يحسب في يومه الصحيح.
الإعداد الأول وإضافة قاعدة المتجر
افتح http://127.0.0.1:19300 فيظهر معالج الإعداد الأول، تكتب فيه اسمك وبريدك وكلمة مرور المدير واسم الموقع، ثم يسألك عن قاعدة بياناتك. والصورة التالية تبين نموذج إضافة قاعدة PostgreSQL من صفحة الإدارة، بالعنوان lab-bi-store وهو اسم ال Container على الشبكة، والمستخدم bi_reader:

بعد الحفظ تقرأ Metabase بنية الجداول Sync وتعرض الجداول والأعمدة، ولاحظ خيار نفق SSH، فهو مفيد عندما تكون قاعدة البيانات على سيرفر آخر لا تريد أن تفتح منفذها للشبكة.
السؤال الأول بالنقر
من زر New نختار Question، ثم البيانات Sales، ثم الفلتر Status is مكتمل، ثم Summarize مجموع Revenue حسب City، وأخيراً الترتيب تنازلياً، والصورة التالية تبين محرر الأسئلة Notebook بعد هذه الخطوات:

وعند الضغط على Visualize تختار Metabase نوع الرسم المناسب، والنتيجة كما في الصورة التالية:

وهذا هو الفرق الحقيقي الذي تقدمه Metabase لفريقك: موظف المبيعات الذي لا يعرف SQL يستطيع أن يبني هذا السؤال بنفسه، ويغير الفلتر إلى «ملغي» ليرى أين تلغى الطلبات أكثر.
سؤال SQL بمتغيرات
بعض الأسئلة أسهل كتابة بلغة SQL، وMetabase تسمح بذلك من New ثم SQL query، والأهم أنها تسمح بمتغيرات تربطها بفلاتر اللوحة، كما في توثيق متغيرات SQL. والسؤال التالي يعرض أكثر خمسة منتجات مبيعاً، والمتغيران {{city}} و{{created}} من نوع Field Filter مرتبطان بعمودي المدينة والتاريخ، فتضيف Metabase الشرط المناسب مكانهما، وإذا لم يختر المستخدم قيمة يصبح الشرط صحيحاً دائماً:
SELECT product AS "المنتج", sum(quantity) AS "الكمية", round(sum(revenue)) AS "المبيعات"
FROM sales
WHERE status = 'مكتمل'
AND {{city}}
AND {{created}}
GROUP BY product
ORDER BY "المبيعات" DESC
LIMIT 5تذكر أن محرر SQL يرسل ما تكتبه إلى قاعدة البيانات كما هو، وهذا سبب إضافي لأن يكون المستخدم للقراءة فقط، وفي صفحة الصلاحيات تستطيع أن تمنع مجموعات المستخدمين من محرر SQL وتتركهم مع الأسئلة بالنقر.
لوحة المبيعات بفلترين
جمعنا خمس بطاقات في لوحة واحدة: إجمالي المبيعات رقماً كبيراً، والمبيعات الشهرية خطاً زمنياً، والمبيعات حسب المدينة أعمدة، والمبيعات حسب الفئة دائرة، وأكثر المنتجات مبيعاً جدولاً من سؤال SQL، ثم أضفنا فلترين، المدينة والتاريخ، وربطنا كل فلتر بالعمود المناسب في كل بطاقة. والصورة التالية تبين اللوحة دون فلاتر:

وعند اختيار مدينة جدة وفترة الأشهر الاثني عشر السابقة تتغير البطاقات كلها معاً، كما في الصورة التالية:

لاحظ أن الجدول الذي بنيناه بسؤال SQL تغير أيضاً، لأن المتغيرين فيه مربوطان بفلاتر اللوحة، أما الرقم الإجمالي فيعرض اختصاراً مثل 6.7M، وتستطيع أن تغير التنسيق من إعدادات البطاقة إذا أردت الرقم كاملاً.
العربية في Metabase بصراحة
هناك جانبان للعربية في أي أداة BI: البيانات العربية داخل الرسوم، وواجهة الأداة نفسها، وفيما يلي ما وجدناه في الإصدار v0.63.19.3:
- البيانات العربية تعمل: أسماء المدن والمنتجات والفئات تظهر صحيحة في الأعمدة والدوائر والجداول، وعناوين البطاقات واللوحة والفلاتر العربية كذلك، وعملية الفلترة بقيمة عربية مثل «مكتمل» تعمل.
- الواجهة مترجمة إلى العربية: قائمة اللغات في الإصدار الذي ثبتناه تحتوي
arوar_SA، وتستطيع أن تختارها لكل مستخدم من إعدادات حسابه أو للموقع كله. - ولكن الاتجاه يبقى من اليسار لليمين: القائمة الجانبية تبقى في اليسار والجداول والمحاور لا تنعكس، وهذا ما يقوله توثيق اللغات صراحة: الواجهة مصممة حول اللغات التي تقرأ من اليسار لليمين، رغم أن Metabase تدعم لغات من اليمين لليسار.
- الترجمة غير كاملة: بعض النصوص بقيت بالإنجليزية في تجربتنا، مثل اسم عمود City تحت الرسم وجزء من عنوان المحور، لأن الأسماء التلقائية للأعمدة تأتي من أسماء الأعمدة في قاعدة البيانات، وتستطيع أن تغيرها من صفحة البيانات الوصفية Table Metadata إلى أسماء عربية.
والصورة التالية تبين اللوحة نفسها بعد اختيار العربية للمستخدم:

والصورة التالية تبين الصفحة الرئيسية بالعربية، ولاحظ أن القائمة الجانبية مترجمة وفي اليسار:

وبالتالي فإذا كان فريقك يقرأ العربية ويقبل واجهة من اليسار لليمين فMetabase مناسبة، أما إذا كان شرطك واجهة عربية كاملة من اليمين لليسار فهذا لا يتوفر فيها اليوم، ولا في Superset كما سنرى في دليل Superset.
النسخ الاحتياطي لقاعدة التطبيق
كل عمل فريقك في Metabase، من الأسئلة واللوحات والمستخدمين والصلاحيات، موجود في قاعدة التطبيق، وبحسب توثيق النسخ الاحتياطي يكفي أن تنسخ هذه القاعدة، لذلك نأخذ تفريغاً منها:
docker exec lab-bi-mbdb pg_dump -U metabase -d metabase -Fc -f /tmp/metabase-app.dump
docker exec lab-bi-mbdb ls -la /tmp/metabase-app.dump-rw-r--r-- 1 root root 1037434 Oct 8 07:31 /tmp/metabase-app.dumpملف من قرابة 1 ميجابايت يحمل كل عمل الفريق، فانقله خارج ال Container وضعه ضمن نظام النسخ الذي شرحناه في النسخ الاحتياطي بقاعدة 3-2-1، وخذ نسخة قبل كل تحديث للإصدار، والسبب أن الإصدار الجديد يغير جداول قاعدة التطبيق عند أول تشغيل، فإذا فشل التحديث رجعت إلى الإصدار السابق مع نسخة القاعدة التي تناسبه. واحفظ أيضاً ملف .env، لأن نسخة القاعدة دون MB_ENCRYPTION_SECRET_KEY لا تفتح كلمات مرور الاتصال المشفرة فيها.
التنظيف
cd ~/lab-bi/metabase && docker compose down -v
docker rm -f lab-bi-store
docker volume rm lab-bi-store-data
docker network rm lab-bi-netقبل أن تشغلها لفريقك
- HTTPS أمامها: انشر Metabase خلف Reverse Proxy بشهادة، مثل Caddy، واجعل
MB_SITE_URLالعنوان الحقيقي، فالروابط في رسائل البريد والاشتراكات تبنى منه. - مستخدم قراءة لكل مصدر: كما فعلنا مع
bi_reader، وعلى نسخة للقراءة إن أمكن. - إصدار مثبت وتحديث مقصود: اقرأ ملاحظات الإصدار، وخذ نسخة من قاعدة التطبيق، ثم غير الرقم في ملف Compose.
- الصلاحيات من البداية: أنشئ مجموعات للمبيعات والإدارة، وحدد من يرى أي قاعدة ومن يستخدم محرر SQL، قبل أن تدعو الفريق كله.
- لا تجعل الاستعلامات الثقيلة مفاجأة: البطاقة التي تقرأ ملايين الصفوف تبطئ كل من يفتح اللوحة، فأضف الفهارس المناسبة أو جداول ملخصة كما في PostgreSQL لكل شيء.
الخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- أداة BI تحول الأسئلة المتكررة إلى لوحات تتحدث وحدها، والمفاهيم المشتركة هي المصدر والسؤال والبطاقة واللوحة والفلتر.
- لا تربط الأداة بقاعدة الإنتاج بمستخدم المالك، وإنما بمستخدم للقراءة فقط، ويفضل على نسخة للقراءة، وقد رأينا PostgreSQL يرفض الحذف منه.
- Metabase تحفظ عملك في قاعدة تطبيق، فاستخدم PostgreSQL بدلاً من H2، واضبط
MB_ENCRYPTION_SECRET_KEY، وانسخ القاعدة والمفتاح معاً. - النسخة المفتوحة برخصة AGPL، وبعض الميزات مثل تقييد الصفوف والأعمدة والتضمين دون شروط AGPL في الخطط التجارية.
- البيانات العربية تعرض صحيحة والواجهة مترجمة، ولكن الاتجاه يبقى من اليسار لليمين.
وفي المقال التالي نثبت Apache Superset على البيانات نفسها، وهي أداة أثقل وأقوى لمن يكتب SQL، أما المقارنة الكاملة بين Metabase وSuperset وPower BI ففي Metabase أم Superset أم Power BI؟
دليلApache Superset: تحليلات ولوحات تقارير على سيرفرككيف تثبت Apache Superset على سيرفرك، وتبني منه مقاييس ورسوماً ولوحة تقارير من بيانات شركتك.
دليلعناقيد قواعد البيانات Database Clustering ومتى تحتاجهاماذا تعني كلمة Cluster في كل قاعدة بيانات، ومتى تحتاج نسخاً أو انتقالاً تلقائياً، ومتى يكفيك سيرفر واحد مع نسخ احتياطية جيدة.
دليلPostgreSQL لكل شيء: متى يكفيك ومتى تحتاج غيره؟كم عملاً يستطيع PostgreSQL واحد أن يقوم به في شركتك الناشئة قبل أن تحتاج Redis أو Elasticsearch، وما الإشارة التي تقول لك إن الوقت قد حان.
دليلالنسخ الاحتياطي بقاعدة 3-2-1 واختبار الاستعادةكيف تحدد كم بيانات تقبل أن تخسر، وتبني نسخاً لا يستطيع المخترق حذفها، وتتأكد أنك تستطيع استعادتها فعلاً.
دليلتصميم قواعد البيانات: التطبيع Normalization ومتى تكسرهلماذا يفشل الجدول الواحد الكبير، وكيف تقسمه إلى جداول لا تناقض نفسها، وما الذي يتغير عندما تكبر البيانات.سجل التحديثات
- أكتوبر 2026: كتابة المقال، وتثبيت Metabase v0.63.19.3 مع قاعدة تطبيق PostgreSQL 18.6 في Docker، وبناء لوحة مبيعات على متجر تجريبي ببيانات عربية، واختبار الواجهة العربية.
- أكتوبر 2026: ربط مقال المقارنة بين أدوات التقارير بعد نشره.