PostgreSQL Architecture, ORM Integration, Schema Organization, Migrations, Transactions, Indexing & Data Access Security
104.1 Introduction
Chapter 103 established the secure backend foundation.
The next critical component is the database.
For an AI platform, the database is not merely a place to store usernames and application records. It may contain:
- user accounts
- organizations and tenants
- projects
- media metadata
- AI generation requests
- job states
- usage records
- subscription information
- audit events
- permissions
- document metadata
- RAG references
- model configurations
- security events
A database compromise can therefore expose a large portion of the platform.
The database architecture must consequently provide:
Confidentiality
+
Integrity
+
Availability
+
Tenant Isolation
+
Auditing
+
Recoverability
104.2 PostgreSQL as the Core Relational Database
A relational database such as PostgreSQL is well suited to the transactional portion of the platform.
The database can provide structured relationships such as:
User
↓
Organization
↓
Project
↓
Generation
↓
Media
while also supporting:
Jobs
Subscriptions
Usage
Permissions
Audit Events
The database should remain the authoritative source for transactional state.
104.3 Database Architecture
A simplified architecture is:
Application
│
▼
┌─────────────────┐
│ Data Access │
│ Layer / ORM │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Connection Pool │
└────────┬────────┘
│
▼
┌─────────────────┐
│ PostgreSQL │
└─────────────────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
Backup Replica Monitoring
The application should not expose PostgreSQL directly to browsers or untrusted clients.
104.4 Database Trust Boundary
The database should be considered a protected infrastructure component.
The intended flow is:
Browser
↓
API
↓
Authorization
↓
Application Service
↓
Repository
↓
Database
Not:
Browser
↓
Database
The second architecture makes centralized authorization and auditing substantially harder.
104.5 Database Schema Organization
A secure AI platform should organize tables around domain responsibilities.
A conceptual schema may include:
users
organizations
memberships
roles
permissions
projects
media_assets
generations
generation_jobs
usage_records
subscriptions
payments
documents
document_chunks
embeddings_metadata
notifications
audit_events
security_events
api_keys
model_registry
Not every deployment needs all of these tables immediately.
The important principle is that each table should have a clearly defined purpose and owner.
104.6 Primary Keys
Every major entity should have a stable unique identifier.
Conceptually:
User
└── user_id
Project
└── project_id
Generation
└── generation_id
Media
└── media_id
The identifier strategy should be consistent across the application.
Depending on requirements, identifiers may use UUIDs or another appropriately designed identifier format.
104.7 Foreign-Key Relationships
Relationships should be enforced at the database level where appropriate.
For example:
organizations
│
▼
projects
│
▼
generations
A generation should not reference a nonexistent project.
Database constraints provide an additional integrity boundary beyond application code.
104.8 Tenant-Aware Data Model
A multi-tenant AI platform must carefully define ownership.
For example:
organizations
│
├── members
├── projects
├── media
├── generations
└── documents
Resources should have an unambiguous ownership path.
For example:
generation
↓
project
↓
organization
This allows the application to determine which tenant owns a resource.
104.9 Tenant Isolation
A query such as:
```text id="s6q8df"
SELECT * FROM projects WHERE id = ?
may be insufficient in a multi-tenant environment.
The application should ensure that resource ownership is also considered.
Conceptually:
```text id="3wh1am"
Find Project
WHERE project_id = requested_id
AND organization_id = current_organization
The principle is:
Resource ID
+
Tenant Context
rather than resource ID alone.
104.10 Defense in Depth with Database Policies
For particularly sensitive multi-tenant systems, database-level controls can provide an additional isolation layer.
PostgreSQL Row-Level Security (RLS) can enforce policies directly at the database layer.
Conceptually:
Application Authorization
+
Database Row-Level Security
This creates defense in depth.
However, RLS should be designed carefully because incorrect policies can either block legitimate operations or create unintended access paths.
104.11 ORM Architecture
An ORM can provide a structured interface between the backend and PostgreSQL.
The architecture becomes:
Application Service
↓
Repository
↓
ORM
↓
PostgreSQL
The ORM should not become an excuse to ignore SQL behavior.
Developers should still understand:
- transactions
- indexes
- query plans
- constraints
- locks
- connection pools
- isolation levels
104.12 Schema as a Contract
The database schema should be treated as an application contract.
A schema defines:
What data exists
What relationships exist
What values are valid
What values are required
What uniqueness rules apply
What data may be deleted
Therefore schema changes should be reviewed carefully.
104.13 Migrations
Database changes should be performed through version-controlled migrations.
Conceptually:
Migration 001
↓
Migration 002
↓
Migration 003
↓
Migration 004
This provides a reproducible database history.
Avoid manually changing production tables without recording the change through the migration system.
104.14 Migration Safety
A migration should be evaluated for:
- compatibility
- execution time
- locking impact
- rollback strategy
- data transformation
- application compatibility
Large production tables require special care.
A seemingly simple schema change can create significant operational impact if it requires a long-running lock.
104.15 Expand-and-Contract Migrations
For high-availability systems, incompatible schema changes can be performed in stages.
Example:
Old Application
↓
Add New Column
↓
Application Supports Both
↓
Backfill Data
↓
Switch Application
↓
Remove Old Column
This is known as an expand-and-contract approach.
It reduces deployment coupling between application and database changes.
104.16 Constraints
Database constraints are valuable security and integrity controls.
Examples include:
NOT NULL
UNIQUE
CHECK
FOREIGN KEY
PRIMARY KEY
For example, a username that must be unique should have a database-level uniqueness constraint rather than relying only on application logic.
104.17 Database Validation vs Application Validation
Both layers are necessary.
Application validation:
User Input
↓
Validation
↓
Business Logic
Database validation:
Data
↓
Constraints
↓
Persistent State
Application validation provides better user feedback.
Database constraints provide a final integrity boundary.
104.18 Transactions
Transactions provide atomicity for related database operations.
For example:
Create Project
+
Create Project Membership
+
Create Audit Event
may need coordinated transactional behavior.
Conceptually:
BEGIN
operation A
operation B
operation C
COMMIT
If a critical operation fails:
ROLLBACK
The database returns to the previous consistent state.
104.19 Transaction Boundaries
Transactions should be as short as practical.
Avoid:
BEGIN
database operation
external AI API call
wait 60 seconds
another operation
COMMIT
A better approach is:
Create State
↓
Commit
↓
External Processing
↓
Update State
This avoids holding database resources while waiting for external systems.
104.20 Isolation Levels
Database transaction isolation determines how concurrent operations interact.
PostgreSQL provides several isolation behaviors.
The application should choose an appropriate isolation level according to the operation.
Higher isolation can improve consistency but may increase contention.
The important principle is:
Correctness Requirement
↓
Choose Isolation Level
↓
Measure Performance
Do not choose an isolation level arbitrarily.
104.21 Optimistic Concurrency
Some application records may be updated concurrently.
A version field can help detect stale updates.
Conceptually:
Record Version = 5
Client A reads version 5
Client B reads version 5
Client A updates → version 6
Client B attempts update using version 5
↓
Conflict detected
This prevents silent overwriting of newer state.
104.22 Idempotency Records
Important operations can use database-backed idempotency records.
For example:
idempotency_key
user_id
operation
status
result_reference
created_at
The system can determine whether a request has already been processed.
This is particularly useful for:
- payments
- AI generation
- webhooks
- file-processing requests
- job creation
104.23 Indexing
Indexes improve query performance.
Potential indexes include:
user_id
organization_id
project_id
created_at
status
external_id
But indexes have costs.
Every additional index can increase:
- storage usage
- write overhead
- maintenance cost
Therefore indexes should be based on actual query patterns.
104.24 Composite Indexes
Some queries depend on multiple fields.
For example:
organization_id
+
created_at
may form a useful composite index when the application frequently queries:
records belonging to an organization
ordered by creation time
Index design should follow actual access patterns rather than assumptions.
104.25 Query Performance
The application should monitor expensive queries.
Important signals include:
Query duration
Rows scanned
Rows returned
Frequency
Lock wait
Connection wait
A query that executes in 20 ms during development may become a serious problem when executed thousands of times per minute.
104.26 N+1 Query Problem
A common ORM performance problem is the N+1 pattern.
Conceptually:
1 query → retrieve projects
then:
1 query per project → retrieve metadata
For 1,000 projects, this can become:
1 + 1,000 queries
The data-access layer should use appropriate joins, batching, or carefully designed queries.
104.27 Connection Pooling
The application should use controlled database connection pooling.
Too few connections can reduce throughput.
Too many connections can overwhelm PostgreSQL.
The architecture should therefore consider:
Application Instances
↓
Connection Pools
↓
Database Capacity
Scaling application replicas without considering database connection capacity can cause database instability.
104.28 Database Credentials
Database credentials should never be embedded in source code.
They should be injected through secure configuration or secret management.
The database account should also have only the permissions required by the application.
104.29 Database Role Separation
Where practical, separate database roles can be used.
For example:
Application Role
Migration Role
Read-Only Analytics Role
Backup Role
A normal application runtime should not automatically possess unrestricted administrative database privileges.
104.30 Database Network Security
PostgreSQL should not normally be exposed directly to the public Internet.
A safer model is:
Internet
↓
Application Boundary
↓
Private Network
↓
PostgreSQL
Network access should be restricted to authorized services.
104.31 Encryption in Transit
Connections between application services and PostgreSQL should use appropriate transport protection.
This prevents credentials and database traffic from being transmitted as plaintext across untrusted networks.
104.32 Encryption at Rest
Database storage should use appropriate encryption-at-rest capabilities provided by the infrastructure.
Encryption at rest helps protect stored data if underlying storage media are improperly accessed.
However:
Encryption at Rest
≠
Complete Data Security
Application authorization, key management, backups, logging, and access controls remain necessary.
104.33 Sensitive Data Classification
Not all database data has the same sensitivity.
A useful classification is:
Public
Internal
Confidential
Highly Sensitive
Examples:
Public:
Application metadata
Internal:
Operational configuration
Confidential:
User project metadata
Highly Sensitive:
Authentication secrets or security-sensitive records
The classification should determine retention, access, logging, and protection requirements.
104.34 Password Storage
User passwords should never be stored as plaintext.
Passwords should be processed using an appropriate password-hashing mechanism designed for password storage.
The database should contain only the resulting verifier representation and necessary metadata.
104.35 API Key Storage
Application API keys require special treatment.
Where possible, the platform should avoid storing recoverable secrets unnecessarily.
A safer architecture may use:
Secret Manager
↓
Reference / Metadata
↓
Application
If an application must store a secret, encryption and strict access controls should be applied.
104.36 Audit Event Storage
Security-relevant audit records may be stored in dedicated tables.
For example:
audit_events
├── event_id
├── actor_id
├── tenant_id
├── action
├── resource_type
├── resource_id
├── result
├── timestamp
└── request_id
Audit records should be protected from unauthorized modification.
104.37 Security Event Storage
Security events may require a separate model:
security_events
├── event_id
├── category
├── severity
├── actor
├── resource
├── detection_source
├── timestamp
└── correlation_id
This supports detection and incident-response workflows.
104.38 Soft Delete vs Hard Delete
Some resources may require soft deletion.
Conceptually:
deleted_at
This can support recovery or audit requirements.
However, soft deletion does not necessarily satisfy privacy deletion requirements.
If a user requests permanent deletion, the system must determine:
What data must be deleted?
What data must be anonymized?
What data must be retained by law or policy?
What backups contain the data?
This connects database architecture with the privacy lifecycle discussed in earlier chapters.
104.39 Cascading Deletes
Foreign-key deletion behavior should be deliberately designed.
For example:
Delete Project
↓
Delete Project Media?
Delete Generations?
Delete Documents?
Delete Audit References?
Automatic cascading can be convenient but dangerous if used without careful analysis.
Sensitive records should not disappear unexpectedly because of an unrelated deletion.
104.40 Database Backups
Backups are essential.
A database backup strategy should consider:
Full backups
Incremental / WAL-based recovery
Retention
Encryption
Access control
Restore testing
Geographic redundancy
A backup that has never been restored successfully should not be considered fully reliable.
104.41 Point-in-Time Recovery
For important production systems, point-in-time recovery can reduce data-loss windows.
Conceptually:
Backup
+
Transaction Logs
↓
Restore to Selected Time
This can be particularly valuable after accidental deletion or data corruption.
104.42 Backup Security
Backups contain sensitive production data.
Therefore they require:
Encryption
Access control
Retention policy
Audit logging
Isolation
Restore testing
A backup repository should not become an easier path to production data than the production database itself.
104.43 Database Monitoring
Important database metrics include:
CPU
Memory
Storage
Connections
Query latency
Lock waits
Transaction rate
Replication status
Backup status
Error rate
Security monitoring should also observe unusual access patterns.
104.44 Database Security Detection
Potential signals include:
Unexpected administrative queries
Large unusual exports
Repeated authorization failures
Unexpected schema changes
Unusual connection sources
Abnormal query volume
These signals can be integrated with the broader security monitoring architecture.
104.45 Database Migration Security
Migration systems should be protected.
Only authorized deployment processes should normally apply production migrations.
A secure workflow is:
Developer
↓
Migration Created
↓
Review
↓
Automated Tests
↓
Staging
↓
Validation
↓
Production Approval
↓
Migration
104.46 Production Database Change Control
High-risk changes should receive additional review.
Examples:
Drop table
Drop column
Change primary key
Change encryption behavior
Change RLS policy
Change permissions
Large data migration
These operations can affect security and availability simultaneously.
104.47 Database Access Logging
Database access should be observable at an appropriate level.
The platform should record enough information to investigate:
Who
Did what
When
Against which resource
From which application context
However, excessive query logging can expose sensitive data.
Logging must therefore balance investigation value against privacy.
104.48 Data Minimization
The database should not store information simply because it might someday be useful.
Before adding a field, ask:
- Is it necessary?
- What is its sensitivity?
- How long should it exist?
- Who needs access?
- Can it be derived instead?
- What happens if it is leaked?
Data minimization reduces both privacy risk and operational complexity.
104.49 Database Threat Model
Important threats include:
SQL injection
Unauthorized access
Cross-tenant data access
Credential theft
Privilege escalation
Data corruption
Accidental deletion
Malicious exports
Backup compromise
Migration mistakes
Resource exhaustion
Lock contention
The database architecture should explicitly address each relevant threat.
104.50 SQL Injection Defense
Application queries should use parameterized queries or safe ORM mechanisms.
The unsafe conceptual pattern is:
User Input
↓
String Concatenation
↓
SQL
The safer pattern is:
User Input
↓
Validated Parameter
↓
Parameterized Query
ORM usage does not automatically eliminate every possible injection risk, especially when raw SQL features are used.
104.51 Raw SQL
Sometimes raw SQL is appropriate for performance or database-specific operations.
When raw SQL is used:
Input
↓
Validation
↓
Parameterized Query
↓
Database
Dynamic SQL construction must be handled carefully.
104.52 Database Resource Abuse
Database resources can be exhausted through:
- expensive queries
- unbounded pagination
- huge exports
- excessive concurrent requests
- repeated search operations
The application should therefore enforce limits such as:
Maximum page size
Maximum query duration
Maximum export size
Rate limits
Job quotas
104.53 Pagination
APIs should avoid returning unbounded database results.
Instead of:
GET /projects
returning every project, the API should use controlled pagination.
Conceptually:
Page Size
Cursor
Sort Order
Cursor-based pagination can be particularly useful for large datasets.
104.54 Search Architecture
Database search should be designed separately from general transactional queries.
Small datasets may use PostgreSQL search capabilities.
Large-scale semantic search may use:
PostgreSQL
+
Vector Store / Vector Extension
+
Object Storage
The correct choice depends on scale and workload.
104.55 AI Metadata Storage
AI generation records should separate metadata from large binary outputs.
For example:
Database:
generation_id
prompt metadata
model
parameters
status
timestamps
usage
storage reference
while:
Object Storage:
Generated image/video/audio
This prevents the relational database from becoming a large binary media repository.
104.56 RAG Data Storage
A RAG system may store:
Document
↓
Document Metadata
↓
Chunk Metadata
↓
Embedding Reference
Large documents and generated files should normally remain in object storage, while searchable metadata and relationships remain in the database.
104.57 Database and Cache Separation
The database should remain the authoritative source of persistent state.
A cache should not become the only copy of critical information.
Conceptually:
Database
↓
Source of Truth
Cache
↓
Performance Layer
If the cache disappears, the application should be able to reconstruct it from authoritative state where appropriate.
104.58 Database Security Checklist
Before moving forward:
[ ] PostgreSQL architecture defined
[ ] Database network boundary defined
[ ] ORM strategy defined
[ ] Schema ownership defined
[ ] Primary keys defined
[ ] Foreign keys defined
[ ] Tenant relationships defined
[ ] RLS requirements evaluated
[ ] Migration system defined
[ ] Migration review process defined
[ ] Constraints defined
[ ] Transaction strategy defined
[ ] Concurrency strategy defined
[ ] Index strategy defined
[ ] Connection pooling defined
[ ] Database roles defined
[ ] Encryption strategy defined
[ ] Sensitive data classification defined
[ ] Audit-event storage defined
[ ] Security-event storage defined
[ ] Backup strategy defined
[ ] Restore testing defined
[ ] Monitoring defined
[ ] SQL injection protections defined
[ ] Query/resource limits defined
[ ] Data retention defined
[ ] Deletion strategy defined
104.59 Reference Data Flow
The secure database lifecycle can be summarized as:
User Request
↓
Authentication
↓
Authorization
↓
Input Validation
↓
Application Service
↓
Repository
↓
ORM / Parameterized Query
↓
Database Constraints
↓
Transaction
↓
Persistent State
↓
Audit / Telemetry
This creates multiple defensive layers.
104.60 Final Architecture
The database layer now fits into the broader platform:
┌───────────────┐
│ Client │
└───────┬───────┘
│
▼
┌───────────────┐
│ API Boundary │
└───────┬───────┘
│
▼
┌───────────────┐
│ Auth + Policy │
└───────┬───────┘
│
▼
┌───────────────┐
│ App Services │
└───────┬───────┘
│
▼
┌───────────────┐
│ Repository │
└───────┬───────┘
│
▼
┌───────────────┐
│ PostgreSQL │
└───────┬───────┘
│
┌───────────────┼────────────────┐
▼ ▼ ▼
Backup Monitoring Recovery
104.61 Conclusion
The database is one of the most important security boundaries in the entire Secure AI Platform.
A strong database architecture does not depend on one control.
It combines:
Application Authorization
+
Tenant Isolation
+
Database Constraints
+
Parameterized Queries
+
Controlled Roles
+
Encryption
+
Auditing
+
Backups
+
Monitoring
+
Recovery
The central principle is:
Never assume that application code alone will protect persistent data.
Security should be enforced through multiple independent layers.
With the database architecture established, the next stage can connect persistent data to the rest of the platform.
The next chapter will focus on the secure API and data-access implementation, including repository patterns, CRUD operations, pagination, transactions, tenant-aware queries, authorization-aware repositories, API contracts, and secure database interaction patterns.
Top comments (0)