پرش به محتویات

داده و چندمستاجری

هر Building مرز Tenant است، اما Account سراسری باقی می‌ماند تا یک فرد بتواند با یک شماره موبایل در چند ساختمان و چند نقش عضو باشد. جداسازی Tenant در Application اجباری و Row-Level Security در PostgreSQL لایه دفاعی دوم است.

مدل Tenant

مفهوم دامنه
accounts سراسری؛ یک هویت برای هر موبایل نرمال‌شده
buildings ریشه Tenant
units وابسته به building_id
memberships پیوند Account با Building و در صورت نیاز Unit
roles و role_assignments Template سراسری یا نمونه Scoped به Building
داده عملیاتی و مالی همیشه وابسته به building_id
تنظیمات پلتفرم و Provider سراسری و فقط برای Platform Admin

قید Unique برای داده Tenant باید building_id را شامل شود؛ برای نمونه شماره واحد با (building_id, normalized_number) یکتا است، نه در کل سامانه.

قواعد Isolation

  • building_id از Path، Session فعال یا Resource والد استخراج و با Membership معتبر تطبیق داده می‌شود؛ مقدار Client به‌تنهایی قابل اعتماد نیست.
  • هر Use Case پیش از دسترسی به داده، Actor، Building فعال و Permission لازم را می‌سازد.
  • Repositoryهای Tenant-scoped بدون building_id در Signature مجاز نیستند.
  • Query سراسری فقط در Package و Role جداگانه Platform Admin اجرا می‌شود.
  • Application Role پایگاه داده مالک Table، Superuser یا دارای BYPASSRLS نیست.
  • روی Tableهای Tenant-scoped، RLS فعال و در صورت تناسب FORCE ROW LEVEL SECURITY می‌شود.
  • Policy به‌شکل Deny-by-default است و هم USING و هم WITH CHECK را پوشش می‌دهد.

Tenant Context با SET LOCAL app.current_building_id = ... فقط داخل Transaction تنظیم می‌شود تا Connection Pool باعث نشت Context بین درخواست‌ها نشود. هر Query Tenant-scoped داخل همان Transaction اجرا می‌شود. آزمون Integration باید تلاش برای خواندن و نوشتن Cross-tenant را رد کند.

RLS جای Authorization دامنه‌ای را نمی‌گیرد؛ فقط مانع نهایی دسترسی اشتباه به Row Tenant دیگر است.

قرارداد داده

  • شناسه عمومی UUIDv7 است تا قابلیت حدس کم و Locality ایندکس بهتر از UUID تصادفی داشته باشد. در PostgreSQL 18 تابع uuidv7() مرجع تولید است.
  • زمان با timestamptz و UTC ذخیره می‌شود. تقویم شمسی فقط در Client یا Presentation ساخته می‌شود.
  • مبلغ با bigint و واحد ریال ذخیره می‌شود؛ float برای پول ممنوع است.
  • نرخ یا درصد با عدد صحیح مقیاس‌دار یا numeric(precision, scale) و Precision صریح نگهداری می‌شود.
  • شماره موبایل ایران پس از Validation در قالب E.164 مانند +989121234567 ذخیره می‌شود.
  • Statusهای دامنه با Check Constraint یا Lookup کنترل‌شده و Transition در Domain محافظت می‌شوند.
  • JSONB فقط برای داده واقعا نیمه‌ساخت‌یافته، Snapshot قرارداد یا metadata کم‌Query استفاده می‌شود؛ جای مدل رابطه‌ای را نمی‌گیرد.
  • حذف نرم پیش‌فرض نیست. برای تاریخچه کسب‌وکار از Status یا رکورد immutable استفاده می‌شود و deleted_at فقط با نیاز مشخص اضافه می‌شود.

Schema و Constraint

  • نام Table، Column، Index و Constraint به snake_case است.
  • NOT NULL، Foreign Key، Unique و Check Constraint نزدیک داده تعریف می‌شوند؛ Validation برنامه به‌تنهایی کافی نیست.
  • رفتار ON DELETE صریح است؛ Cascade برای داده مالی، Audit و Membership بدون بررسی ممنوع است.
  • created_at و updated_at برای Entityهای قابل تغییر وجود دارد؛ زمان کسب‌وکار مانند paid_at جای آن‌ها را نمی‌گیرد.
  • Version عددی برای Optimistic Concurrency روی Entityهای مستعد ویرایش هم‌زمان استفاده می‌شود.
  • Audit مالی و رویدادهای حساس Append-only هستند و اصلاح با رکورد جبرانی انجام می‌شود.

Query و Index

  • SQL کنار Feature و به‌صورت ثابت‌ها و تابع‌های Query دستی روی pgx/v5 نگهداری می‌شود. پارامتر، Row و Scan باید صریح، کوچک و قابل Review باشند؛ Code Generator و ORM عمومی مجاز نیست.
  • SELECT * در Query محصول ممنوع است؛ ستون‌های لازم صریح انتخاب می‌شوند.
  • هر Index باید Query یا Constraint مشخص داشته باشد و با EXPLAIN (ANALYZE, BUFFERS) روی داده نزدیک Production ارزیابی شود.
  • Indexهای Tenant-scoped معمولا با building_id آغاز می‌شوند و ترتیب ستون‌ها بر اساس Filter، Sort و Selectivity واقعی تعیین می‌شود.
  • Pagination بر پایه Cursor و Sort پایدار است؛ Offset بزرگ برای Feed و لیست‌های در حال رشد مجاز نیست.
  • Query بیش از 100ms در Production به‌عنوان Slow Query ثبت و بررسی می‌شود؛ متن کامل دارای PII Log نمی‌شود.
  • pg_stat_statements در Production فعال و Queryهای پرتکرار، پرهزینه و دارای Variance بالا پایش می‌شوند.

Partitioning فقط وقتی حجم، Retention یا Maintenance آن را با Metric توجیه کند اضافه می‌شود. ایجاد Partition زودهنگام برای Tableهای کوچک ممنوع است.

Connection Pool و Transaction

  • Pool با pgxpool ساخته می‌شود و مجموع Connection تمام Replicaها از ظرفیت PostgreSQL کمتر می‌ماند.
  • مقدارهای MaxConns، MinConns، Lifetime و Idle Time بر اساس Load Test و ظرفیت Server تنظیم می‌شوند، نه Copy/Paste.
  • Transaction کوتاه است و در زمان تماس شبکه، Upload یا انتظار کاربر باز نمی‌ماند.
  • Isolation پیش‌فرض READ COMMITTED است؛ REPEATABLE READ یا SERIALIZABLE فقط برای Invariant مشخص و با Retry خطای Serialization استفاده می‌شود.
  • Lock با ترتیب ثابت گرفته می‌شود و Deadlock یا Serialization Failure با Retry محدود و Jitter مدیریت می‌شود.
  • Side Effect بیرونی با Transactional Outbox از Commit داده جدا می‌شود.

Migration

  • Migrationها SQL، ترتیبی، immutable و در db/migrations نگهداری می‌شوند.
  • فایل Merge‌شده و اجراشده ویرایش نمی‌شود؛ اصلاح آن Migration جدید است.
  • تغییر سازگار با راهبرد Expand/Contract انجام می‌شود: افزودن ساختار سازگار، Deploy کد Dual-compatible، Backfill، Cutover و سپس حذف قدیمی در Release جدا.
  • تغییر مخرب، Rewrite بزرگ Table یا ساخت Index بدون Concurrent Strategy باید Lock Impact، زمان، Rollback و فضای موقت را پیش از Production مشخص کند.
  • Migration در Pipeline یک‌بار و پیش از تغییر Traffic اجرا می‌شود؛ Replicaهای App Migration اجرا نمی‌کنند.
  • Down Migration برای داده مخرب، تضمین بازیابی نیست. Rollback کد باید با Schema جدید سازگار باشد و بازیابی داده از Backup انجام شود.
  • CI پایگاه داده خالی را تا آخرین نسخه Migrate و مسیر ارتقا از Snapshot نسخه قبلی را آزمایش می‌کند.

Backup و بازیابی

  • Backup شامل Base Backup زمان‌بندی‌شده و آرشیو پیوسته WAL برای Point-in-Time Recovery است.
  • Backup پیش از خروج از Server رمز می‌شود و Key آن جدا از Object Storage نگهداری می‌شود.
  • نسخه Backup در Bucket خصوصی و ترجیحا در Failure Domain جدا از VPS اصلی نگهداری می‌شود.
  • هدف اولیه Production برابر RPO <= 5m و RTO <= 60m است؛ این اعداد تعهد نهایی نیستند تا Restore Test روی توپولوژی انتخاب‌شده آن‌ها را اثبات کند.
  • Restore خودکار حداقل ماهانه و تمرین بازیابی انتهابه‌انتها حداقل فصلی اجرا می‌شود.
  • موفق بودن Upload Backup کافی نیست؛ Age آخرین WAL، قابلیت Decrypt، Integrity و Restore واقعی Alert دارند.
  • Retention باید با نیاز حقوقی و مالی تصویب شود؛ تا آن زمان حداقل 7 نسخه روزانه، 4 نسخه هفتگی و 6 نسخه ماهانه نقطه شروع است.

معیار پذیرش

  • آزمون Cross-tenant برای تمام Repositoryهای Tenant-scoped وجود دارد.
  • Application Role امکان Bypass کردن RLS یا مالکیت Table ندارد.
  • همه مبلغ‌ها ریال و Integer هستند و همه زمان‌ها UTC دارند.
  • Migration روی پایگاه خالی و نسخه قبلی بدون Downtime ناسازگار آزمایش شده است.
  • Restore Test ثبت‌شده، RPO و RTO هدف را اندازه‌گیری می‌کند.

منابع رسمی