Database Schema — V3 vs V4

So sánh chi tiết từ MySQL single-tenant 4 DBs sang PostgreSQL 16 multi-tenant unified — 92 tables với tenant isolation + Trust Layer + AI Core.

~70 tables V3 · MySQL · 4 DBs
92 tables V4 · PostgreSQL · Unified

Mục lục

  1. Infrastructure Comparison — Engine · Tenancy · DBs
  2. Stats Overview — Tables count per domain
  3. Domain Mapping — V3 17 domains → V4 15 module groups
  4. Table-by-Table Mapping — chi tiết theo module
  5. Schema Patterns — tenant_id, money, JSONB, partitioning
  6. New in V4 — 20+ tables mới (gap areas)
  7. Dropped Tables — HRM, Housing, Community, Content
  8. Migration Strategy — per-table approach

1. Infrastructure Comparison

Thay đổi lõi: DB engine · Tenancy model · Số lượng databases · Extensions.

DimensionV3 CurrentV4 TargetImpact
DB Engine MySQL 8.x PostgreSQL 16 JSONB native · GIN indexes · partitioning · pgvector
Tenancy Model Single-tenant Multi-tenant (row-level) tenant_id trên mọi bảng + RLS policies
Databases 4 (viecxanh + viecxanh_admin + chat_db MongoDB + Redis) 2 (viecxanh_v4 unified + Redis; MongoDB optional cho chat messages) Unified ops DB, no cross-DB queries
Migrations count ~71 migrations split 2 DBs ~92 migrations unified Clean build order, không còn deferred FK hack
Cross-DB Queries ⚠ Admin reads work_records từ public DB ✅ Không còn (unified) Bỏ PublicApiClient proxy complexity
FK Constraints ⚠ Deferred (migration 999999) skip SQLite ✅ Natural dependency order Tests run in-memory DB được
Extensions MySQL built-in only pgvector · pg_trgm · unaccent · pgcrypto · uuid-ossp · pg_stat_statements Vector AI + fuzzy search + encryption native
Row-Level Security ❌ Không có (app-layer only) ✅ RLS policies bật theo tenant_id Defense-in-depth cho tenant isolation
Partitioning ❌ Không có (single-tenant nhỏ) ✅ Native range partition (work_date, created_at) attendance_records 30M+ rows/tháng scale
Soft Deletes ❌ Không dùng (hard delete) deleted_at column + SoftDeletingScope Audit recovery + compliance
ID Strategy BigInt auto-increment BigInt auto-increment (giữ nguyên) Familiar, performant
Money Type decimal(15,2) NUMERIC(19,4) Financial-grade, 4 decimals cho VND chia giờ
Time Types timestamp (no TZ) TIMESTAMPTZ Timezone-aware cho audit, cross-region
Polymorphic Relations ⚠ 15+ morphmap types (tight coupling) Minimize, explicit relations preferred Dễ refactor service boundaries

2. Stats Overview

Số lượng tables qua từng giai đoạn migration.

V3 backend
~40
tables in viecxanh DB
V3 backend-admin
~30
tables in viecxanh_admin
V3 Total
~70
scattered 2 DBs + MongoDB
V4 Target
92
unified · multi-tenant

Table breakdown by category

CategoryV3V4Change
Tenant & Organization~3 (employers, clusters, partners)9+6 new (tenants, subscriptions, shifts, etc.)
Identity & Access~5 (users, roles, permissions, tokens)7+2 login_logs, refined structure
Recruitment~6 (jobs, applications, candidates, sources)10+4 job_distributions, interview_rounds, etc.
Worker Lifecycle~8 (workers, employments, milestones)8~0 structure refined, names unified
Attendance~6 (records, adjustments, imports)8+2 summaries, anomalies, holidays
Payroll & Finance~15 (wallets, transactions, payslips, loans)8-7 tách riêng financial products
Platform Infra~4 (audit, notifications, settings)6+2 domain_events outbox, file_attachments
Supplier Network~5 (ctv_tiers, commissions scattered)6+1 unified structure
Factory Operations05+5 ALL NEW (manpower, eval, feedback)
Reconciliation~3 (disputes, basic)7+4 sessions, items, settlements
AI & Knowledge08+8 ALL NEW (sessions, embeddings, tools)
Chat & MessagingMongoDB (separate DB)4+4 in PG; messages optional MongoDB
Worker App + Profile010+10 ALL NEW (accounts, skills, certs, timeline)
Financial Products0 (salary advance only)4+4 loans, insurance, wallet
Workflow Engine0 (FSM scattered)3+3 ALL NEW
Entity Versioning01+1 ALL NEW
CTV Tree0 (adjacency list scattered)1+1 materialized path
Integration Hub03+3 ALL NEW
HRM (dropped)~30 (viecxanh_admin)0-30 Not in V4 brief
Housing (dropped)9 (landlords, rooms, contracts)0-9 Not in V4 brief
Community (dropped)16 (posts, groups, points)0-16 Not in V4 brief
Content (dropped)6 (SEO posts, policies)0-6 Move to headless CMS

3. Domain Mapping — V3 → V4

17 V3 domains (backend) + 9 V3 domains (backend-admin) → 15 V4 module groups.

KEEP — Reuse as-is với tenant_id
RENAME — Đổi tên/structure
MERGE — Gộp vào module khác
SPLIT — Tách ra nhiều modules
DROP — Bỏ hoàn toàn
NEW — Module mới V4

backend/ (V3) → V4 modules

V3 DomainV4 ModuleActionNotes
ApplicationM11 AI AnalyticsMERGEAggregator → AI Core
InfrastructureM2 IAM + M7 PlatformSPLITUser mgmt → M2, utilities → M7
SharedShared kernelKEEPFramework utilities
DashboardM11 AI AnalyticsMERGEPer-tenant rewrite
WorkerM4 Worker LifecycleKEEP70% reuse (21 models)
WorkerManagementM4 Worker Lifecycle (extend)MERGEGroups/tags gộp M4
EmployerM1 Tenant + Gap A WorkflowSPLITEmployer → tenant entity
EmployerUserM2 IAM usersMERGEUnified user model
ClusterM1 work_locationsRENAMEGeographic unit, not tenant
Factory (V2)DROPLegacy alias, redundant
JobM3 RecruitmentKEEP80% reuse (Dispatch FSM)
PartnerM1 tenant_partnershipsRENAMEN:N tenant relationship
ContentDROPMove to headless CMS
AttendanceM5 AttendanceKEEP80% reuse
FinanceM6 Payroll + Gap E FinancialSPLITPayroll ops vs Financial products
IncentiveM8 Supplier + M10 ReconciliationSPLITCommission engine reuse
HousingDROPNot in V4 brief
CommunityDROPNot in V4 brief

backend-admin/ (V3) → V4 modules

V3 DomainV4 ModuleActionNotes
Attendance (admin)M5 Attendance (admin view)MERGESame tables, admin UI
CampaignM3 Recruitment (campaigns)MERGEGộp vào recruitment
CrmM3 + M4 + M8SPLITLead flows tách domain phù hợp
Finance (staff view)M6 Payroll + M10 ReconciliationMERGEAdmin view shared tables
Hrm (35 models)DROPKhông phải V4 focus
InfrastructureM7 Platform + Integration HubSPLITUser sync pattern → Integration Hub
RecruitmentSupportM8 Supplier NetworkKEEPRecruiter tools
SharedShared kernelKEEPFramework utilities
TrustM10 Reconciliation + Trust LayerMERGEDispute + reputation → M10

V4-only modules (không có trong V3)

🆕 Gap A Workflow Engine (3 tables)
State machine framework reusable. V3 có DispatchEngineService FSM nhưng monolithic, không reusable cho modules khác.
🆕 Gap B Integration Hub (3 tables)
Cross-tenant data sync + webhooks. V3 có PublicApiClient nhưng 1-way, không có framework formal.
🆕 Gap C AI Knowledge Base (3 tables)
pgvector embeddings + AI tools registry. Foundation cho RAG.
🆕 Gap D Chat & Messaging (4 tables)
Cross-tenant conversations. V3 chat-service dùng MongoDB tách biệt.
🆕 Gap E Financial Products (4 tables)
Loans, insurance, wallet. V3 chỉ có salary_advances trong Finance.
🆕 Gap F Digital Profile (5 tables)
9 cấu phần hồ sơ số (skills, experiences, certs, preferences, timeline).
🆕 Gap G Entity Versioning (1 table)
Full snapshot mỗi change. V3 chỉ có Spatie ActivityLog (partial).
🆕 Gap H CTV Tree (1 table)
Materialized path cho hoa hồng đa tầng. V3 dùng adjacency list (chậm).

4. Table-by-Table Mapping

Chi tiết từng bảng V3 → V4: rename, merge, add tenant_id, drop.

M1 — Tenant & Organization

V3 TableV4 TableActionKey Changes
employerstenantsRENAMEEmployer → Tenant entity, thêm type (factory/supplier/hybrid)
employers_subscriptionstenant_subscriptionsRENAMEBilling per tenant
tenant_modulesNEWModule on/off per tenant
partnerstenant_partnershipsRENAMENM ↔ NCC N:N relationship
employer_organizationsorganizationsKEEPSelf-ref tree (HQ/branch/site)
departments (in HRM)departmentsRENAMETách từ HRM dropped, keep structure
positions (scattered)positionsKEEPChức danh unified
clusterswork_locationsRENAMEGeographic unit, decoupled from tenant
shifts (in Attendance)shiftsREFINEPromoted to M1 shared, add OT rules JSONB

M3 — Recruitment

V3 TableV4 TableActionKey Changes
job_postingsjobsRENAMESimpler name, remove target_factory_tenant_id
job_distributionsNEWTách ra bảng riêng cho NCC → NM dispatch
job_posting_versionsvia entity_versionsMERGEUse Trust Layer generic versioning
applications (V2: job_applications)applicationsKEEPDrop V2 alias
candidate_profilescandidatesRENAMEShorter name, full-text index
interviewsinterviews + interview_roundsSPLITSupport multi-round PV
dispatchesmerged into applications + workflow_instancesMERGEState machine → Workflow Engine
job_sourcescandidate_sourcesRENAMEAlign to candidate-centric
recruitment_campaigns (admin)recruitment_campaignsKEEPMove from admin to core
candidate_contacts_logcandidate_contact_logsKEEPRename plural
candidate_mediacandidate_documentsRENAMEUse dedicated table, not polymorphic

M4 — Worker Lifecycle

V3 TableV4 TableActionKey Changes
workersworkersREFINEAdd tenant_id + link to worker_account_id (cross-tenant)
worker_jobsemploymentsRENAMEClearer semantics (1 employment per job)
worker_milestonesretention_milestonesRENAMEExplicit retention focus
worker_status_changesworker_status_logsRENAMEAlign với log convention
worker_documentsworker_documentsKEEPSame structure + tenant_id
worker_addressesJSON in workersMERGEDenormalize (1 address typical)
worker_bank_accountsJSON in workersMERGESame reason
worker_contactsemergency_contact JSONB in workersMERGEFewer joins
leave_requestsleave_requestsKEEP+ workflow_instance link
referralsreferralsKEEPInternal worker referrals
worker_tags, worker_groupsworker_tags, worker_groupsKEEPTừ WorkerManagement domain
worker_onboardingsNEWExplicit onboarding workflow

M5 — Attendance

V3 TableV4 TableActionKey Changes
attendance_periodstimesheet_periodsRENAMEAlign to "timesheet" domain term
attendance_recordsattendance_recordsREFINEPartition by work_date + tenant_id
attendance_importsattendance_importsKEEPExcel/API import history
attendance_adjustmentsattendance_adjustmentsKEEPWith entity_versions integration
overtime_recordsmerged to attendance_recordsMERGEot_hours column in main table
reconciliation_runsmoved to M10MERGEReconciliation domain separate
timesheet_summariesNEWAggregate per period/worker
attendance_anomaliesNEWAI anomaly detection output
shift_assignmentsNEWWorker-shift mapping history
holidaysNEWHolidays per tenant

M6 — Payroll (Finance split)

V3 TableV4 TableActionKey Changes
payroll_cyclespayroll_cyclesKEEPMonthly batches
payslipspayslipsREFINENUMERIC(19,4) + breakdown JSONB
payslip_itemspayslip_itemsKEEPDetail lines
salary_structuressalary_structuresKEEPTemplates
wallet_transactionssplit to payment_records + M12 wallet_balancesSPLITOperations vs aggregate view
walletswallet_balances (in M12)MERGECross-tenant aggregate (worker-centric)
bonusesbonusesKEEPStandalone bonus records
salary_advancessalary_advancesKEEP+ Gap E expansion (loans catalog)
ledger_entriessplit to payment_records + audit_logsSPLITOperations vs audit trail
loans, lending_productsloan_products, loan_applications (in Gap E)RENAMEMove to Financial Products
savings_accountsvia wallet_balances aggregateMERGEBalance aggregation
device_tokensmoved to M12 worker appMERGEPush notification per worker_account

M8-M10 — Ecosystem (Supplier + Factory + Reconciliation)

V3 TableV4 TableActionKey Changes
ctv_users, ctv_tierscollaborators + collaborator_treesMERGEAdjacency → materialized path
commission_policiescommission_rulesRENAME"Rules" clearer than "policies"
commission_formulas, commission_tiers, commission_conditionsmerged into commission_rules.rules (JSONB)MERGESimpler model
commission_runs, commission_linescommission_payoutsMERGESingle table per payout event
recruitersNEWInternal NCC recruiters
vendorsNEWSub-tier vendors
candidate_assignmentsNEWNCC → NM candidate dispatch
manpower_plansNEWFactory headcount forecasting
supplier_evaluationsNEWNCC scorecard
factory_feedbackNEWNM → NCC feedback
headcount_snapshotsNEWDaily aggregates
disputes (in admin)disputes (in M10)REFINE+ dispute_evidences, settlements
reconciliation_sessions, reconciliation_items, settlement_records, policy_snapshots, receivables_payablesNEWFull reconciliation model
trust_scoresvia M10 + audit_logsMERGETrust via policy_snapshots + history

M12 — Worker App (NEW MODULE)

V3 TableV4 TableActionKey Changes
users (worker subset)worker_accounts (NO tenant_id)SPLITCross-tenant identity
worker_account_linksNEWAccount ↔ worker × tenant junction
saved_jobs (scattered)saved_jobsKEEPPer worker_account
job_applications_mobileNEWMobile-sourced applications tracking
notifications (general)worker_notificationsSPLITWorker-specific channel
worker_skills, worker_experiences, worker_certificates, worker_preferences, worker_timeline_eventsNEWDigital Profile 9 parts

5. Schema Patterns Comparison

Những thay đổi pattern ảnh hưởng toàn bộ schema.

V3Single-tenant
-- workers table
CREATE TABLE workers (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  full_name VARCHAR(200),
  phone VARCHAR(20),
  id_number VARCHAR(12),
  status VARCHAR(20),
  created_at TIMESTAMP,
  updated_at TIMESTAMP
  -- NO tenant_id
  -- NO deleted_at
  -- NO RLS
);

-- Query from app layer
SELECT * FROM workers
WHERE status = 'active';
V4Multi-tenant + RLS
-- workers table V4
CREATE TABLE workers (
  id BIGSERIAL PRIMARY KEY,
  tenant_id BIGINT NOT NULL,
  worker_account_id BIGINT,
  full_name VARCHAR(200),
  phone VARCHAR(20),
  id_number VARCHAR(12),
  status VARCHAR(20),
  deleted_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE workers ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON workers
  USING (tenant_id = current_setting('app.current_tenant_id')::BIGINT);
V3Money — decimal(15,2)
-- Risk: precision loss on VND hourly
CREATE TABLE payslips (
  gross_pay DECIMAL(15,2),
  net_pay DECIMAL(15,2),
  ...
);

-- Float used elsewhere (BUG prone)
CREATE TABLE commission_formulas (
  rate FLOAT  -- DANGEROUS
);
V4Money — NUMERIC(19,4)
-- Financial-grade precision
CREATE TABLE payslips (
  gross_pay NUMERIC(19,4),
  net_pay NUMERIC(19,4),
  ...
);

-- All rate columns also NUMERIC
CREATE TABLE commission_rules (
  amount NUMERIC(19,4) NOT NULL,
  percentage NUMERIC(5,3)  -- safe
);

-- Invariant enforced by CHECK
ALTER TABLE payslips
ADD CONSTRAINT chk_net_le_gross
CHECK (net_pay <= gross_pay);
V3Polymorphic scattered
-- 15+ morphmap types tight coupling
CREATE TABLE activity_log (
  subject_type VARCHAR(255),
  subject_id BIGINT,
  ...
);

-- morphmap in AppServiceProvider
Relation::morphMap([
  'worker' => Worker::class,
  'job' => JobPosting::class,
  'application' => Application::class,
  ...15+ entries
]);

-- Hard to split to services later
V4Explicit relations + JSONB
-- Generic via audit_logs + entity_versions
CREATE TABLE audit_logs (
  tenant_id BIGINT,
  entity_type VARCHAR(50),
  entity_id BIGINT,
  action VARCHAR(20),
  old_values JSONB,
  new_values JSONB,
  ...
);

-- entity_type is plain string, no morphmap
-- Services define their own entity types
-- Easier to split to microservices later
V3No partitioning
-- 5M rows/month, no partition
-- Full-table scan risk
CREATE TABLE attendance_records (
  id BIGINT PRIMARY KEY,
  worker_id BIGINT,
  work_date DATE,
  ...
);

CREATE INDEX idx_attendance_date
  ON attendance_records(work_date);

-- Eventually degrades without partition
V4Native range partition
-- PostgreSQL 16 native
CREATE TABLE attendance_records (
  id BIGSERIAL,
  tenant_id BIGINT NOT NULL,
  worker_id BIGINT NOT NULL,
  work_date DATE NOT NULL,
  ...
) PARTITION BY RANGE (work_date);

CREATE TABLE attendance_2026_04
  PARTITION OF attendance_records
  FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');

-- Query planner auto-prunes partitions
-- Easy archival (drop old partitions)
V3Spatie ActivityLog only
-- Partial audit trail
activity_log table:
  - description (text)
  - subject (polymorphic)
  - causer (polymorphic)
  - properties (JSON)

-- Missing:
- Full entity snapshots
- Version numbers
- Policy snapshots at decision time
- Replay capability
V4Full Trust Layer
-- 3 tables complete coverage
audit_logs         -- what changed
entity_versions    -- full snapshot
domain_events      -- outbox pattern
policy_snapshots   -- (in M10)

-- Critical for V4 financial:
SELECT snapshot FROM entity_versions
WHERE entity_type = 'payslips'
  AND entity_id = 42
  AND version_number = 3;
-- Replay any entity state at any point

6. New in V4 — 20+ tables mới

Tables không có trong V3, phải build from zero.

Gap A Workflow Engine
workflow_definitions
workflow_instances
workflow_transitions
Gap B Integration Hub
integration_sync_rules
integration_sync_logs
webhook_subscriptions
Gap C AI Knowledge
knowledge_documents
knowledge_embeddings
ai_tools
Gap D Chat
conversations
conversation_participants
messages
message_reactions
Gap E Financial Products
loan_products
loan_applications
insurance_policies
wallet_balances
Gap F Digital Profile
worker_skills
worker_experiences
worker_certificates
worker_preferences
worker_timeline_events
Gap G Entity Versioning
entity_versions
Gap H CTV Tree
collaborator_trees
M1 Tenant (core tables)
tenants
tenant_modules
tenant_partnerships
tenant_subscriptions
M4 Worker extensions
worker_onboardings
M5 Attendance extensions
timesheet_summaries
attendance_anomalies
shift_assignments
holidays
M9 Factory Operations
manpower_plans
supplier_evaluations
factory_attendance_sources
factory_feedback
headcount_snapshots
M10 Reconciliation extensions
reconciliation_sessions
reconciliation_items
dispute_evidences
settlement_records
policy_snapshots
receivables_payables
M12 Worker App
worker_accounts (cross-tenant!)
worker_account_links
job_applications_mobile
worker_notifications
M3 Recruitment extensions
job_distributions
interview_rounds

7. Dropped Tables — Bỏ hoàn toàn

60+ tables từ V3 KHÔNG chuyển sang V4. Lý do + migration path.

V3 DomainTables droppedCountLý doData handling
HRM (admin) hrm_employees, hrm_departments, hrm_contracts, hrm_payrolls, hrm_attendance, hrm_leaves, hrm_kpi, hrm_training, ... ~30 V4 không phải HR internal — focus workforce ops Export cho record, không migrate. Team HR nội bộ chuyển sang HR-dedicated tool.
Housing landlords, rooms, rental_contracts, bookings, maintenance_requests, landlord_documents, room_media, ... 9 Không có trong V4 brief. Business case weak. Sunset với 3-6 month notice. Optional: tách app riêng nếu demand cao.
Community posts, groups, comments, reactions, points, post_media, group_members, moderation_logs, ... 16 Không phải core V4 Sunset entirely. V4 Chat module đủ cho communication.
Content content_posts, content_categories, content_tags, policies, training_materials, ... 6 SEO + policies → headless CMS riêng Migrate to Strapi/Sanity. Next.js fetch via API.
Factory V2 alias factories (VIEW alias) 1 Legacy backward-compat Remove VIEW. Frontend uses work_locations.
V2 morphmap legacy 5 morphmap entries Cleanup polymorphic bloat Code cleanup only, no data impact.
zalo-mini-app tables Zalo-specific user linking ~3 Zalo app sunset Export CTV data, freeze 12 months.
Tổng cộng drop: ~65 tables (HRM 30 + Community 16 + Housing 9 + Content 6 + misc). Đây là clean-up lớn, giúp V4 schema focused và dễ maintain.

8. Migration Strategy — Per-Table Approach

Mapping từng bảng V3 → V4 + data migration method.

8.1. Data Migration Categories

CategoryCountStrategyRisk
Direct copy ~20 tables 1:1 copy với tenant_id backfill (infer từ employer ownership) LOW
Rename + copy ~15 tables Copy với tên mới, cùng structure, backfill tenant_id LOW
Refine + copy ~12 tables Copy với columns mới (tenant_id, NUMERIC type cast, JSONB denormalize) MEDIUM
Split ~6 operations 1 V3 table → 2+ V4 tables (employments, wallets, etc.) MEDIUM
Merge ~10 operations Nhiều V3 tables → 1 V4 table (addresses/contacts → JSONB) MEDIUM
New (no migrate) ~30 tables Empty table, populate qua usage LOW
Drop (no migrate) ~65 tables Export CSV backup, không migrate LOW

8.2. Migration Timeline

Migration scenario: Canary tenant migrate trước, rồi batch migrate còn lại. Dùng pgloader cho MySQL → PostgreSQL bulk copy, sau đó custom scripts cho transformations phức tạp.
StepActionDurationDowntime?
1 Schema V4 finalized + tested in staging 1 week No
2 Migration scripts developed + tested against dump 2 weeks No
3 Canary tenant selected + engaged 1 week No
4 Dry-run migration cho canary tenant (from backup) 3-5 days No
5 Canary cutover (V3 read-only → migrate → V4 live) 4-8 hours YES (canary only)
6 Monitor canary 2 weeks (metrics, bugs) 2 weeks No
7 Batch migrate remaining tenants (batch size 5-10) 4-6 weeks YES (per batch)
8 V3 sunset announcement + decommission 2-4 weeks No

8.3. Invariants to Enforce Post-Migration

Financial invariants
  • SUM(payslip_items.amount) = payslip.net_pay (± 0.01)
  • SUM(payslips.net_pay) = payroll_cycle.total_net
  • wallet_balances.advance_used ≤ advance_available
  • Cross-check với ledger_entries từ V3
Tenant isolation invariants
  • Mọi row có tenant_id IS NOT NULL
  • FK reference chỉ trong cùng tenant (trừ partnerships)
  • RLS policies active cho all production tables
  • Integration test 2+ tenants không leak data
Continuity invariants
  • Worker count V3 = Worker count V4 (per tenant)
  • Active employments V3 = V4
  • Pending applications V3 = V4
  • Active CTV count V3 = collaborators V4
Trust Layer requirements
  • Mọi V3 ActivityLog entry → audit_logs
  • Latest state → entity_versions v1
  • Commission policies → policy_snapshots
  • RLS bypass audit log cho super admin

Summary

V3 ~70 tables scattered 2 DBs → V4 92 tables unified PostgreSQL

~60% data migratable (direct/rename/refine) · ~30% structural changes (split/merge) · ~65 tables dropped (HRM, Community, Housing, Content) · ~30 tables ALL NEW (gap areas)

Migration timeline: 3-4 months (canary + batch) · Downtime: ~8 hours per tenant · Zero data loss target.