Belajar Swoole - Database: Coroutine MySQL & Connection Pools
Episode 13 of 26

Belajar Swoole - Database: Coroutine MySQL & Connection Pools

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.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

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.

Coroutine MySQL Client: Dasarnya

Client coroutine MySQL yang kita kenalkan di episode 9:

Query MySQL dalam coroutine
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.

Masalah: Koneksi per Coroutine = Bencana

Dengan coroutine, naif membuka koneksi di setiap coroutine:

Anti-pattern: koneksi baru tiap 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.

Connection Pool: Konsep

100%

Aturan mainnya:

  • Pool dibuka dengan ukuran tetap (misal 10 koneksi) sekali di awal.
  • Coroutine memanggil get() — mengambil koneksi idle; bila kosong, menunggu (coroutine menunda, worker tidak membeku).
  • Setelah selesai, put() mengembalikan koneksi ke pool.
  • Koneksi yang error/putus dibuang, diganti yang baru.

PDOPool: Implementasi Resmi

Swoole menyediakan Swoole\Database\PDOPool untuk PDO (MySQL/PostgreSQL/sqlite):

Setup PDOPool
<?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:

  1. get() dan put() selalu berpasangan — taruh put() di finally agar tidak bocor.
  2. Pool dibangun sekali — di luar handler, sebelum start(); jangan bikin pool per-request.
  3. Ukuran pool sesuai concurrency — 10 koneksi melayani ribuan request karena dipakai bergantian.

Redis Pool

Pola yang sama dengan RedisPool:

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);
    }
});

Query di Bawah Concurrency Tinggi

Kombinasi coroutine + pool membuat query parallel benar-benar berguna:

Query parallel dengan pool
$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.

Common Pitfalls

MasalahPenyebabSolusi
Too many connections di MySQLPool terlalu besar / koneksi bocorKecilkan pool; pastikan put() di finally
Request menggantungget() menunggu pool kosong tanpa timeoutBatasi concurrency request, atau perbesar pool
"MySQL server has gone away"Koneksi idle terlalu lama diputus serverPool auto-reconnect, atau set wait_timeout
Transaksi silang coroutineSatu PDO dipakai dua coroutine bersamaanJangan pernah berbagi koneksi; satu koneksi satu coroutine pada satu waktu

Penutup

Pada episode 13 ini, kalian telah mengelola koneksi database secara benar.

Inti yang harus dibawa pulang:

  • Client coroutine MySQL/PostgreSQL membuat query tidak memblokir worker.
  • Koneksi per-coroutine = bencana; gunakan connection pool.
  • PDOPool/RedisPool: get() → pakai → put() di finally.
  • Pool dibangun sekali, berukuran sesuai concurrency.
  • Query parallel dengan WaitGroup memangkas latensi total.

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!

Belajar Swoole - Database: Coroutine MySQL & Connection Pools | Belajar Swoole