الرئيسيةمن أناالدوراتالمدونةسوق الأوامرالمناهج والباقاتالشركاء

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

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

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • المناهج والباقات
  • سوق الأوامر
  • المدونة

الدعم

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

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

الرئيسيةالدوراتالمناهجالمدونةالدخول
DevOps بالعربي

مفاتيح التكرار (Idempotency Keys): أعِد المحاولة على الدفع بأمان بلا خصم مزدوج

متوسط4 أغسطس 20265 دقائق قراءة
مفاتيح التكرار (Idempotency Keys): أعِد المحاولة على الدفع بأمان بلا خصم مزدوج

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

لو عميل ضغط زر الدفع مرتين، أو الشبكة قطعت الرد وتطبيقك أعاد المحاولة تلقائيًا، ممكن تخصم منه 100 جنيه بدل 50. مفتاح التكرار (Idempotency Key) بيمنع ده بمنطق واحد على مستوى الـ API: نفس الطلب يتنفّذ مرة واحدة بس، مهما وصل مرات.

مفاتيح التكرار: إزاي تعيد المحاولة على عملية حسّاسة من غير ما تكررها

مخطط لطلب دفع POST يصل مرتين بنفس مفتاح التكرار a1b2 عبر بوابة Idempotency فينتج خصم واحد بقيمة 50 دولار بدل خصمين

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

الطلب وصل للسيرفر، الدفع اتنفّذ فعلاً، بس الرد ضاع في الطريق بسبب timeout. العميل (أو مكتبة الـ HTTP client) شاف إنه مفيش رد، فأعاد إرسال نفس الطلب. دلوقتي عندك خصمين لعملية واحدة. المشكلة مش في السيرفر ولا في الشبكة؛ المشكلة إن عملية الدفع نفسها مش آمنة للتكرار.

مثال بسيط قبل التعريف العلمي

تخيّل إنك بتطلب قهوة، وبتدّي الكاشير ورقة عليها رقم فريد كتبته إنت. زحمت السكة وقلت الطلب تاني بنفس الرقم. الكاشير بيبص، يلاقي الرقم ده اتنفّذ قبل كده، فيرجّعلك نفس الكوباية، مش يعملك تانية. الورقة دي هي مفتاح التكرار.

علميًا: العملية تكون idempotent لما تنفيذها مرة أو ألف مرة يدّي نفس الأثر. الطلبات القرائية زي GET، وكمان PUT وDELETE، آمنة للتكرار بطبيعتها. لكن POST اللي بيعمل side effect (زي الخصم) مش idempotent. الحل: العميل يولّد مفتاحًا فريدًا (UUID) لكل عملية منطقية، ويبعته في هيدر Idempotency-Key. السيرفر يخزّن المفتاح مع الرد، وأول ما يشوف نفس المفتاح تاني، يرجّع الرد المحفوظ من غير ما ينفّذ العملية.

ليه الطريقة الشائعة بتفشل

الناس بتحاول تحل ده من ناحية الواجهة: "نعطّل الزرار بعد أول ضغطة" أو "نفترض إن العميل مش هيبعت مرتين". الطريقة دي بتفشل في اللي بيحصل فعلاً: الـ retries التلقائية جوه مكتبات HTTP، والـ load balancer اللي بيعيد الطلب، وتطبيق الموبايل اللي فقد الاتصال ثانية. كل دول بيبعتوا الطلب تاني من غير ما المستخدم يلمس حاجة. لازم الحماية تكون على السيرفر، عند نقطة تنفيذ الـ side effect.

الحل خطوة بخطوة

  1. العميل يولّد UUID لكل عملية، ويبعته في هيدر Idempotency-Key.
  2. السيرفر يدوّر على المفتاح في جدول مخصص، جوه معاملة (transaction) مع قفل.
  3. لو المفتاح موجود، يرجّع الرد المحفوظ فورًا بدون تنفيذ.
  4. لو مش موجود، ينفّذ العملية مرة واحدة، ويخزّن الرد تحت المفتاح، وبعدين يرجّعه.

جدول التخزين في PostgreSQL بيعتمد على قيد PRIMARY KEY علشان يمنع إدخال نفس المفتاح مرتين على مستوى قاعدة البيانات نفسها:

SQL
CREATE TABLE idempotency_keys (
  key         TEXT PRIMARY KEY,
  response    JSONB       NOT NULL,
  status_code INT         NOT NULL,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

والمنطق في الـ endpoint (Flask + psycopg). لاحظ الـ FOR UPDATE اللي بيقفل الصف علشان يتعامل مع طلبين متزامنين بنفس المفتاح:

Python
@app.post("/payments")
def create_payment():
    key = request.headers.get("Idempotency-Key")
    if not key:
        return {"error": "Idempotency-Key required"}, 400

    with db.transaction() as tx:
        row = tx.execute(
            "SELECT response, status_code FROM idempotency_keys "
            "WHERE key = %s FOR UPDATE",
            (key,)
        ).fetchone()

        if row:                          # مفتاح متكرر: نفس الرد المحفوظ
            return row["response"], row["status_code"]

        result = charge_card(request.json)   # ينفّذ مرة واحدة بس
        tx.execute(
            "INSERT INTO idempotency_keys (key, response, status_code) "
            "VALUES (%s, %s, %s)",
            (key, Json(result), 200)
        )
        return result, 200

خطوة مهمة للإنتاج: اربط المفتاح بهوية العميل (user_id + key) علشان مفتاح حد ما يصطدمش بمفتاح حد تاني، وارفض أي طلب بيعيد استخدام مفتاح قديم بجسم مختلف.

الأرقام والـ trade-off

في نظام مدفوعات بـ 200 ألف عملية في اليوم، كان معدل الخصم المزدوج حوالي 0.3% (قرب 600 عملية يوميًا) بسبب إعادة المحاولة والشبكة. بعد تفعيل مفاتيح التكرار نزل المعدل لصفر تقريبًا. تكلفة البحث عن المفتاح في جدول مفهرس أقل من 1 مللي ثانية لكل طلب (الأرقام تقديرية للتوضيح، بس الترتيب واقعي).

الـ trade-off هنا: بتكسب أثر "مرة واحدة بالظبط" (exactly-once effect) وأمان كامل لإعادة المحاولة. بتخسر تخزين إضافي (صف لكل عملية)، زمن بحث بسيط، وتعقيد في التعامل مع الطلبات المتزامنة (محتاج قفل أو قيد فريد). الافتراض إن العملية ليها side effect حقيقي؛ من غير كده الكلام ده مالوش لازمة.

خزّن المفتاح مع مدة صلاحية (TTL) زي 24 ساعة، وامسح القديم بمهمة مجدولة. Stripe مثلًا بيحتفظ بمفاتيح التكرار لمدة 24 ساعة.

متى لا تستخدم هذه الطريقة

لو العملية قرائية بحتة (GET) فهي idempotent أصلاً ومش محتاجة مفتاح. لو العملية آمنة للتكرار بطبيعتها (زي "خلّي القيمة = X" اللي تكرارها مايغيّرش حاجة)، مفيش داعي. للأدوات الداخلية الصغيرة اللي تكرارها مش بيكلّف، مفاتيح التكرار ممكن تبقى تعقيد زيادة. ركّز الحماية على العمليات اللي تكرارها بيكلّف فلوس أو بيبعت حاجة للعميل.

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

افتح أهم endpoint فيه POST بيعمل side effect (دفع، إنشاء طلب، إرسال إيميل)، ضيف جدول idempotency_keys بقيد فريد، وخلّي العميل يبعت هيدر Idempotency-Key. بعد كده ابعت نفس الطلب مرتين ورا بعض بنفس المفتاح، وأكّد إن الـ side effect حصل مرة واحدة والرد اترجّع نفسه في المرتين.

المصادر

  • Stripe API Docs — Idempotent requests: https://docs.stripe.com/api/idempotent_requests
  • IETF Draft — The Idempotency-Key HTTP Header Field: https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/
  • RFC 9110 — HTTP Semantics (Idempotent Methods): https://www.rfc-editor.org/rfc/rfc9110#name-idempotent-methods
  • MDN Web Docs — Idempotent: https://developer.mozilla.org/en-US/docs/Glossary/Idempotent

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

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

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