Koneksi MySQL & ukuran pool
Di sinilah kekhawatiran temanmu soal koneksi database sebenarnya berada — dan jawabannya bukan API, melainkan aritmetika sederhana yang jarang dilakukan orang.
Intisari
- Satu
Pool, satu instanceKysely, untuk seluruh proses. Jangan pernah membuatnya per request. - Rumusnya:
connectionLimit× jumlah task maksimum harus di bawahmax_connectionsRDS. - Autoscaling yang tidak memperhitungkan ini akan menjatuhkan database tepat saat trafik memuncak.
connectionLimitbesar tidak membuat kueri lebih cepat — MySQL tetap memproses berurutan per koneksi.- Timeout wajib ada di tiap lapisan, dan harus mengecil ke dalam: Cloudflare > ALB > Astro > kueri.
Satu berkas, satu pool
// src/lib/db.ts
import { Kysely, MysqlDialect } from "kysely";
import { createPool } from "mysql2";
import type { DB } from "./db-types";
const pool = createPool({
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT ?? 3306),
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
// Ukuran pool — lihat hitungannya di bawah
connectionLimit: 10,
queueLimit: 0,
// Batas waktu
connectTimeout: 5_000,
// Skema CI3 sering menyimpan waktu sebagai DATETIME tanpa zona waktu.
timezone: "Z",
dateStrings: false,
// Wajib untuk RDS: sertifikat AWS, verifikasi penuh
ssl: { rejectUnauthorized: true },
// Angka besar dari kolom BIGINT jangan diam-diam kehilangan presisi
supportBigNumbers: true,
bigNumberStrings: false,
});
export const db = new Kysely<DB>({
dialect: new MysqlDialect({ pool }),
});
createPool dari mysql2, bukan mysql2/promise.
MysqlDialect milik Kysely mengharapkan pool gaya callback dan membungkusnya sendiri.
Memberikan versi promise menghasilkan error yang membingungkan saat kueri pertama, bukan saat impor —
jadi terlihat seperti masalah kueri, padahal masalah konfigurasi.
Aritmetika yang menentukan
Inilah yang sebenarnya dikhawatirkan temanmu. Tiap proses Node punya pool sendiri. Jumlah koneksi total ke MySQL adalah:
total koneksi = connectionLimit × jumlah task ECS (MAKSIMUM, bukan yang sekarang)
+ koneksi dari sistem lain (CI3 lama, cron, admin, BI)
+ cadangan untuk DBA
Untuk RDS MySQL 8.0, max_connections default diturunkan dari memori instance. Contoh nyata:
| Kelas instance | RAM | max_connections default (kira-kira) |
|---|---|---|
db.t4g.medium | 4 GB | ~340 |
db.m6g.large | 8 GB | ~680 |
db.m6g.xlarge | 16 GB | ~1.365 |
db.r6g.2xlarge | 64 GB | ~5.000 (dibatasi ke 5.000) |
Contoh perhitungan untuk portalmu di db.m6g.large (~680):
Astro: connectionLimit 10 × maks 12 task = 120
CI3 lama selama masa transisi (Fase 9) = 150 ← jangan lupa ini
Cron & worker = 20
Admin, BI, koneksi manual = 30
────
320 dari 680. Aman.
Kalau autoscaling dinaikkan ke 40 task:
Astro: 10 × 40 = 400
+ sisanya = 200
────
600 dari 680. Mulai berbahaya.
Ini kegagalan yang paling menyakitkan karena terjadi tepat saat kamu paling tidak mampu
menanganinya. Trafik memuncak → autoscaling menambah task → tiap task membuka pool baru →
max_connections habis → semua task gagal terhubung, termasuk yang lama.
Situsmu mati total di puncak trafik, dan penyebabnya adalah mekanisme yang seharusnya menyelamatkannya.
Batas atas autoscaling harus dihitung dari max_connections, bukan dari CPU saja.
Kenapa connectionLimit besar tidak membantu
Godaannya menyetel connectionLimit: 100 supaya "tidak pernah kehabisan". Itu salah paham
tentang apa yang dilakukan pool.
Koneksi database bukan pekerja — ia saluran. MySQL memproses kueri di saluran itu satu per satu. Kalau database-mu sanggup melayani 200 kueri per detik dan tiap kueri makan 5 ms, sepuluh koneksi sudah cukup untuk menjenuhkannya. Koneksi ke-sebelas tidak menambah kecepatan; ia hanya membuat antrean berpindah dari aplikasimu ke database — tempat yang jauh lebih buruk untuk mengantre, karena di sana ia memakan memori dan membuat semua kueri melambat bersamaan.
| Gejala | Artinya | Tindakan |
|---|---|---|
| Kueri menunggu pool, database santai | connectionLimit terlalu kecil | Naikkan sedikit, ukur lagi |
| Kueri lambat, database sibuk | Kuerinya yang lambat | EXPLAIN, perbaiki index |
Too many connections | Total pool melebihi max_connections | Turunkan limit atau batas autoscaling |
| Latensi p95 melonjak saat scale-out | Task baru membuka koneksi bersamaan | Perlambat laju scaling |
Titik awal yang masuk akal untuk portalmu: connectionLimit: 10 per task.
Ukur waktu tunggu pool sebelum mengubahnya.
Timeout yang mengecil ke dalam
Cloudflare proxy 100 detik (batas platform)
└─ ALB idle timeout 60 detik
└─ Astro/Node 30 detik
└─ kueri 10 detik ← paling ketat
Kalau lapisan dalam lebih longgar dari lapisan luar, klien menyerah lebih dulu sementara kuerinya tetap berjalan dan tetap memegang koneksi. Pembaca melihat error; database-mu tetap bekerja untuk tidak ada siapa-siapa. Kalikan dengan trafik puncak dan pool-mu habis oleh pekerjaan yang tidak ada penontonnya.
// Batas waktu per kueri, memakai AbortSignal
export async function ambilArtikelPublik(slug: string) {
return db
.selectFrom("artikel")
.selectAll()
.where("slug", "=", slug)
.where("status", "=", "terbit")
.executeTakeFirst();
}
MySQL 8.0 punya pagar tingkat server yang layak dipasang:
SET SESSION MAX_EXECUTION_TIME = 10000; membatalkan SELECT yang lewat 10 detik di
sisi database. Pasang ini di event connection pool-mu supaya berlaku untuk semua kueri baca.
Ia hanya berlaku untuk SELECT, tapi di portal berita itu 95% bebanmu.
pool.on("connection", (conn) => {
conn.query("SET SESSION MAX_EXECUTION_TIME = 10000");
conn.query("SET SESSION time_zone = '+00:00'");
});
Menutup pool saat container dimatikan
// src/lib/db.ts
async function tutup() {
await db.destroy();
}
process.once("SIGTERM", tutup);
process.once("SIGINT", tutup);
ECS mengirim SIGTERM lalu menunggu sebelum SIGKILL. Tanpa penutupan yang rapi,
koneksi ditinggalkan menggantung dan MySQL butuh waktu untuk membersihkannya — yang berarti hitungan
koneksimu meleset persis saat deploy, ketika task lama dan baru hidup bersamaan.
Verifikasi, jangan asumsi
-- Berapa koneksi sekarang, dan dari mana
SELECT user, host, COUNT(*) AS jumlah
FROM information_schema.processlist
GROUP BY user, host
ORDER BY jumlah DESC;
-- Batas dan puncak yang pernah tercapai
SHOW VARIABLES LIKE 'max_connections';
SHOW STATUS LIKE 'Max_used_connections';
Max_used_connections adalah angka yang paling jujur: ia mencatat puncak sejak server terakhir
restart. Kalau ia sudah mendekati max_connections, kamu sudah pernah nyaris tumbang tanpa
menyadarinya.
Latihan: hitung anggaran koneksimu di atas kertas: max_connections RDS-mu yang
sebenarnya, dikurangi koneksi CI3 saat ini (jalankan kueri processlist di atas untuk angka
nyatanya), dibagi jumlah task ECS maksimum yang kamu rencanakan. Tetapkan
connectionLimit dari hasilnya. Lalu buat src/lib/db.ts, hubungkan ke salinan
database-mu, dan jalankan satu SELECT 1 dari halaman Astro.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.