Migration Rehberi
Veritabanı şema değişiklikleri Alembic ile yönetilir. Her migration production-critical'dir.
Son Migration'lar
| Revision | Tarih | Açıklama | Pattern | Risk |
|---|---|---|---|---|
0038_chk_device_source_ocpp | 2026-04-26 | chk_device_source whitelist'ine 'ocpp' ekle | NOT VALID + VALIDATE | DÜŞÜK (expand-only) |
0037_add_ocpp_tables | 2026-04 | OCPP 1.6J şeması (8 tablo + hypertable) | CREATE | ORTA |
0036_merge_sofar_mqtt | — | Merge migration (multiple heads) | merge | DÜŞÜK |
0038_chk_device_source_ocpp (PR #277)
- Revision id:
0038_chk_device_source_ocpp(27 char —alembic_versionVARCHAR(32) limit altında) - Down revision:
0037_add_ocpp_tables - Constraint adı:
ck_devices_chk_device_source(naming convention sonucu — bkz. aşağıda) - Pattern: NOT VALID + VALIDATE (production lock minimize)
- Whitelist:
('modbus', 'zigbee', 'virtual', 'manual', 'ocpp')— eski 4 değer +'ocpp' - Expand-only: Mevcut hiçbir satır reddedilmez; yeni whitelist eski whitelist'i kapsar.
- Downgrade güvenliği:
device_source='ocpp'kayıt varsaRuntimeErrorraise eder (cavalier DELETE yok —ocpp_chargers.device_idCASCADE +charge_sessions.charger_idRESTRICT zinciriyle çakışmayı önler). Operatör manuel temizlik yapmalı. - Gerekçe: 0037 migration'ı OCPP tablolarını ekledi ama
chk_device_sourcewhitelist'ine'ocpp'eklemeyi atladı (test gap). Backendservice.create_chargerdevice_source="ocpp"insert ederkenCheckViolationError→ POST/api/ocpp/chargersHTTP 500 (production incident). - Detay troubleshooting: OCPP Charger Create 500.
Komutlar
# Yeni migration oluştur
cd backend && alembic revision --autogenerate -m "açıklama"
# Migration uygula
alembic upgrade head
# Son migration'ı geri al
alembic downgrade -1
# Mevcut durumu kontrol et
alembic current
Güvenli Migration Pattern'leri
Expand → Backfill → Contract
- Expand: Yeni kolon/tablo ekle (nullable veya default ile)
- Backfill: Mevcut verileri doldur
- Contract: Eski kolon/tabloyu kaldır (sonraki release'de)
Yeni Kolon Ekleme
# ✅ Doğru: nullable veya server_default ile
op.add_column('devices', sa.Column('device_role', sa.String(), nullable=True))
# ❌ Yanlış: NOT NULL direkt ekleme (mevcut satırlar hata verir)
op.add_column('devices', sa.Column('device_role', sa.String(), nullable=False))
Tablo Silme
- Önce kod referanslarını kaldır (import, query, relationship)
- Bir sonraki release'de migration ile tabloyu sil
- Asla aynı PR'da kod + tablo silme yapma
NOT VALID + VALIDATE (CHECK constraint genişletme)
Whitelist tipi CHECK constraint'ine yeni değer eklerken (örn. enum genişletme) tablo lock'ını minimize eden production-grade pattern:
# 1) Eski constraint DROP
op.execute("ALTER TABLE devices DROP CONSTRAINT ck_devices_chk_device_source")
# 2) Yeni constraint NOT VALID — anlık metadata, satır taraması YOK
op.execute(
"ALTER TABLE devices "
"ADD CONSTRAINT ck_devices_chk_device_source "
"CHECK (device_source IN ('modbus','zigbee','virtual','manual','ocpp')) "
"NOT VALID"
)
# 3) VALIDATE — ShareUpdateExclusiveLock; okuma/yazma trafiği bloklanmaz
op.execute("ALTER TABLE devices VALIDATE CONSTRAINT ck_devices_chk_device_source")
Lock matrisi:
| Adım | Lock seviyesi | Süre | Trafik etkisi |
|---|---|---|---|
DROP CONSTRAINT | AccessExclusiveLock | Anlık | Metadata — anlık |
ADD ... NOT VALID | AccessExclusiveLock | Anlık | Metadata — satır tarama YOK |
VALIDATE CONSTRAINT | ShareUpdateExclusiveLock | Tablo boyutuna göre | Okuma/yazma bloklanmaz |
Pure DROP+ADD da hızlı ama büyüyen tablolarda full-scan AccessExclusiveLock'u uzar. NOT VALID + VALIDATE paterni Zeus 2.0 migration konvansiyonu (bkz. 0038_chk_device_source_ocpp.py).
Naming Convention
backend/app/core/database/base.py:11-19 SQLAlchemy MetaData üzerinde naming convention tanımlar:
convention = {
"ix": "ix_%(column_0_label)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
"pk": "pk_%(table_name)s",
}
metadata = MetaData(naming_convention=convention)
Önemli: CheckConstraint(..., name="chk_device_source") ile model'de yazsan bile DB'deki gerçek ad ck_devices_chk_device_source olur ("ck": "ck_%(table_name)s_%(constraint_name)s" template uygulanır).
Raw SQL kullanırken bypass riski:
Migration'da op.execute("ALTER TABLE ... ADD CONSTRAINT chk_device_source CHECK ...") yazarsan convention bypass olur, sonradan ORM ile drop ederken ck_devices_chk_device_source aranır ve bulunmaz. Bunu doğrulamak için:
-- Tablodaki tüm constraint adlarını listele
SELECT conname, pg_get_constraintdef(oid)
FROM pg_constraint
WHERE conrelid = 'devices'::regclass;
Defansif drop pattern (0038 örneği): hem convention adı hem legacy ad listesi denenir.
Kurallar
- Migration test edilmeden asla merge edilmez
- TimescaleDB hypertable →
op.execute()ile raw SQL - Büyük tablolarda index ekleme →
CREATE INDEX CONCURRENTLY - Kolon rename → yeni kolon ekle, backfill, eski kolonu sil
- CHECK constraint genişletme → NOT VALID + VALIDATE paterni
- Naming convention'a sadık kal (raw SQL'de bypass riski)
Bilinen Tuzaklar
| Tuzak | Etki | Çözüm |
|---|---|---|
alembic_version.version_num VARCHAR(32) | 32 char üstü revision id StringDataRightTruncationError | Revision id'yi kısa tut. alembic_version boyutunu büyütmek ayrı PR. Örnek: 0038_extend_chk_device_source_ocpp (34 char) FAIL → 0038_chk_device_source_ocpp (27 char) OK. |
| Naming convention bypass | Raw SQL ile yaratılan constraint convention adıyla bulunmaz | Yukarıdaki "Naming Convention" bölümü; defansif drop pattern. |
| Multiple heads | alembic upgrade head belirsiz davranır | Merge migration. migration-sanity CI job tek-head invariant'ını garanti eder. |
| TimescaleDB hypertable | Standart Alembic create_table hypertable üretmez | op.execute("SELECT create_hypertable('table', 'time')") raw SQL. |
MIGRATION_TARGET override | Production drift riski | PR #277 sonrası varsayılan head. Override sadece staging hot-fix için. Detay: CI/CD Pipeline. |
Multiple Heads ve Merge Migration (Issue #256)
Sorun
Birden fazla geliştirici aynı parent revision'a paralel olarak fork yapıp ayrı migration'lar oluşturduğunda Alembic multiple heads durumuna düşer:
$ alembic heads
0034_remove_sofar_power_from_metadata (head)
0035_add_mqtt_gateway_bus_unique (head)
Bu durumda alembic upgrade head komutu hangi head'e gideceği belirsiz olduğu için başarısız olur veya yanlış sıralı uygulamaya neden olur.
Çözüm: Merge Migration
alembic merge ile iki head birleştirilir. Sonuç olarak down_revision tuple haline gelir:
# 0036_merge_sofar_mqtt.py
"""merge sofar power columns + mqtt gateway bus unique"""
revision: str = "merge_sofar_mqtt"
down_revision: tuple = (
"sofar_power_columns", # 0034
"mqtt_gw_bus_unique", # 0035
)
branch_labels = None
depends_on = None
def upgrade() -> None:
pass # Merge-only, schema değişikliği yok
def downgrade() -> None:
pass
Komut
alembic merge -m "merge sofar mqtt" 0034_remove_sofar_power_from_metadata 0035_add_mqtt_gateway_bus_unique
Best Practice — head + single-head invariant (PR #277)
PR #277 sonrası MIGRATION_TARGET varsayılanı head'tir. Güvenliği iki katmanla sağlanır:
migration-sanityCI job — multiple heads varsa CI fail eder, deploy hiç başlamaz.- Merge migration konvansiyonu — paralel branch'lerden gelen iki head merge edilmeden head'e push'lanmaz.
# ✅ Doğru — varsayılan akış
MIGRATION_TARGET="${MIGRATION_TARGET:-head}"
alembic upgrade "${MIGRATION_TARGET}"
# ✅ Doğru — staging hot-fix için spesifik revision pin'i
MIGRATION_TARGET=0037_add_ocpp_tables alembic upgrade $MIGRATION_TARGET
Production'da MIGRATION_TARGET spesifik revision'a pin'lemek migration drift'in başlangıcıdır. Sadece staging'de hot-fix testi için kullanın. Detay: CI/CD Pipeline.
migration-sanity Job (CI)
CI pipeline'ında pre-deploy doğrulama olarak çalışır:
HEADS=$(alembic heads | wc -l)
if [ "$HEADS" -ne 1 ]; then
echo "ERROR: Multiple alembic heads detected — merge migration gerekli"
alembic heads
exit 1
fi
Bu job tek satır head garantisi sağlar; PR merge öncesi multiple-heads durumu yakalanır.