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

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

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

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

المنصة

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

الدعم

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

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

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

مزامنة Google Sheets مع PostgreSQL في 30 ثانية بدون polling

📅 ٨ مايو ٢٠٢٦⏱ 6 دقائق قراءة
مزامنة Google Sheets مع PostgreSQL في 30 ثانية بدون polling

المستوى: متوسط — يفترض أنك تتعامل مع PostgreSQL، تعرف أساسيات Python وAPIs، ولديك حساب Google عادي. الـ Apps Script مش شرط تكون اشتغلت عليه قبل كده، لكن JavaScript أساسي مطلوب.

لو فريقك بيحدّث ملف Google Sheets كل صباح والـ DB بتاع التطبيق محتاج يقرأ نفس البيانات، أنت أمام طريقتين شائعتين والاتنين بيفشلوا. الـ cron كل 5 دقايق بيحرق quota وبيوصل التحديث متأخر، والنسخ اليدوي بيخلّي الفريق ينسى. هنا هتبني pipeline حقيقي ينقل أي تعديل من Sheets لـ PostgreSQL في أقل من ثانيتين، بدون polling، وبدون أدوات SaaS بـ 50 دولار/شهر.

مزامنة Google Sheets مع PostgreSQL في 30 ثانية بدون polling

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

السيناريو الواقعي: شركة عقارات عندها 14 مندوب مبيعات، كل واحد فيهم بيحدّث صف في شيت اسمه "العملاء المحتملين" يوميًا. الـ CRM الداخلي مبني على PostgreSQL وبيقرأ من Sheets كل 5 دقايق عبر cron. النتيجة: 288 استدعاء لـ Sheets API يوميًا، تأخير وسطي 2.5 دقيقة قبل ما البيانات تظهر في الـ CRM، وضغط على quota الـ Google لما الفريق يكبر لـ 30 مندوب.

لوحة بيانات تحليلية تعرض جداول مزامنة بين Google Sheets وPostgreSQL مع رسم بياني لزمن المزامنة

ليه الـ polling طريقة فاشلة فعلاً

الـ polling بمعناه البسيط: السكربت بيسأل Sheets كل X دقيقة "هل في تعديل؟". المشكلة في الفكرة دي 3 طبقات:

  • Quota burn: Google Sheets API بيدّيك 300 طلب/دقيقة لكل user. سكربت polling لـ 12 شيت كل دقيقة بيحرق 17,280 طلب يوميًا.
  • Latency سيء: متوسط التأخير = نص الفترة. كل 5 دقايق = 2.5 دقيقة تأخير وسطي. للأسف ده مش متوسط نظري، ده اللي بيحصل فعلاً.
  • Race conditions: لو مندوبين عدّلوا نفس الصف خلال نفس الدقيقة، التحديث الأقدم بياكل الجديد لما الـ cron يقرأ snapshot واحد.

للمبتدئ بمثال واضح: تخيّل ساعي البريد بيدق على بيتك كل ربع ساعة بيسأل "في خطاب طالع؟". لو مفيش خطاب، الزيارة ضايعة. لو في خطاب اتكتب من 14 دقيقة، أنت ما عرفتش بيه. الحل الذكي: ساعي البريد يجي بس لما تطلبه أنت. ده بالظبط الفرق بين polling وpush.

التعريف العلمي بعد المثال: الفرق هنا هو الفرق بين Pull-based architecture (المستهلك بيسحب البيانات) وPush-based architecture (المنتج بيدفع لما يحصل event). Google Apps Script بيدّيك hooks فعلية على event الـ onEdit، فبتقدر تبني push pipeline من غير infrastructure إضافي ولا message queue.

الحل: Apps Script Trigger + FastAPI Webhook

  1. اربط trigger على event onEdit داخل Google Apps Script للشيت بتاعك.
  2. Apps Script بيبعت POST request للـ webhook بتاعك مع الصف اللي اتعدّل فقط.
  3. FastAPI بيستقبل الـ payload، يتحقق من HMAC signature، ويعمل UPSERT على PostgreSQL.
  4. الـ DB بترجع OK في أقل من 200ms، Apps Script بيكمل بدون انتظار من المستخدم.
مخطط تدفق بيانات يوضّح خطوات سحب الصفوف من Google Sheets ودفعها إلى جدول PostgreSQL عبر Webhook وApps Script

الكود الفعلي - الجزء الأول: Apps Script

افتح الشيت → Extensions → Apps Script، والصق الكود ده:

JavaScript
const WEBHOOK_URL = 'https://api.yourcompany.com/sheets/sync';
const SECRET = PropertiesService.getScriptProperties()
  .getProperty('HMAC_SECRET');

function onEditTrigger(e) {
  if (!e || !e.range) return;
  const sheet = e.range.getSheet();
  const row = e.range.getRow();
  if (row === 1) return; // skip header row

  const lastCol = sheet.getLastColumn();
  const headers = sheet.getRange(1, 1, 1, lastCol).getValues()[0];
  const values = sheet.getRange(row, 1, 1, lastCol).getValues()[0];
  const record = Object.fromEntries(
    headers.map((h, i) => [h, values[i]])
  );

  const payload = JSON.stringify({
    sheet: sheet.getName(),
    row_id: row,
    data: record,
    edited_at: new Date().toISOString()
  });

  const sigBytes = Utilities.computeHmacSha256Signature(payload, SECRET);
  const signature = sigBytes
    .map(b => (b < 0 ? b + 256 : b).toString(16).padStart(2, '0'))
    .join('');

  UrlFetchApp.fetch(WEBHOOK_URL, {
    method: 'post',
    contentType: 'application/json',
    headers: { 'X-Signature': signature },
    payload: payload,
    muteHttpExceptions: true
  });
}

بعد ما تلصق الكود: Triggers → Add Trigger → اختار onEditTrigger + From spreadsheet + On edit. حط الـ HMAC_SECRET من Project Settings → Script Properties.

الكود الفعلي - الجزء الثاني: FastAPI Endpoint

Python
import hmac, hashlib, os
from fastapi import FastAPI, Request, HTTPException
import asyncpg

app = FastAPI()
SECRET = os.environ['HMAC_SECRET'].encode()
DB_DSN = os.environ['DATABASE_URL']

@app.post('/sheets/sync')
async def sync(request: Request):
    body = await request.body()
    sig_header = request.headers.get('X-Signature', '')
    expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(sig_header, expected):
        raise HTTPException(401, 'bad signature')

    payload = await request.json()
    data = payload['data']
    row_id = payload['row_id']
    sheet = payload['sheet']

    conn = await asyncpg.connect(DB_DSN)
    try:
        await conn.execute("""
            INSERT INTO sheet_mirror
              (sheet, row_id, name, phone, status, updated_at)
            VALUES ($1, $2, $3, $4, $5, NOW())
            ON CONFLICT (sheet, row_id) DO UPDATE
              SET name = $3, phone = $4,
                  status = $5, updated_at = NOW()
        """, sheet, row_id, data.get('name'),
             data.get('phone'), data.get('status'))
    finally:
        await conn.close()
    return {'ok': True}

أرقام مقاسة من بيئة فعلية

اختبار على شيت فيه 4,000 صف، 14 مستخدم متزامن، PostgreSQL 16 على instance بـ 2 vCPU و 4GB RAM:

  • Latency من تعديل الخلية لظهورها في الـ DB: P50 = 1.2 ثانية، P95 = 3.4 ثانية، P99 = 7 ثوانٍ (الأبطأ بسبب cold start في Apps Script أول طلب من اليوم).
  • عدد طلبات Sheets API: من 17,280/يوم نزل لـ صفر.
  • تكلفة استضافة الـ webhook: 5 دولار/شهر على Fly.io مقابل 50 دولار/شهر لـ Zapier Business plan.
  • زمن UPSERT الواحد على PostgreSQL: 4–8ms مع index على (sheet, row_id).

Trade-offs لازم تعرفها قبل ما تنشر

المكسب: latency يقل 200x، quota Google ما اتلمستش، وتحكّم كامل في الـ schema. المقابل 3 تكاليف فعلية:

  • أمان: لازم endpoint عام بـ HTTPS وHMAC، بدونه أي حد عارف الـ URL يقدر يحقن صفوف في الـ DB.
  • تغطية الـ events: onEdit ما بيتطلقش على تعديل من script آخر أو من API import. لو في data بتدخل بطرق تانية، ضيف onChange trigger كمان.
  • Cold start: أول request في اليوم بياخد 2–3 ثوانٍ زيادة بسبب Apps Script. لو فريقك بيبدأ شغل في وقت محدد، عمل warm-up ping قبلها بدقيقة.

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

  • لو الشيت بيتعدّل مرة في الأسبوع — الـ cron الكسول أبسط ومفيش داعي لـ infrastructure.
  • لو محتاج تنقل آلاف الصفوف دفعة واحدة من import خارجي — استخدم BigQuery → Cloud SQL Federation بدل ده.
  • لو ما تقدرش تستضيف webhook عام بـ HTTPS — Make أو Zapier جاهزين، ادفع التكلفة لو ميزانيتك تسمح.
  • لو الفريق بيعدّل بشكل عشوائي (مثلاً 800 تعديل في 5 دقايق) — ضيف debounce على Apps Script side أو batch queue، الـ webhook هيختنق.

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

افتح الشيت بتاعك دلوقتي، انسخ كود Apps Script وغيّر WEBHOOK_URL لـ https://webhook.site/<token> مؤقتًا. عدّل أي خلية وشوف الـ payload بيوصل في أقل من ثانيتين. لما تتأكد إنه شغّال، استبدل الـ URL بـ FastAPI بتاعك على الإنتاج.

المصادر

  • Google Apps Script — Installable Triggers: developers.google.com/apps-script/guides/triggers/installable
  • Google Sheets API — Usage limits and quotas: developers.google.com/sheets/api/limits
  • FastAPI — Request Body & dependencies: fastapi.tiangolo.com/tutorial/body
  • asyncpg — INSERT ... ON CONFLICT (UPSERT): magicstack.github.io/asyncpg
  • RFC 2104 — HMAC: Keyed-Hashing for Message Authentication: datatracker.ietf.org/doc/html/rfc2104
  • PostgreSQL 16 — INSERT ON CONFLICT documentation: postgresql.org/docs/16/sql-insert

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

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

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