Rules
Öne çıkan·v1.0.0·2026-04-22Veritabanı Kuralları
İlişkisel veritabanı (PostgreSQL öncelikli) tasarım ve kullanım kuralları — schema, naming, indexing, migration, query pattern, transaction, performance, backup. CLAUDE.md / .cursorrules için.
databasepostgressqlmigrationrulesbackendclaude-md
İçerik
Bu içeriği projenin CLAUDE.md / AGENTS.md / .cursorrules dosyasına yapıştır.
Veritabanı Kuralları (PostgreSQL odaklı)
Naming
- Tablolar:
snake_case, tekil (user,order,invoice) — veya takım standardı çoğul. Proje içinde tutarlı. - Kolonlar:
snake_case—created_at,email_verified_at,is_active. - Primary key:
id(her tablo için). - Foreign key:
<table>_id—user_id,order_id. - Timestamp:
created_at,updated_at,deleted_at(soft delete varsa). Her zamanTIMESTAMPTZ(timezone-aware). - Boolean:
is_*,has_*,can_*prefix. - Index:
<table>_<cols>_<type>_idx—orders_user_id_status_idx. - Constraint:
<table>_<rule>_check—orders_total_positive_check.
Schema
- Normalize et, sonra gerekliyse denormalize (performans gerekçesiyle).
- NOT NULL default: kolonu eklerken "gerçekten nullable mı" sor. Nullable kolonlar bug mıknatısıdır.
- Default value'lar explicit — fallback davranışını schema söyler.
- Enum: PostgreSQL
CREATE TYPE AS ENUMveyaCHECKconstraint'li text. Evolve etmek istiyorsan text + CHECK tercih. - JSON/JSONB: structured data için değil, semi-structured için. JSONB her zaman (binary, indexable).
- UUID vs bigint PK: çoğu projede UUID (v7 time-sortable tercih). Bigint her zaman büyük yazma yüklü, merkezi sistemler için.
Timestamp
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
- Her tabloda
created_at,updated_at. - Trigger ile
updated_atauto-update veya app seviyesinde.
Soft delete
deleted_at TIMESTAMPTZ
- Hard delete = gerçek DELETE. Soft delete =
deleted_atset. Projede biri seçilir, karıştırma. - Query'lerde
WHERE deleted_at IS NULL— view ile sarmak tercih.
Index'leme
- Query pattern'inden önce index yazma — over-indexing yazma performansını vurur.
- WHERE / JOIN / ORDER BY kolonları aday.
- Composite index sırası: equality önce, range sonra.
(status, created_at)≠(created_at, status). - Partial index selective condition'lar için:
CREATE INDEX orders_pending_idx ON orders(created_at) WHERE status = 'pending'; - Unique: business rule'u (email unique, slug unique) DB ile enforce et, app'te değil.
- Foreign key'e index zorunlu (Postgres otomatik değil) — JOIN performansı.
- BRIN büyük time-series için kompakt alternative.
- Full-text:
tsvector+ GIN index.
Review: pg_stat_user_indexes ile kullanılmayan index'leri tespit et, kaldır (yazma pahası).
Foreign key
- Her ilişki FK ile enforce edilir:
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE ON DELETEdavranışı bilinçli:CASCADE,SET NULL,RESTRICT,NO ACTION. DefaultNO ACTION(implicit reject).- Cycle dikkat — A references B, B references A = migration acısı.
Transactions
- DEFAULT: her write operation transaction içinde. ORM genelde halleder.
- Transaction süresi kısa — uzun transaction = lock uzun = outage riski.
- Dış HTTP çağrısı transaction içinde yasak. Lock süresi blast radius'u büyütür.
- Isolation level:
READ COMMITTED(Postgres default) çoğu projeye yeter.REPEATABLE READ/SERIALIZABLEspesifik ihtiyaçlarda — retry logic'i gerektirir.
SELECT ... FOR UPDATEkilit gerekiyorsa. Kullandıktan sonra deadlock potansiyelini düşün.
Query pattern
SELECT *yasak prod kodda — gereksiz kolon transfer, schema değiştiğinde kırılma.- Explicit column list:
SELECT id, email, created_at FROM users WHERE ... - Parametreli query her zaman:
String concat yasak.SELECT * FROM users WHERE id = $1 LIMITher list query'de — unbounded fetch yasak.EXPLAIN ANALYZEyeni query eklenirken koş. Sequential scan büyük tabloya = iyice bak.
N+1 ve join
-
N+1 probleminin farkında ol:
for user in users: orders = Order.find(user_id=user.id) # ❌ N+1 -
Eager load:
- SQL: JOIN + tek query
- ORM:
includes,selectinload,populate
-
Join büyük tabloya dikkat — query plan'ı gör. Subquery / CTE bazen daha okunaklı.
Migration
- Reversible — her up için down yaz.
- Zero-downtime:
- Add column → nullable önce, backfill, sonra NOT NULL
- Rename column → expand (yeni kolon + dual write) + backfill + contract (eski kolon drop)
- Add index →
CONCURRENTLY
- Büyük tabloya
ALTERdikkat — LOCK süresi ölçülür. - Tek migration tek iş — 10 DDL + 3 DML tek dosyada olmaz.
- Staging'de prod-size ile test.
Detaylı: Migration Yazma skill'i.
Connection pool
- App seviyesinde pool zorunlu — her istekte yeni connection yasak.
- Max connections DB tarafında (Postgres default 100) aşılmasın. Pool toplamı < max.
- Pool sınırları:
- Per-process pool 5-20
- PgBouncer / pgcat gibi proxy büyük projelerde
Backup
- Automated daily backup minimum. Point-in-time recovery (PITR) kritik DB için.
- Test restore — backup var = restore edildi mi? Quarterly test.
- Backup şifrelemesi at-rest.
- Retention policy — ne kadar süre saklanır, compliance ne diyor.
Monitoring
- Query performance:
pg_stat_statements— yavaş query tespiti- Slow query log (> 500ms)
- Connection: aktif vs idle connection count
- Replication lag: read replica'lar geride mi?
- Disk space: %80 alert
- Bloat: autovacuum çalışıyor mu, table/index bloat metrikleri
Data types
- Money:
NUMERIC(12, 2)—float/doubleasla (kayan noktalı hata). - Boolean:
BOOLEAN—SMALLINT 0/1kullanma. - Enum:
CHECK+ text veya Postgres ENUM. - JSON:
JSONB(binary, indexable).JSONkullanma. - Array: PostgreSQL array okey küçük veri için. Büyük set = ayrı tablo.
- UUID:
UUID—CHAR(36)kullanma. - Text:
TEXT(PostgreSQL'deVARCHAR(n)ile performans farkı yok;VARCHAR(n)sadece business limit gerekiyorsa).
Query güvenliği
- Prepared statements zorunlu.
- Least privilege: uygulama user'ı sadece gerekli permission'lara sahip:
- Read-only user:
SELECTsadece - Write user:
SELECT, INSERT, UPDATE, DELETE—DROP,TRUNCATEyok - Migration user: ayrı, daha geniş yetki
- Read-only user:
- Row-level security (RLS) multi-tenant için — güçlü ama karmaşık; doğru uygularsan IDOR'u DB seviyesinde çözer.
ORM vs raw SQL
- ORM çoğu CRUD için yeter (SQLAlchemy, Prisma, Drizzle).
- Karmaşık analytical query için raw SQL tercih — ORM abstraction'ı zayıf.
- Migration: ORM migrate (auto-generate) + manuel review. Critical migration'ı elle yaz.
Veri kalitesi
- Constraint'leri DB'de:
- UNIQUE (email, slug)
- NOT NULL (gerekenlerde)
- CHECK (age > 0, status IN (...))
- FOREIGN KEY (ilişkiler)
- App-level validation DB constraint'in tekrarı değil — her iki katman da katkı sağlar.
- "Garbage in" önleme: input validation + DB constraint + zaman zaman data audit.
Locking
- Optimistic locking (version kolonu) çoğu çakışmasız durumda yeter.
- Pessimistic locking (
SELECT FOR UPDATE) gerçekten race varsa. - Advisory lock (
pg_advisory_lock) uygulama-seviyesinde kritik section için.
Replication & read replicas
- Read replica analitik / heavy read için.
- Read-after-write consistency kullanıcı için → primary'e yönlendir (session affinity).
- Replication lag ölçülür; alerts'i var.
Yasak listesi (özet)
SELECT *prod kodda- String concat SQL
VARCHARpara için (NUMERIC kullan)float/doublepara için- Foreign key'siz ilişki
- NOT NULL'suz kolon "emin değilim" diye
- Index'siz FK kolonu (JOIN yavaş)
- Uzun transaction + dış API call
- Migration up var, down yok
ALTERprod'da concurrently'siz- Pool'suz connection management
- Production DB credential paylaşımı
- Backup test edilmeden
TRUNCATE/DROPapp user permission'da- Default enum çevirmesi migration yok
Commit
Conventional Commits. Migration için feat(db): ..., data fix için chore(db): backfill .... Destructive migration için kurul tartışma.