یک میلیون بازدید صفحه در دقیقه، روی یک سرور: Statnive Live چطور برای مقیاس مهندسی شد
چطور یک باینری Go، رولآپهای ClickHouse و یک ردیاب 687 بایتی، یک میلیون بازدید صفحه در دقیقه را روی یک سرور 8 هستهای مدیریت میکنند — بدون اینکه سایتتان کند شود.
عملکرد آنالیتیکس در واقع یک مسئلهٔ سرعت سایت است
بیشتر مقالهها دربارهٔ عملکرد آنالیتیکس روی بکاند تمرکز میکنند؛ یعنی اینکه سرور چند رویداد در ثانیه را میتواند پردازش کند. اما این عددِ اشتباهی برای شروع است. عددی که صاحب سایت واقعاً بابتش هزینه میدهد این است که اسکریپت آماری شما چه بلایی سر زمان بارگذاری صفحه برای بازدیدکنندگان میآورد — و از آن مسیر، چه تأثیری روی Core Web Vitals، نرخ تبدیل و سئو میگذارد.
Core Web Vitals گوگل (در 12 مارس 2024، INP جای FID را گرفت) — یعنی LCP، INP و CLS — یک سیگنال رتبهبندی هستند. پردازش جاوااسکریپت روی موبایل تقریباً 2 تا 5 برابر کندتر از دسکتاپ است؛ یعنی یک اسکریپت آماری 50 کیلوبایتی روی دسکتاپ، روی گوشی میتواند به هزینهٔ پردازشی معادل 200 کیلوبایت تبدیل شود. اسکریپتهای آماری که رندر را مسدود میکنند، بزرگترین عامل از بینبرندهٔ عملکرد در این حوزهاند.
Statnive Live دقیقاً با در نظر گرفتن همین عدمتقارن مهندسی شده است. اعداد اصلی — یعنی 200 میلیون رویداد در روز برای هر نود، یک ردیاب 687 بایتی و تأخیر کوئری p99 زیر 500 میلیثانیه — همگی در خدمت یک هدف هستند: لایهٔ آنالیتیکس هرگز نباید دلیل کند شدن صفحهٔ پرداخت شما شود. این مطلب با ذکر مسیر فایلها توضیح میدهد که این کار چطور انجام میشود، تا بتوانید هر ادعا را خودتان راستیآزمایی کنید.
این آخرین قسمت از مجموعهٔ چهارقسمتی پیش از انتشار statnive.live ماست. هرجا ادعای قابلاندازهگیری مطرح کردهایم، فایل یا دستوری را هم میبینید که آن را اثبات میکند.
ردیاب 687 بایتی
ردیاب Statnive Live در تاریخ 8 اردیبهشت 1405 اندازهگیری شد: 1,394 بایت در حالت کمحجمشده / 687 بایت در حالت gzipشده. اینها اعداد آرمانی نیستند؛ اینها همان بایتهایی هستند که دستور go:embed در زبان Go داخل باینری حک کرده، و میتوانید آنها را در هر کلون از مخزن دوباره بهدست بیاورید:
$ 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، URLهای متنی و واردکردن از CDN. اگر یک بازنویسی بهاشتباه یک کتابخانهٔ انتقال بزرگتر را وارد کند، یا یک ویژگی جدید سراغ localStorage برود، CI آن Pull Request را رد میکند.
برای مقایسه، اسکریپت gtag.js در GA4 تقریباً 110 کیلوبایت فشرده است، و عدد منتشرشدهٔ Plausible برای همان اسکریپت 135 کیلوبایت gzipشده است. هر کدام را که در نظر بگیرید، ردیاب Statnive Live دو مرتبهٔ بزرگی کوچکتر است — یعنی هر کدام از این دو عدد را مبنا بگیرید، باز هم بیش از 50 برابر سبکتر از GA4 است.
انتقال داده با sendBeacon بهعلاوهٔ fetch keepalive انجام میشود؛ هر دو از نوع «بفرست و فراموش کن» هستند و هیچکدام رشتهٔ اصلی را مسدود نمیکنند. ساختار یک IIFE در جاوااسکریپت خالص است؛ هیچ فریمورکی در کار نیست، چون 1,394 بایت جای یک فریمورک را ندارد. ردیاب بهصورت first-party و از طریق go:embed منتشر میشود: نه CDN خارجی، نه جستجوی DNS بیرون از دامنهٔ اپراتور، و نه مدیر تگ شخص ثالث. قانون air-gap-validator در CI هر تغییری در ردیاب را که بخواهد یک ارجاع خارجی را دوباره وارد کند، رد میکند.
مسیر ورود داده — بفرست و فراموش کن، اول WAL
قرارداد صاحب سایت با ردیاب این است: «این چیز هرگز نباید صفحهٔ من را مسدود کند.» قرارداد سرور با ردیاب هم این است: «این چیز هرگز نباید رویداد شما را از دست بدهد.» خط لولهٔ ورود داده در Statnive Live طوری ساخته شده که هر دوی این قراردادها کمهزینه باشند.
هر درخواست ورود داده، پیش از اینکه هندلر پاسخ 202 بدهد، از یک Write-Ahead Log عبور میکند. هندلر منتظر fsync میماند — اما روی یک تیکر group-commit با بازهٔ 100 میلیثانیه، نه برای هر رویداد. چون fsync بهازای هر رویداد، توان عملیاتی را روی دیسک معمولی حدود 100 رویداد در ثانیه محدود میکند، در حالی که ما روی کف SaaS به حدود 7 هزار EPS پایدار نیاز داریم. این WAL همان tidwall/wal است (با مجوز MIT، vendorشده) که با NoSync: true باز میشود؛ همان تیکر 100 میلیثانیهای دوام داده را تأمین میکند. هندلر پیش از ارسال تأییدیهٔ 202، با AppendAndWait منتظر میماند. اگر sync روزی شکست بخورد، پروسه خاتمه پیدا میکند — آنالیتیکس جایی نیست که بیسروصدا تاریخچه را خراب کند.
هندلر اندازهٔ بدنهٔ درخواست را با http.MaxBytesReader در Go روی 8 کیلوبایت سقف میگذارد:
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 و هدرهای prefetch (مثل 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_visitors—ENGINE = AggregatingMergeTree() PARTITION BY toYYYYMM(hour) ORDER BY (site_id, hour)daily_pages—ORDER BY (site_id, day, pathname)daily_sources—ORDER 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 روی Nullable(Int8) عدد 2 برابر را اندازه گرفت). رولآپها بهجایش از DEFAULT '' و DEFAULT 0 استفاده میکنند که هم مسیر نوشتن و هم مسیر ادغام را سریع نگه میدارد.
اعداد
نوار اثبات منتشرشده در صفحهٔ /live چهار عدد را فهرست میکند:
- ردیاب 600 بایت gzipشده (نسخهٔ گردشدهٔ بازاریابی همان 687 بایت)
- 200 میلیون رویداد در روز برای هر نود
- <500 میلیثانیه p99
- دادهها فقط در EU/EEA
برای هرکدام یک توضیح صادقانه:
- ردیاب: در تاریخ 8 اردیبهشت 1405 اندازهگیری شد: 1,394 بایت کمحجمشده / 687 بایت gzipشده؛ بودجه 1,500 بایت / 700 بایت gzipشده، که در CI تأیید میشود.
- 200 میلیون رویداد در روز: سقف طراحی است، نه تولید اندازهگیریشده. منبع: ظرفیت کلاس Hetzner در سند داخلی پروژه شمارهٔ 19؛ کف SaaS یک Hetzner AX42 (8 هسته / 64 گیگابایت) با حاشیهٔ مازاد است. 200 میلیون در روز یعنی حدود 2,300 EPS پایدار، که کاملاً درون ظرفیت توان عملیاتی منتشرشدهٔ ClickHouse جا میگیرد (کلودفلر یک ورود داده با نرخ 11 میلیون ردیف در ثانیه را روی 36 نود اجرا میکند؛ Plausible از PostgreSQL مهاجرت کرد، چون بالای حدود 1 میلیون رویداد در روز، ClickHouse الزامی است).
- <500 میلیثانیه p99: سقف طراحی است، نه تولید اندازهگیریشده. عدد p99 تولید فاز Phase-11a پس از راهاندازی ثبتنام عمومی منتشر میشود؛ ادعای ProofStrip یک آستانهٔ دروازهٔ فارغالتحصیلی است، نه یک اندازهگیری.
- دادهها فقط در EU/EEA: پردازش در نورنبرگ آلمان روی یک Netcup VPS 2000 G12 NUE انجام میشود — به این معنا اندازهگیریشده است که یک تست یکپارچگی، باینری را زیر
iptables -P OUTPUT DROPاجرا میکند و ثابت میکند هیچ خروجی الزامیای وجود ندارد.
داشبورد درون یک بودجهٔ 16 کیلوبایتی gzipشده برای جاوااسکریپت اولیه مینشیند که size-limit آن را در برابر قطعهٔ ساختهشدهٔ index-*.js تأیید میکند. قطعهٔ تنبل نمودار روی 25 کیلوبایت محدود است، قطعههای تنبل پنل روی 10 کیلوبایت، و CSS روی 5 کیلوبایت / 3 کیلوبایت. میتوانید این دروازه را بهصورت محلی دوباره اجرا کنید:
$ npm --prefix web run bundle-gate
SLOهای ثابتهای آنالیتیکس که دروازهٔ تست تضمین میکند:
- از دست رفتن رویداد ≤ 0.05% سمت سرور / ≤ 0.5% سمت کلاینت
- دادههای تکراری ≤ 0.1%
- درستی انتساب ≥ 99.5%
- نشت رضایت / PII = 0
- سربار TTFB ≤ +10% / +25 میلیثانیه
هر آستانه انتشار را مسدود میکند، CI آن را در هر Pull Request تأیید میکند، و علاوه بر این، در هر فاز پیش از هر جابهجایی به تولید، طی یک آزمون 72 ساعتهٔ خیساندن بهعلاوهٔ ماتریس آشوب 6 سناریویی تضمین میشود. هر جهش بزرگ بعدی هر شکلی که داشته باشد، باید پیش از انتشار از این دروازهها عبور کند.
معاملهٔ صادقانه — تأخیر یکساعته
تأخیر یکساعته بخشی از Statnive Live است که بعضی خوانندهها از آن خوششان نمیآید؛ پس بیایید همان اول رویش انگشت بگذاریم. قانون معماری شمارهٔ 3 اینطور میگوید:
تأخیر یکساعته، نه بلادرنگ — این کار 98% هزینهٔ کوئری را صرفهجویی میکند. هرگز حالت بلادرنگ 5 دقیقهای نسازید.
آن «98%» در برابر یک خط لولهٔ فرضی 5 دقیقهای روی همان پشته سنجیده شده — یعنی نوشتن رولآپها را ارزان نگه میدارد، ردپای رولآپ هر سایت را زیر 100 کیلوبایت در روز برای هر سایت نگه میدارد (3 رولآپ نسخهٔ v1؛ تا 6 رولآپ در v1.1)، و کاری میکند که کوئریهای داشبورد بهجای اسکن جدولهای داغ، از تجمیعهای فشرده سرویس بگیرند. اگر آمار را یک بار در ساعت یا یک بار در روز بررسی میکنید، تأخیر یکساعته نامرئی است. اما اگر برای پایش جهش یک رویداد زنده به بازخورد زیریکدقیقه نیاز دارید، Live ابزار درستی نیست — یک محصول آنالیتیکس بلادرنگ انتخاب کنید، هزینهٔ کوئری حدود 50 برابریِ بیشتر را بپذیرید و بروید سراغ کارتان.
پنل بلادرنگ همچنان وجود دارد و فعالهای یک ساعت گذشته را از همان رولآپ hourly_visitors نشان میدهد که بقیه چیزها هم از آن میخوانند. هیچ خط لولهٔ جداگانهٔ 5 دقیقهای پشت آن نیست؛ و این عمدی است. این معامله، نقطهٔ کانونی معماری است، نه یک هزینهٔ پنهان.
این برای سایت شما چه معنایی دارد
معماری بالا همان چیزی است که داستان صاحب سایت را بیحادثه میکند:
ردیاب نمیتواند صفحهٔ پرداخت شما را مسدود کند. sendBeacon بهعلاوهٔ fetch keepalive از نوع «بفرست و فراموش کن» است — حتی اگر مبدأ آنالیتیکس آفلاین باشد، صفحه باز هم پیمایش میشود و مشتری باز هم پرداخت میکند. خودتان با از کار انداختن نقطهٔ پایانی آنالیتیکس و تماشای کارکرد صفحه آن را راستیآزمایی کنید.
تأثیر روی Core Web Vitals به 687 بایت بهعلاوهٔ یک IIFE درونخطی محدود است. این عدد کاملاً زیر هر آستانهٔ مستندشدهٔ «مسدودکنندهٔ رندر» در این حوزه است. ما تأثیر LCP ردیابِ افزونهٔ وردپرس را در مطلبی جداگانه سنجیدیم؛ اما هنوز برای ردیاب Live یک اختلاف LCP اندازهگیریشده منتشر نکردهایم و چیزی را که نداریم ادعا نمیکنیم.
سربار سمت سرور روی یک مبدأ جداگانه زندگی میکند. ردیاب به یک نقطهٔ پایانی Statnive Live ارسال میکند، نه به اپلیکیشن وب شما. تیکر fsync با بازهٔ 100 میلیثانیه روی WAL، حدود 7 هزار EPS پایدار روی کف SaaS را برایمان میخرد — و هیچچیز در این میان با بودجهٔ درخواست PHP، Node یا Rails اپلیکیشن شما رقابت نمیکند.
پرسشهای رایج
آیا تا 10 میلیون بازدید صفحه در روز مقیاسپذیر است؟
بله. 10 میلیون بازدید در روز تقریباً برابر با 115 رویداد در ثانیهٔ پایدار است — یعنی کاملاً زیر سقف طراحی 200 میلیون در روز (حدود 2,300 EPS پایدار) روی یک جعبهٔ تکهستهای 8 هسته / 32 گیگابایت. اگر روی یک نود از این عدد فراتر رفتید، مهاجرتها همین حالا از Go-templateهای {{if .Cluster}} استفاده میکنند، بنابراین گذار از تکنود به حالت توزیعشده فقط یک تغییر تنظیمات است، نه ساختن دوبارهٔ پلتفرم.
آیا میتوانم آن را روی هاست اشتراکی اجرا کنم؟
نه. ClickHouse به یک سرور واقعی نیاز دارد (حداقل 8 هسته / 32 گیگابایت). برای هاست اشتراکی، افزونهٔ وردپرس پاسخ درست است — چون داده را در MySQL/MariaDB موجود شما ذخیره میکند و هیچ سطح عملیاتی جدیدی اضافه نمیکند.
این در مقایسه با اسکریپت 110 کیلوبایتی GA4 چطور است؟
اسکریپت gtag.js در GA4 بسته به نسخهٔ بار داده، جایی بین 110 کیلوبایت فشرده (Stape) و 135 کیلوبایت gzipشده (Plausible) قرار دارد. ردیاب Statnive Live برابر 687 بایت gzipشده است. بیش از 50 برابر کوچکتر، فارغ از اینکه کدام عدد GA4 را انتخاب کنید. در اینجا اختلاف زمان پردازش روی موبایل حرف اول را میزند؛ روی یک گوشی اندروید میانرده، ردیاب در میان نویز محو میشود.
پلن SaaS روی چه سختافزاری اجرا میشود؟
کف SaaS منتشرشده یک Hetzner AX42 (8 هسته / 64 گیگابایت) است. VPS تولید فعال SaaS هم یک Netcup VPS 2000 G12 NUE در نورنبرگ آلمان است — پردازش فقط در EU/EEA، بدون انتقال فصل پنجم. مقالهٔ 3 سمت قراردادی را پوشش میدهد و مقالهٔ 2 سمت مقرراتی را.
بودجهٔ اندازه چطور تضمین میشود؟
دو دروازهٔ CI روی هر Pull Request اجرا میشوند. (الف) go test ./internal/tracker/... بودجهٔ ردیاب 1,500 بایت / 700 بایت gzipشده را بهعلاوهٔ رد توکنهای ممنوع تضمین میکند. (ب) 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
همین دستورها روی هر Pull Request در GitHub Actions اجرا میشوند. هیچ «بنچمارک انتشار» جداگانهای وجود ندارد — اگر یک Pull Request بودجهای را بشکند، ادغام نمیشود. اگر یک انتشار زیر آزمون 72 ساعتهٔ خیساندن یک SLO را بشکند، منتشر نمیشود. مهندسی برای یک میلیون بازدید صفحه در دقیقه، از نزدیک اصلاً پرزرقوبرق به نظر نمیرسد؛ بیشترش دروازههای CI، فیلترهای رد سریع و جدولهای رولآپ است، و خیلی کم از آن قهرمانانه است.
نتیجهٔ نهایی
پشتهٔ آنالیتیکسی که در سال 2026 منتشر میکنید، بیشتر بر اساس کاری که با سایت شما میکند سنجیده میشود، نه کاری که برای سایت شما میکند. انتخابهای طراحی Statnive Live این معامله را شفاف میکنند: یک ردیاب 687 بایتی first-party، یک خط لولهٔ رولآپ با تأخیر یکساعته که 98% از هزینهٔ کوئریِ موردنیاز حالت بلادرنگ را صرفهجویی میکند، و یک مجموعه SLO تأییدشده در CI که پیش از اینکه به دست شما برسد، یک انتشار را مسدود میکند. ما عدد p99 تولید-اندازهگیریشدهای را که هنوز منتشر نکردهایم ادعا نمیکنیم، و اختلاف LCPای را که هنوز بنچمارک نگرفتهایم ادعا نمیکنیم — اما هر عدد بالا به مسیر فایلی میرسد که میتوانید آن را راستیآزمایی کنید.
Statnive Live بهزودی در statnive.com/live عرضه میشود. این مجموعهٔ چهارقسمتی یک معرفی آرام و پلهپله است: افزونهٔ وردپرس در برابر Statnive Live برای درخت تصمیم، آنالیتیکس منطبق با GDPR در سال 2026 برای سمت مقرراتی، مالکیت دادههای آنالیتیکس خودتان برای سمت شکل استقرار، و این مطلب برای سمت مهندسی. صفحهٔ ویژگیها همان معرفی یکصفحهای است. اگر یک عدد در این مطلب اشتباه از آب درآمد، برایم بنویسید — هر ادعا یک فایل یا یک دستور پشتش دارد، و ترجیح میدهیم یکی را اصلاح کنیم تا اینکه یک نیمهحقیقت صیقلخورده منتشر کنیم.