Mengelola koneksi database di Swoole dengan benar: client MySQL/Redis coroutine, konsep dan implementasi connection pool dengan PDOPool dan RedisPool, serta pola query aman di bawah concurrency tinggi tanpa membocorkan koneksi.

Setelah di episode 12 kita berbagi state antar worker dengan Table dan Atomic — pada episode kali ini kita menangani topik yang paling sering jadi sumber masalah di aplikasi Swoole produksi: koneksi database. Di PHP-FPM, setiap request membuka koneksi baru, dipakai, lalu ditutup — simpel tapi mahal. Di Swoole, koneksi bisa hidup terus, tetapi justru di situlah bahayanya bila dikelola asal-asalan.
Mengapa episode ini penting? Karena database adalah bottleneck nomor satu hampir semua aplikasi. Coroutine menyelesaikan masalah latency (query tidak memblokir worker), tetapi menciptakan masalah baru: berapa banyak koneksi yang aman dibuka? Jawabannya ada di satu kata: pool.
Client coroutine MySQL yang kita kenalkan di episode 9:
use Swoole\Coroutine;
Coroutine::create(function () {
$db = new Coroutine\MySQL();
$db->connect([
'host' => '127.0.0.1',
'port' => 3306,
'user' => 'root',
'password' => 'secret',
'database' => 'app',
]);
$rows = $db->query('SELECT id, name FROM users LIMIT 10');
$db->close();
});query() mengembalikan array hasil; prepare() + execute() untuk prepared statement. Query ini tidak memblokir worker — sambil menunggu database, coroutine lain jalan.
Tip
Untuk PostgreSQL, gunakan build option --enable-coroutine-postgresql (episode 3) dan class Swoole\Coroutine\PostgreSQL. Pola pemakaiannya sama: connect, query/prepare+execute, close. Client ini yang membuat Swoole unggul di benchmark NpgsqlRest pada Juli 2026.
Dengan coroutine, naif membuka koneksi di setiap coroutine:
$server->on('Request', function ($req, $res) {
Coroutine::create(function () use ($req, $res) {
$db = new Coroutine\MySQL();
$db->connect([...]); // koneksi BARU tiap request!
$rows = $db->query('SELECT ...');
$db->close();
$res->end(json_encode($rows));
});
});Seribu request concurrent = seribu koneksi MySQL. Database akan kolaps. Solusinya: connection pool — sekumpulan koneksi tetap yang dipinjam, dipakai, dan dikembalikan.
Aturan mainnya:
get() — mengambil koneksi idle; bila kosong, menunggu (coroutine menunda, worker tidak membeku).put() mengembalikan koneksi ke pool.Swoole menyediakan Swoole\Database\PDOPool untuk PDO (MySQL/PostgreSQL/sqlite):
<?php
use Swoole\Database\PDOPool;
use Swoole\Database\PDOConfig;
use Swoole\Coroutine;
use Swoole\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;
$pool = new PDOPool(
(new PDOConfig())
->withDriver('mysql')
->withHost('127.0.0.1')
->withPort(3306)
->withDbName('app')
->withUserName('root')
->withPassword('secret'),
10 // ukuran pool
);
$server = new Server('0.0.0.0', 9501);
$server->on('Request', function (Request $req, Response $res) use ($pool) {
$pdo = $pool->get(); // pinjam
try {
$stmt = $pdo->query('SELECT id, name FROM users LIMIT 10');
$rows = $stmt->fetchAll();
$res->header('Content-Type', 'application/json');
$res->end(json_encode($rows));
} catch (Throwable $e) {
$res->status(500);
$res->end('DB Error');
} finally {
$pool->put($pdo); // SELALU kembalikan
}
});
$server->start();Tiga aturan emas pool:
get() dan put() selalu berpasangan — taruh put() di finally agar tidak bocor.start(); jangan bikin pool per-request.Pola yang sama dengan RedisPool:
use Swoole\Database\RedisPool;
use Swoole\Database\RedisConfig;
$redisPool = new RedisPool(
(new RedisConfig())
->withHost('127.0.0.1')
->withPort(6379),
20
);
$server->on('Request', function (Request $req, Response $res) use ($redisPool) {
$redis = $redisPool->get();
try {
$value = $redis->get('last_seen:' . ($req->get['uid'] ?? 0));
$res->end($value ?: 'none');
} finally {
$redisPool->put($redis);
}
});Kombinasi coroutine + pool membuat query parallel benar-benar berguna:
$server->on('Request', function (Request $req, Response $res) use ($pool) {
$ids = $req->get['ids'] ?? '1,2,3,4,5';
$ids = array_map('intval', explode(',', $ids));
$wg = new Coroutine\WaitGroup();
$result = [];
foreach ($ids as $id) {
$wg->add();
Coroutine::create(function () use ($wg, $pool, $id, &$result) {
$pdo = $pool->get();
try {
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
$result[$id] = $stmt->fetch();
} finally {
$pool->put($pdo);
$wg->done();
}
});
}
$wg->wait();
$res->header('Content-Type', 'application/json');
$res->end(json_encode($result));
});5 query berjalan parallel dengan hanya beberapa koneksi dari pool — latensi total ≈ 1 query, bukan 5.
| Masalah | Penyebab | Solusi |
|---|---|---|
Too many connections di MySQL | Pool terlalu besar / koneksi bocor | Kecilkan pool; pastikan put() di finally |
| Request menggantung | get() menunggu pool kosong tanpa timeout | Batasi concurrency request, atau perbesar pool |
| "MySQL server has gone away" | Koneksi idle terlalu lama diputus server | Pool auto-reconnect, atau set wait_timeout |
| Transaksi silang coroutine | Satu PDO dipakai dua coroutine bersamaan | Jangan pernah berbagi koneksi; satu koneksi satu coroutine pada satu waktu |
Pada episode 13 ini, kalian telah mengelola koneksi database secara benar.
Inti yang harus dibawa pulang:
PDOPool/RedisPool: get() → pakai → put() di finally.Di episode 14 selanjutnya, kita mengalirkan data antar service: Kafka, RabbitMQ & message brokers — coroutine producer dan consumer worker dengan Swoole. Sampai jumpa di episode 14!