Drizzle'ın öğreticileri şemayı tanımlayıp bir sorgu çalıştırmakla bitiyor.
Üretimde asıl işi başlatan sorular ondan sonra geliyor: göçleri ne zaman
uygulayacaksın, next build veritabanına erişemediğinde ne olacak, ve
geliştirme sunucusu neden bir süre sonra bağlantı sınırına takılıyor?
Bu yazı student.czay.dev panelinden — 1000 satırlık şema, 17 numaralı göç ve
Docker'la kendi sunucumda çalışan bir kurulum.
Üç komut, üç farklı iş
Drizzle Kit'in en çok karıştırılan yeri bu.
| Komut | Ne yapıyor | Nerede kullanılır |
|---|---|---|
drizzle-kit generate | Şemayla veritabanı arasındaki farktan SQL dosyası üretir | Her şema değişikliğinde |
drizzle-kit migrate | Üretilmiş SQL dosyalarını sırayla uygular | Yerelde ve üretimde |
drizzle-kit push | Şemayı doğrudan veritabanına iter, dosya üretmez | Yalnız prototip |
push hızlı ve baştan çıkarıcı: şemayı değiştir, it, bitti. Ama geriye
hiçbir kayıt bırakmıyor. Ne uygulanan değişikliğin geçmişi var, ne geri
alma imkânı, ne de başka bir ortamda aynı adımları tekrarlama yolu.
Üretim veritabanı olan bir projede generate + migrate kullan. push
tek kişilik bir prototipte, atılacak bir veritabanında anlamlı.
Göç dosyalarına anlamlı isim vermek de sonradan işe yarıyor. Bu paneldeki klasör şöyle görünüyor:
drizzle/
0000_taban.sql
0001_dogum_tarihi.sql
0002_ders_serisi.sql
0003_grup_panosu.sql
0004_saklama_ve_silme.sql
…
0015_sinif_odulleri.sql
0016_sosyal_profiller.sqlAltı ay sonra "şu alan ne zaman eklendi" sorusunun cevabı klasörde duruyor.
Göçleri ne zaman uygulamalı?
Cevap: derlemede değil, kapsayıcı açılışında.
İmaj derlenirken veritabanına erişimin yok ve olmamalı da — aynı imaj başka bir veritabanına da açılabilmeli. Ayrıca dağıtım aracının yeniden başlatması, ölçeklemesi ve geri alması hep aynı imajı kullanıyor; şemayı imaja gömemezsin.
Göç, kodun değil veritabanının durumu. Kodun sürümü imajda, şemanın durumu veritabanında.
Açılış betiği sunucudan önce göçleri uyguluyor:
#!/bin/sh
set -e
echo "[giris] şema göçleri uygulanıyor…"
node scripts/migrate.cjs
echo "[giris] sunucu açılıyor…"
exec node apps/student/server.jsGöç başarısızsa set -e sayesinde kapsayıcı hiç açılmıyor. Bu bilinçli: yarım
şemayla istek karşılayan bir sürüm, açılmayan bir sürümden kötüdür — hata ilk
kullanıcı isteğinde ve sebebi görünmeden çıkar.
Göç klasörünü betiğe göre çöz
Küçük ama üretimde canını yakan bir ayrıntı:
import path from "node:path";
// Çalışma dizinine göre DEĞİL, betiğin konumuna göre
const gocKlasoru = path.join(__dirname, "..", "drizzle");Kaynakta betik db/ altında, kapsayıcıda paketlenmiş hâli scripts/ altında
duruyor. İkisinde de bir üstteki drizzle/ doğru; ama çalışma dizinine
güvenirsen kapsayıcı içinde klasörü bulamıyor.
İki koşucu, tek kayıt
Yerelde drizzle-kit migrate, üretimde kendi paketlenmiş betiğim çalışıyor.
İkisi de aynı drizzle.__drizzle_migrations tablosunu kullandığı için hangisi
çalışırsa çalışsın kayıt ortak — bir göç iki kez uygulanmıyor. Kendi koşucunu
yazacaksan bu tabloyu değiştirme.
next build veritabanı istiyor
Bu, Next.js + ORM birleşiminin klasik tuzağı ve teşhisi zor.
Sorun şu: derleme sırasında Next.js modülleri değerlendiriyor. Veritabanı
istemcisini modül seviyesinde kurmuşsan, o kurulum derleme anında çalışıyor ve
DB_POSTGRES_URL yoksa next build kırılıyor. Oysa derlemenin veritabanına
ihtiyacı yok.
Benim durumumda tetikleyici better-auth'un belirli bir sürümden sonra
adaptöre kurulum anında dokunmasıydı — derleme sunucuda, veritabanı erişimi
olmadan yapılıyordu ve her seferinde patlıyordu.
function connect(): Database {
if (globalForDb.__studentDb) return globalForDb.__studentDb;
const derlemede = process.env.NEXT_PHASE === "phase-production-build";
const url =
process.env.DB_POSTGRES_URL ??
(derlemede ? "postgresql://derleme@127.0.0.1:5432/derleme" : undefined);
if (!url) {
throw new Error("DB_POSTGRES_URL tanımlı değil.");
}
const sql = globalForDb.__studentSql ?? postgres(url, {
max: 10,
connect_timeout: 10,
});
const instance = drizzle(sql, { schema });
globalForDb.__studentSql = sql;
globalForDb.__studentDb = instance;
return instance;
}
/** Bağlantı ilk sorguda kurulur; modül değerlendirmesinde değil. */
export const db = new Proxy({} as Database, {
get: (_target, prop) => connect()[prop as keyof Database],
});İki şey birden çalışıyor:
- Yer tutucu adres yalnız derleme aşamasında devreye giriyor
(
NEXT_PHASE). Çalışma anında değişken hâlâ zorunlu ve hata mesajı aynı. postgres.jssoketi ilk sorguda açıyor, kurulumda değil. Yani yer tutucu adresle hiçbir bağlantı denemesi yapılmıyor; derleme boyunca tek bir paket bile gitmiyor.
Proxy de bunun için: db nesnesine gerçekten dokunulana kadar connect()
çalışmıyor.
Bağlantı havuzu sızıntısı
İki ayrı sızıntı var ve ikisi de sessizce ilerliyor.
Next.js geliştirme sunucusu her değişiklikte modülleri yeniden yüklüyor. Her
yükleme yeni bir postgres() havuzu açıyor, eskisi kapanmıyor. Yarım saat
çalıştıktan sonra Postgres'in varsayılan 100 bağlantı sınırına
dayanıyorsun ve "too many clients" hatası alıyorsun.
Çözüm havuzu globalThis üzerinde tutmak: modül yeniden yüklense de nesne
kalıyor.
İkincisi daha sinsi ve bunu üretimde yakaladım. Havuzu yalnız
geliştirmede önbelleğe alıyordum — yaygın tavsiye bu. Ama yukarıdaki proxy
her özellik erişiminde connect() çağırıyor. Yani üretimde db.select,
db.insert, db.query her erişimde yeni bir havuz kuruyordu.
Havuzlar hiç kapanmadığı için bağlantı sayısı istek başına artıyordu. Çözüm: üretimde de önbelleğe almak.
Havuz boyutu da bilinçli seçilmeli. Bu panelde max: 10: Postgres aynı makinede
olduğu için bağlantı kurmak ucuz ve 10 soket bu eşzamanlılığa fazlasıyla
yetiyor, Postgres'in varsayılan sınırını da zorlamıyor.
Yapılandırma
// drizzle.config.ts
export default defineConfig({
schema: "./db/schema.ts",
out: "./drizzle",
dialect: "postgresql",
dbCredentials: { url: databaseUrl },
strict: true,
verbose: true,
});strict: true yıkıcı bir işlem üretileceğinde onay istiyor — sütun silen bir
göçü fark etmeden üretmeni engelliyor. verbose: true de üretilen SQL'i
gösteriyor. İkisini de açık tut; göç dosyasını uygulamadan önce okumak bu
işin en ucuz güvenlik önlemi.
Özet
generate+migratekullan;pushprototip için- Göç dosyalarına anlamlı isim ver — altı ay sonra kendine teşekkür edersin
- Göçleri kapsayıcı açılışında uygula, derlemede değil
- Göç klasörünü
__dirname'e göre çöz, çalışma dizinine göre değil - Kendi koşucunu yazsan bile
__drizzle_migrationstablosunu paylaş - Bağlantıyı ilk sorguya ertele;
next buildveritabanı istememeli - Havuzu
globalThis'te tut — geliştirmede de üretimde de strict: trueile üretilen SQL'i uygulamadan önce oku
İlgili yazı: Next.js'i kendi sunucunda yayınlamak
