-
Notifications
You must be signed in to change notification settings - Fork 0
ARGUS A28
will2469 edited this page Aug 29, 2026
·
1 revision
| Meta Field | Specification |
|---|---|
| Rule Code | ARGUS-A28 |
| Identifier | TABLE_LOCKING_CONSTRAINT_ADDITION |
| Severity | CRITICAL |
| Category | Database Schema Migration, Zero-Downtime DDL & Concurrency Availability |
| Analysis Layer | Layer 1 - Pure SQL-AST Migration Analysis |
| CWE Mapping | CWE-662: Improper Synchronization, CWE-400: Uncontrolled Resource Consumption |
| OWASP ASVS | OWASP ASVS v4.0.3/v5.0 §V1.4.3 (Zero-Downtime Schema Evolution & Table Lock Prevention) |
| PostgreSQL Target |
ACCESS EXCLUSIVE Lockout, Sequential Table Validation Lock Duration & Multi-Table Deadlock Prevention |
| Default Status | enabled |
Adding FOREIGN KEY or CHECK constraints to existing database tables in migration files (db/migrations/) must use the 2-phase zero-downtime addition pattern (NOT VALID followed by a separate VALIDATE CONSTRAINT).
┌─────────────────────────────────────────────────────────────────────────────┐
│ ARCHITECTURAL INVARIANT │
│ │
│ Adding constraints directly without `NOT VALID` on populated production │
│ tables is strictly prohibited. │
│ │
│ A standard `ADD CONSTRAINT` acquires an `ACCESS EXCLUSIVE` table lock and │
│ holds it while scanning the entire table to validate existing rows, │
│ causing prolonged platform-wide read/write outages. │
│ │
│ Exception: Tables newly created in the SAME migration file are exempt. │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ THE PRODUCTION CONSTRAINT LOCKING DISASTER │
│ │
│ Table `orders` has 20,000,000 rows. Table `users` has 5,000,000 rows. │
│ │
│ Case A: Direct ADD CONSTRAINT (VIOLATION): │
│ ALTER TABLE orders ADD CONSTRAINT fk_user FOREIGN KEY (u_id) REFERENCES... │
│ ├─► Acquires `ACCESS EXCLUSIVE` lock on `orders` (blocks ALL reads/writes) │
│ ├─► Acquires `SHARE ROW EXCLUSIVE` lock on `users` │
│ ├─► Sequentially scans 20M rows while holding exclusive locks (10+ min!) │
│ └─► SEV-1 OUTAGE: Multi-table lock freeze, connection pool crash! │
│ │
│ Case B: 2-Phase Zero-Downtime Constraint Addition (COMPLIANT): │
│ Phase 1: ALTER TABLE orders ADD CONSTRAINT fk_user ... NOT VALID; │
│ ├─► Acquires `ACCESS EXCLUSIVE` for < 2ms (enforces on new writes only) │
│ Phase 2: ALTER TABLE orders VALIDATE CONSTRAINT fk_user; │
│ ├─► Acquires `SHARE UPDATE EXCLUSIVE` lock only (Zero-Downtime scan) │
│ └─► Live reads & writes proceed uninterrupted! │
└─────────────────────────────────────────────────────────────────────────────┘
Adding a FOREIGN KEY or CHECK constraint via standard ALTER TABLE:
- Acquires an
ACCESS EXCLUSIVElock on the target table (blocking allSELECT,INSERT,UPDATE, andDELETE). - For foreign keys, it also acquires a
SHARE ROW EXCLUSIVElock on the referenced parent table. - It performs a full sequential scan across all existing rows to verify data integrity before releasing the lock. On large tables, this scan takes several minutes, freezing the entire application.
-
Phase 1 (
NOT VALID):PostgreSQL acquires the lock for less than 2 milliseconds, checks catalog metadata, and begins enforcing the constraint on all future writes immediately.ALTER TABLE orders ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id) NOT VALID;
-
Phase 2 (
VALIDATE CONSTRAINT):PostgreSQL validates existing data in the background with only aALTER TABLE orders VALIDATE CONSTRAINT fk_user;
SHARE UPDATE EXCLUSIVElock, allowing live concurrent read and write operations without downtime.
flowchart TD
A["Migration File (*.up.sql)"] --> B["Extract All AlterTableStmt Nodes via pg_query_go"]
B --> C{"Was Table Created in the SAME Migration File?"}
C -- "Yes (Empty Table)" --> D["PASS (Exempt)"]
C -- "No (Existing Table)" --> E{"Does AlterTableCmd Add FK or CHECK Constraint?"}
E -- "No (Other Alter Commands)" --> F["PASS"]
E -- "Yes" --> G{"Is 'NOT VALID' (SkipValidation) Flag Set?"}
G -- "Yes" --> H["PASS (2-Phase Compliant)"]
G -- "No" --> I["FAIL: ARGUS-A28 Direct Table Locking Constraint (CWE-662)"]
-
Table Inventory: Collects all
CreateStmttable definitions created in the current migration file. -
Alter Table Walker: Inspects
AlterTableStmtnodes and filters forAT_AddConstraint. -
Constraint Validation: For
CONSTR_FOREIGNandCONSTR_CHECK:- Checks
c.SkipValidation == true(representingNOT VALID). - If
c.SkipValidation == falseon an existing table$\rightarrow$ Flags Critical Violation.
- Checks
-
Exemptions: Suppressed via
-- argus:ignore ARGUS-A28 <reason>.
-- VIOLATION: Direct FK addition blocks reads and writes on both tables
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(id);-- VIOLATION: Direct CHECK constraint forces synchronous table validation
ALTER TABLE users
ADD CONSTRAINT chk_users_phone_len
CHECK (length(phone) >= 10);-- COMPLIANT (Phase 1): Instant constraint registration without table lock
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(id) NOT VALID;
-- COMPLIANT (Phase 2): Background non-blocking validation
ALTER TABLE orders VALIDATE CONSTRAINT fk_orders_customer;-- COMPLIANT: Direct constraint on newly created table in same migration
CREATE TABLE IF NOT EXISTS order_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
product_id UUID NOT NULL
);
ALTER TABLE order_items
ADD CONSTRAINT fk_order_items_product
FOREIGN KEY (product_id) REFERENCES products(id);-
Split Constraint Addition into 2 Phases:
- In your schema migration, always append
NOT VALIDtoADD CONSTRAINT. - In a subsequent migration step or file, execute
VALIDATE CONSTRAINT.
- In your schema migration, always append
-
Validate Outside Transactions if Needed:
VALIDATE CONSTRAINTcan run concurrently while your application handles regular user traffic.
rules:
ARGUS-A28:
enabled: true-- argus:ignore ARGUS-A28 maintenance window isolated constraint addition
ALTER TABLE users ADD CONSTRAINT chk_users_legacy CHECK (legacy_id > 0);