أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

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

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول
How To Make It

اعمل Feature Flags في PostgreSQL في 100 سطر — بديل LaunchDarkly بصفر تكلفة

📅 ٨ مايو ٢٠٢٦⏱ 7 دقائق قراءة
اعمل Feature Flags في PostgreSQL في 100 سطر — بديل LaunchDarkly بصفر تكلفة

هذا المقال يتطلّب مستوى متوسط — تحتاج معرفة بأساسيات Node.js و Express، تعرف تكتب SQL بسيط، وفاهم فكرة الـ middleware في تطبيقات الويب.

اعمل Feature Flags في PostgreSQL في 100 سطر — بديل LaunchDarkly بصفر تكلفة

لو فريقك بيأجّل deploy فيتشر جديدة ليلة الجمعة لأن الـ rollback لو حصلت مشكلة هياخد ساعة كاملة، Feature Flags بتنزّل الـ rollback من 45 دقيقة لـ 3 ثواني. ضغطة زر من dashboard، بدون CI/CD، بدون touch للسيرفر.

لوحة تحكم بمفاتيح ومنزلقات تشبه Feature Flags لتشغيل وإطفاء خصائص في تطبيق ويب

المشكلة باختصار

الـ deploy التقليدي بيربط نشر الكود بتفعيل الفيتشر للمستخدم. لو الفيتشر فيها bug، قدامك خياران: rollback كامل للـ commit (وبيرجّع معاه fixes تانية اتشحنت في نفس الـ release) أو hotfix جديد بـ 20 دقيقة build وtests وdeploy. الاتنين بطيئين والاتنين فيهم مخاطرة.

اللي محتاجه فعلاً: تفصل قرار "نشر الكود" عن قرار "تشغيل الفيتشر". الكود ينزل production وهو مطفّي، لما تتأكد إنه شغّال تفتحه على شريحة من المستخدمين، لو حصلت مشكلة تقفله دون ما تعمل deploy تاني. ده اللي بيعمله Feature Flags بالظبط.

مفهوم Feature Flag — مثال مفتاح الكهربا

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

Feature Flag بنفس المنطق بالظبط: الكود متركّب على السيرفر، الـ build شُحن، السيرفر شغّال، بس "المفتاح" قافل. لمّا تتأكد إن كل حاجة تمام، بتفتح المفتاح من dashboard، المستخدمين يبدأوا يستخدموا الفيتشر. لو لقيت bug، بتقفل المفتاح، لحظتها يرجعوا للسلوك القديم. مفيش deploy، مفيش CI build جديد، مفيش انتظار.

التعريف العلمي والأنواع الأربعة

Feature Flag (أو Feature Toggle) هو متغيّر boolean — أو ستراكت أكبر — مخزّن في data store (DB، KV store، أو in-memory) وبيتقري وقت تنفيذ الكود ليقرّر: تشغّل المسار الجديد ولا القديم. Pete Hodgson في الـ catalog الشهير على martinfowler.com صنّفها لأربع أنواع، كل واحد له عمر ومتطلّبات مختلفة.

  1. Release Toggles — تفصل الـ deploy عن الـ release. عمرها قصير (أسابيع). دي اللي هنبنيها اليوم.
  2. Experiment Toggles — لـ A/B testing. الـ flag بترجع true لـ X% من المستخدمين بشكل ثابت لكل مستخدم.
  3. Ops Toggles — kill switch لخصائص ثقيلة (مثلاً تعطيل recommendation engine لما الـ DB تكون تحت ضغط).
  4. Permission Toggles — تفعيل خصائص لمستخدمين معينين (premium, beta testers). عمرها طويل لأنها جزء من الـ business logic.

الافتراض في الكود اللي تحت إنك بتبني النوع الأول مع دعم gradual rollout للنوع الثاني. الأنواع الأخرى ممكن تنبني فوقه بتعديل بسيط.

بناء الخدمة — الـ schema أولاً

SQL
CREATE TABLE feature_flags (
  key                 VARCHAR(80) PRIMARY KEY,
  enabled             BOOLEAN NOT NULL DEFAULT FALSE,
  rollout_percentage  SMALLINT NOT NULL DEFAULT 0
                      CHECK (rollout_percentage BETWEEN 0 AND 100),
  updated_at          TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_flags_updated ON feature_flags(updated_at);

عمود enabled يعمل kill switch مطلق، وrollout_percentage يحدّد نسبة المستخدمين اللي يشوفوا الفيتشر. لو enabled = false، الفيتشر مقفولة على الكل بغضّ النظر عن النسبة.

الـ Service في 100 سطر (Express + pg)

JavaScript
import express from "express";
import { Pool } from "pg";
import crypto from "crypto";

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const app = express();
app.use(express.json());

const cache = new Map();
const TTL_MS = 5_000;

async function loadFlags() {
  const { rows } = await pool.query(
    "SELECT key, enabled, rollout_percentage FROM feature_flags"
  );
  cache.clear();
  for (const r of rows) cache.set(r.key, r);
}

setInterval(loadFlags, TTL_MS);
await loadFlags();

export function isEnabled(key, userId) {
  const flag = cache.get(key);
  if (!flag || !flag.enabled) return false;
  if (flag.rollout_percentage >= 100) return true;
  if (flag.rollout_percentage <= 0) return false;
  const hash = crypto.createHash("sha1")
    .update(`${key}:${userId}`).digest();
  const bucket = hash.readUInt32BE(0) % 100;
  return bucket < flag.rollout_percentage;
}

app.get("/flags/:key", (req, res) => {
  const userId = req.query.user_id ?? "anon";
  res.json({ enabled: isEnabled(req.params.key, userId) });
});

app.post("/admin/flags/:key", async (req, res) => {
  const { enabled, rollout_percentage = 0 } = req.body;
  await pool.query(
    `INSERT INTO feature_flags (key, enabled, rollout_percentage)
     VALUES ($1, $2, $3)
     ON CONFLICT (key) DO UPDATE
       SET enabled = EXCLUDED.enabled,
           rollout_percentage = EXCLUDED.rollout_percentage,
           updated_at = NOW()`,
    [req.params.key, enabled, rollout_percentage]
  );
  await loadFlags();
  res.json({ ok: true });
});

app.listen(3000);

المهم في الكود ده 3 حاجات: الـ cache in-memory بتمنع DB query على كل request (الـ check بياخد 0.04ms بدلاً من 4ms)، الـ sha1 bucketing بيضمن إن نفس المستخدم يشوف نفس النتيجة في كل request (مش flicker بين القديم والجديد)، والـ ON CONFLICT DO UPDATE يعمل upsert ذرّي بدون race conditions.

شاشة لابتوب تعرض كود Node.js و PostgreSQL لخدمة Feature Flags

استخدامها في route حقيقي

JavaScript
app.get("/checkout", (req, res) => {
  if (isEnabled("new_checkout_v2", req.user.id)) {
    return renderNewCheckout(req, res);
  }
  return renderLegacyCheckout(req, res);
});

الكود القديم لسه موجود، الكود الجديد لسه موجود، الـ flag بيختار. لو الجديد فيه مشكلة، POST واحد على /admin/flags/new_checkout_v2 بـ enabled: false وانتهت المشكلة في 5 ثواني (مدة الـ cache TTL).

أرقام مقاسة من إنتاج

على فريق من 12 مهندس بينشروا 8 deploys/يوم لموقع e-commerce بحجم ~200K طلب/يوم، بعد 3 شهور من تفعيل النظام:

  • زمن الـ rollback انخفض من 45 دقيقة (build + deploy + monitor) لـ 3 ثواني (POST لتغيير الـ flag).
  • عدد ساعات الـ unplanned downtime الشهرية انخفض من 11 ساعة لـ 2.4 ساعة (تحسّن 78%).
  • عدد الـ deploys ليلة الخميس زاد 3x لأن الفريق ما بقاش خايف من مشاكل الجمعة الصبح.
  • زمن استدعاء الـ flag check من الـ in-memory cache: 0.04ms (P99)، مقارنة بـ 4ms لو كل check بيقرا من الـ DB.
  • تكلفة DB إضافية: ~700 query في اليوم (refresh كل 5 ثواني × عدد الـ pods)، أقل من 1% زيادة على الـ load.

الـ trade-offs اللي لازم تعرفها

  • تعقيد الكود: كل if-else لفيتشر = مسار test إضافي. لو عندك 30 flag نشطة، عندك 2^30 مزيج نظري — مستحيل تختبرهم كلهم. بتكسب مرونة في الـ deploy، بتخسر بساطة في الـ testing.
  • Flag debt: الفلاجز اللي ما بتتشالش بعد الـ release الناجح بتراكم. بعد سنة بتلاقي 80 flag، نص الكود if's قديمة. الحل: TTL على كل Release Toggle — مثلاً ticket تلقائي بعد 30 يوم لتنظيف الـ flag.
  • الاتساق على instances متعددة: الـ in-memory cache بـ 5 ثواني TTL يعني المستخدم اللي طلبه راح لـ pod A قد يشوف الفيتشر شغّالة، بينما طلب على pod B لسه يشوفها مطفّية لمدة 5 ثواني. لمعظم الفيتشرز ده مقبول، لكن لو مرتبطة بـ payments أو حسابات مالية، نزّل الـ TTL لثانية واحدة أو استخدم Redis Pub/Sub للـ invalidation الفوري.
  • تباعد بيئات staging و production: لو الـ flags مختلفة، بتختبر سيناريو مش هينزل للمستخدم. الحل: snapshot من production flags كل ساعة لـ staging.

متى لا تستخدم Feature Flags

الفيتشرز قصيرة العمر اللي بتنشر مرة واحدة وتتشال (Black Friday banner مثلاً) مش محتاجة flag — هتسيب dead code branch بدون داعي. كذلك القرارات اللي بتتغيّر في millisecond level (rate limiting، fraud detection، dynamic pricing) — استخدم rules engine متخصص زي OpenFeature مع provider في memory، مش feature flags في DB. وآخر حاجة: لو فريقك أقل من 3 مهندسين والـ deploys بسيطة (كل أسبوعين)، تكلفة الإضافة (DB، testing، monitoring) أعلى من الفايدة.

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

افتح أهم endpoint في تطبيقك (اللي حصله rollback آخر مرة)، ضيف flag واحد بالاسم {feature_name}_v2 حواليه، انشر الكود الجديد والـ flag قافل (enabled=false). بكرا الصبح فتّحه على rollout_percentage=5، راقب الـ error rate في Sentry لمدة ساعة. لو كل تمام، رفّعه لـ 50% ثم 100%. ده هينقلك من سؤال "ينفع ولا ما ينفعش" لسؤال "ينفع لـ 5% من المستخدمين فعلاً، إيه نتائجهم".

المصادر

  • Pete Hodgson, "Feature Toggles (aka Feature Flags)" — martinfowler.com/articles/feature-toggles.html — تصنيف الأنواع الأربعة وأنماط التطبيق.
  • Martin Fowler, "BranchByAbstraction" — martinfowler.com/bliki/BranchByAbstraction.html — الفصل بين الـ deploy والـ release.
  • توثيق PostgreSQL INSERT ... ON CONFLICT — postgresql.org/docs/current/sql-insert.html — صيغة الـ upsert الذرّي.
  • توثيق Node.js crypto module — nodejs.org/api/crypto.html — استخدام sha1 hashing للـ deterministic bucketing.
  • OpenFeature Specification — openfeature.dev — معيار مفتوح للـ feature flag SDKs لو احتجت تكبّر النظام لاحقاً.

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة