TuBrief
구독 채널
비디오
커뮤니티

الخطوات التقنية للتخلي عن Firebase والانتقال إلى استضافة ذاتية بتكلفة 5 دولارات شهرياً

TuBrief 편집팀
2026년 7월 14일
0
Computing/Software

원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.

العربية한국어EnglishEspañol中文हिन्दीDeutschFrançaisPortuguêsРусскийBahasa Indonesia日本語

관련 영상

بديل Firebase المجاني هذا هو مجرد ملف واحد7:49

بديل Firebase المجاني هذا هو مجرد ملف واحد

Better Stack

커뮤니티의 다른 글

사내 시스템에 llm api 붙일 때 마주하는 현실적인 한계와 대응법

2026년 9월 13일

레거시 백엔드에 GPT-6 Astra 붙일 때 예산 승인과 보안 통과를 먼저 끝내는 법이 있습니다

2026년 9월 13일

에이전트끼리 대화하다 6천만 원 청구서가 나오는 이유

2026년 9월 13일

사내 RAG 벡터 검색에 Okta 권한 필터를 직접 거는 방법

2026년 9월 13일

브라우저 에이전트에게 내 구글 계정을 통째로 넘기면 안 되는 이유

2026년 9월 12일

Apple Won the AI Race

2026년 9월 12일

댓글 (0)

Log in to leave a comment

아직 작성된 글이 없습니다

© 2026 . All rights reserved.

TuBrief
구독 채널
비디오
커뮤니티
로그인

الخطوات التقنية للتخلي عن Firebase والانتقال إلى استضافة ذاتية بتكلفة 5 دولارات شهرياً

تعتبر Firebase جذابة في البداية، حيث تتيح لك التركيز على بناء الميزات دون القلق بشأن البنية التحتية. ولكن بمجرد نمو الخدمة ولو قليلاً، تبدأ أرقام الفاتورة في الارتفاع. ومع ذلك، فإن فكرة الانتقال إلى الاستضافة الذاتية قد تكون مخيفة. ماذا لو فُقدت البيانات؟ ماذا لو توقفت الخدمة في كل مرة أقوم فيها بالنشر؟

هذه مخاوف مشروعة، ولكن هناك حل. لقد لخصت هنا الطريقة المحددة لنقل بيانات Firestore بأمان إلى PocketBase (نظام أساسي متكامل يعتمد على SQLite)، وإنشاء بيئة نشر مستمرة (Zero-downtime) باستخدام GitHub Actions و Nginx.


خط أنابيب نقل بيانات Firestore إلى PocketBase

عند نقل البيانات غير المهيكلة من Firestore إلى نموذج علاقي في PocketBase، فإن العائق الأول الذي تواجهه هو طول المعرف (Identifier). يستخدم Firestore معرف مستند بطول 20 حرفاً، بينما يفرض PocketBase افتراضياً قيد 15 حرفاً أبجدياً رقمياً. إذا تجاهلت هذا الطول وحاولت الإدخال، فستواجه خطأ validation_length_invalid.

لتجنب هذه المشكلة، يجب تقليل الطول مع الحفاظ على التفرّد. الطريقة الأكثر ضماناً هي إجراء عملية التجزئة (Hashing) لمعرف Firestore باستخدام SHA256 ثم قص أول 15 حرفاً. لا داعي للقلق بشأن التصادم؛ فحتى في مجموعات البيانات الكبيرة، فإن احتمالية تطابق قيم التجزئة المكونة من 15 حرفاً ضئيلة جداً لدرجة يمكن تجاهلها.

هيكل سكريبت النقل (Migration Script) بلغة Node.js للتعامل مع ذلك هو كما يلي، حيث يستخدم حزمتي firebase-admin و axios لجلب البيانات على دفعات من 100 سجل.

`javascript
// migration.js
const admin = require('firebase-admin');
const axios = require('axios');
const crypto = require('crypto');

admin.initializeApp({
credential: admin.credential.applicationDefault()
});

const db = admin.firestore();
const PB_URL = 'http://127.0.0.1:8090/api/collections/posts/records';

async function migrate() {
let lastDoc = null;
let hasMore = true;

while (hasMore) {
let query = db.collection('posts').orderBy('name').limit(100);
if (lastDoc) {
query = query.startAfter(lastDoc);
}

const snapshot = await query.get();
if (snapshot.empty) {
  hasMore = false;
  break;
}

for (const doc of snapshot.docs) {
  const data = doc.data();
  // تحويل معرف Firestore المكون من 20 حرفاً إلى 15 حرفاً أبجدياً رقمياً
  const newId = crypto.createHash('sha256').update(doc.id).digest('hex').substring(0, 15);
  
  try {
    await axios.post(PB_URL, {
      id: newId,
      firestore_id: doc.id, // تسجيل المعرف الأصلي للتحقق عند الحاجة
      title: data.title,
      content: data.content
    });
  } catch (err) {
    console.error(`إخفاق في النقل: ${doc.id}`, err.response?.data);
  }
}
lastDoc = snapshot.docs[snapshot.docs.length - 1];

}
}

migrate();
`

لإجراء هذه العملية دون توقف الخدمة، يجب تخصيص فترة انتقالية. ابدأ بنقل أكثر من 90% من البيانات الموجودة مسبقاً في الخلفية باستخدام هذا السكريبت. بعد ذلك، قم بنشر كود "الكتابة المزدوجة" (Dual-Write) في تطبيق العميل للكتابة في كل من Firebase و PocketBase في وقت واحد. بمجرد التأكد من تطابق البيانات، قم بتحويل نقاط نهاية القراءة إلى PocketBase وقم بإزالة كود Firebase. من وجهة نظر المستخدم، لن تتوقف الخدمة لثانية واحدة.


إعداد خادم PocketBase يعمل على مدار الساعة

PocketBase هو نظام أساسي خفيف يعمل كملف تنفيذي واحد. ومع ذلك، إذا قمت بتشغيله ببساطة على خادم Linux دون إعدادات، فلن تكون هناك طريقة لاستعادة الخدمة في حالة إغلاق العملية بسبب نقص الذاكرة غير المتوقع (OOM).

يجب عليك إعداد systemd للتأكد من إعادة تشغيل العملية تلقائياً إذا توقفت. قم بإنشاء ملف /etc/systemd/system/pocketbase.service وسجل المحتويات التالية:

`ini
[Unit]
Description=PocketBase Service
After=network.target

[Service]
Type=simple
User=pocketbase
Group=pocketbase
LimitNOFILE=65535
ExecStart=/opt/pocketbase/pocketbase serve --http="127.0.0.1:8090"
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
`

هنا، يعد إعداد LimitNOFILE=65535 مهماً جداً. عند استخدام ميزة الاشتراك في الوقت الفعلي (Websockets) التي يتميز بها PocketBase، فإنه يمنع انقطاع الاتصال عند تزاحم المستخدمين، والذي يحدث عادة بسبب تجاوز حد واصفات الملفات الافتراضي في Linux.

في الواجهة الأمامية، نضع Nginx لمعالجة شهادات SSL والوكيل العكسي (Proxy). يجب إضافة خيارات في إعداد /etc/nginx/sites-available/pocketbase لضمان عدم تأخر اتصالات البث في الوقت الفعلي:

`nginx
upstream pocketbase {
server 127.0.0.1:8090;
}

server {
server_name api.yourdomain.com;

location / {
    proxy_pass http://pocketbase;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # دعم البث في الوقت الفعلي عبر Websocket و SSE
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_buffering off;
}

}
`

الآن، بمجرد تطبيق شهادة HTTPS باستخدام أمر certbot --nginx، تكون قد انتهيت من تكوين البنية التحتية الأساسية.

من واقع الخبرة، حتى على أقل فئة VPS تحتوي على 512 ميجابايت من الذاكرة العشوائية، يمكن معالجة أكثر من 1000 طلب خفيف في الثانية بسهولة مع تعديلات بسيطة على إعدادات SQLite. لزيادة أداء SQLite عند تشغيل PocketBase، يجب تفعيل وضع WAL (Write-Ahead Log). يقوم PocketBase بتفعيل وضع WAL افتراضياً، ولكن إذا تعاملت مع SQLite مباشرة، فتأكد من إعدادات PRAGMA journal_mode=WAL; و PRAGMA synchronous=NORMAL;. ستختفي اختناقات الأداء الناتجة عن انتظار الطلبات أثناء الكتابة إلى القرص.


النسخ الاحتياطي في الوقت الفعلي باستخدام Cloudflare R2 و Litestream

أكثر خطأ يقع فيه المطورون المستقلون هو نسخ ملف قاعدة بيانات SQLite (data.db) مباشرة من خادم يعمل. هذه الطريقة تحمل مخاطر عالية بتلف البيانات أثناء النسخ.

نحن نستخدم Litestream، وهي أداة تقوم باعتراض إطارات WAL الخاصة بـ SQLite في الوقت الفعلي وترسل الأجزاء المعدلة فقط إلى سحابة التخزين. نوصي باستخدام Cloudflare R2 كمخزن للنسخ الاحتياطي، لأن رسوم النقل الصادر مجانية، لذا فإن تكلفة النسخ الاحتياطي ستكون شبه معدومة.

قم بإنشاء ملف إعداد /etc/litestream.yml كما يلي:

`yaml
dbs:

  • path: /opt/pocketbase/pb_data/data.db
    replicas:
    • type: s3
      bucket: your-r2-bucket-name
      endpoint: https://.r2.cloudflarestorage.com
      access-key-id:
      secret-access-key:

`

بمجرد الإعداد بهذه الطريقة، حتى لو تم مسح ملف قاعدة البيانات الأصلي، يمكنك استعادته إلى حالة ما قبل ثانية واحدة باستخدام أمر واحد:

bash litestream restore -if-replica-exists /opt/pocketbase/pb_data/data.db

إذا قمت بحذف البيانات عن طريق الخطأ أثناء التطوير وأردت الاستعادة إلى نقطة زمنية معينة، فيمكنك تحديد قيمة الوقت للقيام بذلك:

`bash

1. إيقاف PocketBase مؤقتاً

sudo systemctl stop pocketbase.service

2. إنشاء ملف استعادة لنقطة زمنية محددة

litestream restore -timestamp "2026-07-14T15:00:00Z" -o /tmp/recovered.db /opt/pocketbase/pb_data/data.db

3. التحقق من سلامة البيانات واستبدال الملف

sqlite3 /tmp/recovered.db "PRAGMA integrity_check;"
mv /tmp/recovered.db /opt/pocketbase/pb_data/data.db
chown -R pocketbase:pocketbase /opt/pocketbase/pb_data/

4. استئناف الخدمة

sudo systemctl start pocketbase.service
`

توفر Cloudflare R2 سعة تخزين مجانية تبلغ 10 جيجابايت شهرياً. بالإضافة إلى رصيد مجاني يصل إلى مليون طلب كتابة و10 ملايين طلب قراءة، مما يجعل تكلفة النسخ الاحتياطي تقترب من الصفر على مستوى المشاريع الفردية.


النشر المستمر باستخدام GitHub Actions و Nginx

النشر عبر Docker مريح، ولكن في خوادم VPS الصغيرة ذات ذاكرة 512 ميجابايت أو 1 جيجابايت، فإن الذاكرة التي يستهلكها Docker Daemon بحد ذاته تصبح باهظة. سنقوم بتنفيذ نشر مستمر (بدون Docker) عن طريق التبديل بين منافذ Linux systemd (Blue-Green).

نستغل ميزة SQLite التي تتيح لعمليات متعددة القراءة والكتابة في ملف واحد. سنقوم بتسجيل قالب خدمة systemd بحيث يمكن تشغيل PocketBase على المنفذ 9011 (الأزرق) والمنفذ 9012 (الأخضر) بشكل منفصل.

أولاً، مثال على سير عمل (Workflow) لإرسال الملف التنفيذي الخفيف الذي تم بناؤه بواسطة GitHub Actions إلى الخادم:

`yaml

.github/workflows/deploy.yml

name: Deploy
on:
push:
branches: [ main ]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Build
run: |
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o pocketbase main.go
- name: Transfer Binary to VPS
uses: appleboy/scp-action@master
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_KEY }}
source: "pocketbase"
target: "/srv/pocketbase/next_release"
`

بمجرد وصول الملف التنفيذي إلى الخادم، يتم تنفيذ سكريبت النشر. يتحقق السكريبت من المنفذ النشط حالياً (9011 أو 9012) ويقوم بتشغيل الإصدار الجديد على المنفذ الخامل:

`bash
#!/bin/bash

deploy_swap.sh

CURRENT_PORT=$(curl -s http://127.0.0.1:8090/api/health | jq -r '.port' 2>/dev/null || echo "9011")

if [ "$CURRENT_PORT" = "9011" ]; then
TARGET_PORT="9012"
else
TARGET_PORT="9011"
fi

تشغيل الإصدار الجديد في الخلفية على المنفذ المستهدف

sudo systemctl start pocketbase@$TARGET_PORT.service

انتظار 3 ثوانٍ للتحقق من الحالة (Health Check)

sleep 3
HEALTH_CHECK=(curl−shttp://127.0.0.1:(curl -s http://127.0.0.1:(curl−shttp://127.0.0.1:TARGET_PORT/api/health | jq -r '.status')

if [ "HEALTH_CHECK" = "OK" ]; then # تحديث إعدادات Nginx Upstream وإعادة التحميل echo "upstream pocketbase { server 127.0.0.1:TARGET_PORT; }" | sudo tee /etc/nginx/conf.d/upstream.conf
sudo systemctl reload nginx

# إيقاف عملية الإصدار السابق
sudo systemctl stop pocketbase@$CURRENT_PORT.service
echo "تم النشر بنجاح. تم التحويل إلى المنفذ $TARGET_PORT."

else
echo "فشل النشر. الإصدار الجديد لا يستجيب."
sudo systemctl stop pocketbase@$TARGET_PORT.service
exit 1
fi
`

باستخدام هذه الطريقة، يعمل النشر المستمر بشكل مثالي دون الحاجة إلى أدوات تنسيق حاويات باهظة الثمن. ففي اللحظة التي يقوم فيها Nginx بإعادة التحميل (بالملي ثانية)، ينتظر الطلبات تلقائياً ويوجهها بأمان إلى المنفذ الجديد.

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