Tips & Tricks Statnive Live · Parhum Khoshbakht

مليون مشاهدة صفحة في الدقيقة على خادم واحد: كيف صُمِّم Statnive Live للتوسع

كيف يتعامل ملف Go ثنائي وجداول تجميع ClickHouse ومتتبع بحجم 687 بايت مع مليون مشاهدة صفحة في الدقيقة على خادم واحد بثمانية أنوية — دون إبطاء موقعك.

أداء التحليلات مشكلة سرعة الموقع

تركز معظم المقالات المتعلقة بأداء التحليلات على الجانب الخلفي — كم من الأحداث في الثانية يستطيع الخادم معالجتها. هذا ليس الرقم الصحيح للبدء به. الرقم الذي يدفع ثمنه صاحب الموقع فعلاً هو ما تفعله سكريبت التحليلات بأوقات تحميل الصفحة لدى زوارك — ومن خلال ذلك تأثيرها على Core Web Vitals ومعدل التحويل وتحسين محركات البحث.

Core Web Vitals من Google (استُبدل INP بـ FID في 12 مارس 2024) — LCP وINP وCLS — هي إشارة ترتيب. تحليل JavaScript على الجوال أبطأ بنحو 2–5 مرات مقارنة بسطح المكتب، مما يعني أن سكريبت تحليلات بحجم 50 KB على سطح المكتب قد يصبح تكلفة تحليل تعادل 200 KB على الهاتف. سكريبتات التحليلات التي تحجب العرض هي القاتل الأكبر للأداء في هذا المجال.

صُمِّم Statnive Live مع وضع هذا التفاوت في الاعتبار. الأرقام الرئيسية — 200 مليون حدث في اليوم لكل عقدة، ومتتبع بحجم 687 بايت، وزمن استجابة p99 أقل من 500 ملي ثانية — كلها في خدمة هدف واحد: لا تصبح طبقة التحليلات السبب في إبطاء عملية الدفع لديك. هذا المنشور يشرح الكيفية مع مسارات الملفات حتى تتحقق من كل ادعاء.

هذا هو الجزء الختامي في سلسلتنا الرباعية لإطلاق statnive.live. عند تقديم أي ادعاء قابل للقياس، ستجد الملف أو الأمر الذي يثبته.

المتتبع بحجم 687 بايت

قيس متتبع Statnive Live بـ 1,394 بايت مضغوطاً / 687 بايت بعد gzip في 28 أبريل 2026. هذه ليست أرقاماً افتراضية — إنها البايتات التي حفرتها توجيهة go:embed في الملف الثنائي، ويمكنك إعادة اشتقاقها في أي نسخة مستنسخة من المستودع:

$ wc -c internal/tracker/dist/tracker.js
    1394 internal/tracker/dist/tracker.js

$ gzip -9 -c internal/tracker/dist/tracker.js | wc -c
     687

الأرقام لا تتغير، لأن الملف مضمّن عبر go:embed في الملف الثنائي — لا يمكنك شحن متتبع مختلف عن الموجود في مستودعك دون إعادة البناء. ولا يمكنها الانتفاخ دون أن يُلاحَظ ذلك: اختبار Go في internal/tracker/tracker_test.go يفرض الحد الأقصى عند 1,500 بايت مضغوطاً / 700 بايت بعد gzip، ويفشل البناء إذا تجاوز أيٌّ من العتبتين:

const (
    maxMinifiedBytes = 1500
    maxGzippedBytes  = 700
)

يحظر الاختبار ذاته الشكل الكامل للمتتبع غير التافه — XMLHttpRequest وlocalStorage وsessionStorage وindexedDB وdocument.cookie والـ URLs بنص واضح وواردات CDN — عبر مطابقة نصية بحثاً داخل الحزمة المضمّنة. إذا سحب إعادة هيكلة مكتبة نقل أكبر عن طريق الخطأ، أو احتاج ميزة جديدة إلى localStorage، رفض CI طلب السحب.

للمقارنة، سكريبت gtag.js الخاص بـ GA4 يبلغ حجمه تقريباً 110 KB مضغوطاً، والرقم المنشور من Plausible هو 135 KB بعد gzip للسكريبت ذاتها. بصرف النظر عن الرقم الذي تختاره: متتبع Statnive Live أصغر بمرتبتين من حيث الحجم — أخف بأكثر من 50 مرة من GA4 بغض النظر عن أي رقم لـ GA4 تستند إليه.

وسيلة النقل هي sendBeacon بالإضافة إلى fetch keepalive — كلاهما يعمل على مبدأ «أطلق وانسَ»، ولا يحجب أيٌّ منهما الخيط الرئيسي. البنية هي IIFE بلغة JavaScript الصرفة؛ لا إطار عمل، لأن 1,394 بايت لا تتسع لواحد. يُشحن المتتبع بصورة first-party عبر go:embed: لا CDN خارجي، ولا بحث DNS خارج نطاق المشغّل، ولا مدير علامات من طرف ثالث. تقوم قاعدة air-gap-validator في CI برفض أي تغيير في المتتبع من شأنه إعادة إدخال مرجع خارجي.

مسار الاستيعاب — أطلق وانسَ، WAL أولاً

عقد صاحب الموقع مع المتتبع هو «لا ينبغي أن يحجب هذا صفحتي». وعقد الخادم مع المتتبع هو «لا ينبغي أن يضيع هذا الحدث». تُبنى خطوط أنابيب استيعاب Statnive Live حول جعل العقدين غير مكلفين.

يمر كل طلب استيعاب عبر Write-Ahead Log قبل أن يرد المعالج بـ 202. ينتظر المعالج إتمام fsync — لكن على دورة تجميع بـ 100 ملي ثانية، لا بحدث واحد في المرة، لأن fsync لكل حدث سيحصر الإنتاجية في ~100 حدث/ثانية على القرص العادي وما نحتاجه هو ~7 آلاف حدث في الثانية استمراراً على أدنى مستوى لـ SaaS. WAL هو tidwall/wal (مرخص MIT، موردَّد)، مفتوح مع NoSync: true؛ ودورة الـ 100 ملي ثانية تتولى المتانة. ينتظر المعالج عبر AppendAndWait قبل إرسال إقرار 202. إذا فشلت المزامنة في أي وقت، تتوقف العملية — التحليلات ليست المكان المناسب للإفساد الصامت للسجل.

يحد المعالج أجسام الطلبات عند 8 KB عبر http.MaxBytesReader في Go:

const (
    maxBodyBytes  = 8 * 1024  // 8 KB MaxBytesReader
    maxArrayItems = 10        // batch at most 10 events per request
    uaMinLen      = 16
    uaMaxLen      = 500
)

قبل WAL، تسقط بوابة الرفض السريع القمامة الواضحة عند HTTP 204 — طول User-Agent خارج نطاق 16–500، وUA غير ASCII، وIP كـ UA، وUUID كـ UA، وترويسات الجلب المسبق (X-Purpose وX-Moz). هذه الطلبات لا تصل أبداً إلى الإثراء ولا إلى WAL ولا إلى جداول التجميع. الإدخال غير المتزامن في ClickHouse موجود، لكنه مقيد بنقطة نهاية /ingest-fallback المنفصلة — وليس على مسار /api/event الساخن.

تحديد المعدل واعٍ بـ CGNAT: تحصل الطلبات من ASN لشبكات المحمول على مفتاح مركّب (ip, site_id) بـ 1 ألف طلب/ثانية مستمراً / 2 آلاف فورة، بينما يقع الآخرون تحت حد 100 طلب/ثانية لكل IP. يمنع حد عالمي لكل site_id مقداره 25 ألف طلب/ثانية عميلاً واحداً من إشباع المضيف. الوعي بـ CGNAT مهم لأن بوابة شبكة الهواتف قد تقع خلف IP واحد — وحد ساذج لكل IP سيحجب آلاف الزوار الشرعيين على الشبكة ذاتها.

عنوان IP الخام لا يُحفظ أبداً. يدخل خط الأنابيب فقط للبحث في GeoIP، ثم يُتخلص منه قبل أن يرى كاتب الدفعات الصف. سجل التدقيق خالٍ من IP أيضاً بحكم التصميم — لا يزال محدد المعدل يستخدم IP لقرار التحديد، لكن تسلسل سجل التدقيق يحذفه. تفرض قاعدة gdpr-code-review ذلك في CI.

مسار الاستعلام — ثلاثة جداول تجميع وHyperLogLog

لوحة التحكم لا تستعلم أبداً عن الأحداث الخام. تأتي جميع قراءات لوحة التحكم من جداول التجميع — هذه هي قاعدة البنية رقم 1، مفروضة بنقطة اختناق في CI. جدول events_raw للكتابة فقط، باستثناء نوافذ القمع التي تستدعي windowFunnel() بنتيجة مخزنة مؤقتاً لمدة ساعة.

جداول التجميع الثلاثة في الإصدار v1 هي مشاهدات AggregatingMergeTree، جميعها مفهرسة أولاً بـ site_id:

  • hourly_visitorsENGINE = AggregatingMergeTree() PARTITION BY toYYYYMM(hour) ORDER BY (site_id, hour)
  • daily_pagesORDER BY (site_id, day, pathname)
  • daily_sourcesORDER BY (site_id, day, channel, referrer_name, utm_source, utm_medium)

عدد الزوار الفريدين يُحسب عبر HyperLogLog باستخدام AggregateFunction(uniqCombined64, FixedString(16)) — خطأ قدره ~0.5%، وذاكرة تحت الخطي. FixedString(16) هو تجزئة BLAKE3-128 مبتورة إلى 16 بايت؛ والهوية هي BLAKE3(daily_salt || identity_input)، مع ملح يومي مشتق من HMAC(master_secret, site_id || YYYY-MM-DD)، يُدار يومياً ولا يُحفظ أبداً. الزائر ذاته، تجزئة مختلفة كل يوم — وجداول التجميع تحمل فقط حالة التجزئة، ليس المدخل.

كل استعلام للوحة التحكم يمر عبر مساعد واحد:

// whereTimeAndTenant emits the WHERE clause every read query MUST start
// with: site_id = ? AND <timeColumn> >= ? AND <timeColumn> < ?.
// site_id is the first WHERE term so the (site_id, …) ORDER BY prefix
// can prune partitions cleanly.
func whereTimeAndTenant(f *Filter, timeColumn string) (string, []any) {
    clause := fmt.Sprintf("WHERE site_id = ? AND %s >= ? AND %s < ?",
        timeColumn, timeColumn)
    return clause, []any{f.SiteID, f.From, f.To}
}

قاعدة CI ترفض أي استعلام جديد يتجاوز whereTimeAndTenant أو لا يبدأ بـ WHERE site_id = ?. يبدو هذا دقيقاً؛ لكنه في الواقع الفرق بين أقسام محددة بنظافة وبين ClickHouse متعدد المستأجرين يفحص بيانات الجميع في كل تحديث للوحة التحكم.

Nullable(...) محظور في أعمدة التحليلات — تكلفته المقاسة 10–200% على التجميعات (وثيقة المشروع 20 رصدت 2× على Nullable(Int8)). تستخدم جداول التجميع DEFAULT '' وDEFAULT 0 بدلاً من ذلك، مما يبقي كلاً من مسارَي الكتابة والدمج سريعَين.

الأرقام

شريط الإثبات المنشور على صفحة /live يسرد أربعة:

  • متتبع بحجم 600 B مضغوطاً (النسخة التسويقية المقرّبة لـ 687 B)
  • 200 مليون حدث/يوم لكل عقدة
  • <500 ملي ثانية p99
  • بيانات حصراً في EU/EEA

تعليقات صادقة على كل منها:

  • المتتبع: مقيس بـ 1,394 B min / 687 B gz في 28 أبريل 2026؛ الحد الأقصى 1,500 B / 700 B gz، مؤكَّد في CI.
  • 200 مليون حدث/يوم: سقف تصميم، لا إنتاج مقيس. المصدر: الظرف المحدد بفئة Hetzner في وثيقة المشروع 19؛ أدنى مستوى لـ SaaS هو Hetzner AX42 (8 أنوية / 64 GB) مع هامش. 200 مليون/يوم = ~2,300 حدث/ثانية استمراراً، داخل ظرف الإنتاجية المنشور لـ ClickHouse (Cloudflare تشغّل استيعاباً بـ 11 مليون صف/ثانية عبر 36 عقدة؛ Plausible انتقلت من PostgreSQL لأن ClickHouse إلزامي فوق ~مليون حدث/يوم).
  • <500 ملي ثانية p99: سقف تصميم، لا إنتاج مقيس. p99 الإنتاجية للمرحلة 11a ستُنشر بعد إطلاق الاشتراك العام؛ ادعاء ProofStrip هو عتبة تخرج، لا قياس.
  • بيانات حصراً في EU/EEA: معالجة في نورنبرغ، ألمانيا على Netcup VPS 2000 G12 NUE — مقيسة بمعنى وجود اختبار تكامل يشغّل الملف الثنائي تحت iptables -P OUTPUT DROP ويثبت عدم وجود خروج مطلوب.

تقع لوحة التحكم ضمن حد أولي بـ 16 KB JavaScript مضغوطاً، مؤكَّد عبر size-limit مقابل الجزء المبني index-*.js. جزء المخطط الكسول محدود بـ 25 KB، وأجزاء اللوحات الكسولة بـ 10 KB، وCSS بـ 5 KB / 3 KB. يمكنك تشغيل البوابة محلياً:

$ npm --prefix web run bundle-gate

مستويات الخدمة لأحداث التحليلات التي تفرضها بوابة الاختبار:

  • فقدان الأحداث ≤ 0.05% من الخادم / ≤ 0.5% من العميل
  • التكرارات ≤ 0.1%
  • دقة الإسناد ≥ 99.5%
  • تسريب الموافقة / البيانات الشخصية = 0
  • تأخر TTFB ≤ +10% / +25 ملي ثانية

كل عتبة تحجب الإصدار، مؤكَّدة في كل طلب سحب بواسطة CI، وتُفرض إضافياً خلال نقع 72 ساعة لكل مرحلة ومصفوفة فوضى من 6 سيناريوهات قبل أي تحويل إلى الإنتاج. مهما بدا التحديث القادم، فلا بد من اجتياز هذه البوابات قبل الشحن.

المقايضة الصادقة — تأخير ساعة واحدة

تأخير الساعة هو الجزء من Statnive Live الذي لن يُعجب بعض القراء، لذا دعنا نُسمّيه صراحة. قاعدة البنية رقم 3 تقرأ:

تأخير ساعة واحدة، لا وقت فعلي — يوفر 98% من تكلفة الاستعلام. لا تبنِ خطاً بخمس دقائق في الوقت الفعلي.

«98%» مرتبطة بخط أنابيب افتراضي بخمس دقائق على المكدس ذاته — إبقاء كتابات جداول التجميع رخيصة، وإبقاء بصمة جداول التجميع لكل موقع أقل من 100 KB/يوم/موقع (3 جداول تجميع في v1؛ حتى 6 في v1.1)، وإبقاء استعلامات لوحة التحكم تُخدَّم من تجميعات مضغوطة بدلاً من فحص الجداول الساخنة. إذا كنت تتحقق من التحليلات مرة في الساعة أو مرة في اليوم، فإن تأخير الساعة غير محسوس. إذا كنت بحاجة إلى تغذية راجعة دون دقيقة لمراقبة ارتفاع حدث حي، فـ Live ليس الأداة المناسبة — اختر منتج تحليلات في الوقت الفعلي، وتقبّل التكلفة الأعلى بـ ~50 مرة للاستعلام، وامضِ قُدُماً.

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

ما يعنيه هذا لموقعك

البنية أعلاه هي ما يجعل قصة صاحب الموقع هادئة:

المتتبع لا يمكنه حجب عملية الدفع. sendBeacon بالإضافة إلى fetch keepalive هو «أطلق وانسَ» — حتى إذا كانت نقطة التحليلات غير متصلة بالشبكة، ستنتقل الصفحة ويكمل العميل الدفع. تحقق من ذلك بإيقاف نقطة نهاية التحليلات ومشاهدة الصفحة تعمل.

تأثير Core Web Vitals محدود بـ 687 بايت بالإضافة إلى IIFE مضمّن. هذا أقل بكثير من أي عتبة موثقة «لحجب العرض» في هذا المجال. قسنا تأثير متتبع إضافة WordPress على LCP في منشور منفصل؛ لم ننشر بعد قياساً لفرق LCP لمتتبع Live، ولن ندّعي ما لم نقسه.

العبء على الخادم يقع على نقطة أصل منفصلة. يرسل المتتبع إلى نقطة نهاية Statnive Live، لا إلى تطبيقك على الويب. دورة WAL fsync بـ 100 ملي ثانية توفر ~7 آلاف حدث/ثانية استمراراً على أدنى مستوى SaaS — لا شيء في ذلك ينافس ميزانية طلبات PHP أو Node أو Rails لتطبيقك.

الأسئلة الشائعة

هل سيتوسع لـ 10 ملايين مشاهدة صفحة في اليوم؟

نعم. 10 ملايين مشاهدة/يوم تعادل تقريباً 115 حدثاً/ثانية استمراراً — أقل بكثير من سقف التصميم بـ 200 مليون/يوم (~2,300 حدث/ثانية استمراراً) على صندوق واحد بـ 8 أنوية / 32 GB. إذا تجاوزت ذلك على عقدة واحدة، فإن الترحيلات تستخدم بالفعل قوالب {{if .Cluster}} في Go لكي يكون الانتقال من عقدة واحدة إلى Distributed مجرد تغيير في الإعدادات، لا إعادة منصة.

هل يمكنني تشغيله على استضافة مشتركة؟

لا. يحتاج ClickHouse إلى خادم حقيقي (8 أنوية / 32 GB كحد أدنى). للاستضافة المشتركة، إضافة WordPress هي الإجابة الصحيحة — تخزّن في MySQL/MariaDB الموجودة لديك ولا تضيف أي سطح تشغيلي جديد.

كيف تقارن بـ سكريبت GA4 البالغة 110 KB؟

سكريبت gtag.js الخاصة بـ GA4 تتراوح بين 110 KB مضغوطة (Stape) و135 KB بعد gzip (Plausible) حسب نسخة الحمولة. متتبع Statnive Live هو 687 B بعد gzip. أصغر بأكثر من 50 مرة، بصرف النظر عن رقم GA4 الذي تختاره. يسيطر الفرق في وقت التحليل على الجوال؛ على هاتف Android متوسط المستوى، يختفي المتتبع في الضوضاء.

ما الأجهزة التي تعمل عليها خطة SaaS؟

أدنى مستوى SaaS المنشور هو Hetzner AX42 (8 أنوية / 64 GB). VPS الإنتاجي النشط لـ SaaS هو Netcup VPS 2000 G12 NUE في نورنبرغ، ألمانيا — معالجة حصراً في EU/EEA، ولا نقل وفق الفصل الخامس. المقال الثالث يغطي الجانب التعاقدي؛ المقال الثاني يغطي الجانب التنظيمي.

كيف يُفرض حد الحجم؟

تعمل بوابتا CI على كل طلب سحب. (أ) go test ./internal/tracker/... تفرض حد المتتبع بـ 1,500 B / 700 B gz بالإضافة إلى رفض الرمز المحظور. (ب) npm --prefix web run bundle-gate تشغّل size-limit مقابل جميع مداخل لوحة التحكم الخمسة في web/.size-limit.json. كلاهما جزء من make ci-local، الذي تشغله GitHub Actions من البداية إلى النهاية مقابل ClickHouse حقيقي في 8–12 دقيقة.

أظهر الإثباتات

كل ادعاء أعلاه قابل للإعادة من نسخة مستنسخة من statnive-live:

# Tracker size budget — 1,500 B min / 700 B gz, asserted by Go test
$ wc -c internal/tracker/dist/tracker.js
    1394 internal/tracker/dist/tracker.js
$ gzip -9 -c internal/tracker/dist/tracker.js | wc -c
     687
$ go test ./internal/tracker/...
ok      github.com/statnive/statnive.live/internal/tracker      0.32s

# Dashboard bundle budget — five size-limit entries
$ npm --prefix web run bundle-gate

# Whole gate — ClickHouse + integration + smoke + e2e (~8–12 min)
$ make ci-local

الأوامر ذاتها تعمل في GitHub Actions على كل طلب سحب. لا يوجد «معيار إصدار» منفصل — إذا كسر طلب سحب حداً ما، لا يُدمج. وإذا كسر إصدار مستوى خدمة تحت نقع 72 ساعة، لا يُشحن. هندسة مليون مشاهدة صفحة في الدقيقة تبدو عادية في الواقع؛ في معظمها هي بوابات CI وفلاتر رفض سريع وجداول تجميع، وليس فيها شيء بطولي يُذكر.

الخلاصة

المنظومة التحليلية التي تشحنها في 2026 يُحكَم عليها أساساً بما تفعله لـ موقعك، لا بما تفعله من أجله. خيارات تصميم Statnive Live تجعل المقايضة صريحة: متتبع first-party بحجم 687 بايت، وخط أنابيب تجميع بتأخير ساعة يوفر 98% من تكلفة الاستعلام التي كان الوقت الفعلي سيطالب بها، ومجموعة مستويات خدمة مؤكَّدة بـ CI تحجب إصداراً قبل أن يصلك. لا ندّعي أرقام p99 قياسية من الإنتاج لم نشحنها بعد، ولا ندّعي فروق LCP لم نقِسها بعد — لكن كل رقم أعلاه يرجع إلى مسار ملف يمكنك التحقق منه.

Statnive Live قادم قريباً على ar.statnive.com/live. هذه السلسلة الرباعية هي المقدمة التدريجية: إضافة WP مقابل Statnive Live لشجرة القرار، تحليلات متوافقة مع GDPR في 2026 للجانب التنظيمي، امتلك بيانات تحليلاتك للجانب الخاص بشكل النشر، وهذا المنشور للجانب الهندسي. صفحة الميزات هي الصفحة الواحدة. إذا تبيّن أن أي رقم في هذا المنشور خاطئ، راسلني — كل ادعاء له ملف أو أمر خلفه، ونحن نفضّل تصحيح رقم على شحن نصف حقيقة منقّحة.

Get Statnive Free