aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Gameserver

MySQL Connection Pool: gids voor verbindingsbeheer

Op een drukke gameserver of een webapplicatie met veel verkeer is een MySQL connection pool (verbindingspool) een van de goedkoopste manieren om de prestaties te verbeteren. De reden is dat het openen en sluiten van een nieuwe verbinding voor elke databasebewerking veel duurder is dan het lijkt: een TCP-handshake, authenticatie, onderhandeling over de tekenset en de toewijzing van een thread aan de serverkant. Als dat alles bij elke aanvraag wordt herhaald, besteed je uiteindelijk meer tijd en CPU aan het opzetten dan aan de query's zelf. Dit artikel legt uit wat een verbindingspool is, waarom en hoe die de serverbelasting verlaagt, en hoe je er een correct opzet in verschillende talen.

Waarom is een verbinding openen duur?

Wanneer je mysql_real_connect aanroept of een nieuw PDO-object maakt, gebeurt het volgende achter de schermen:

  • Er wordt een TCP-verbinding tot stand gebracht tussen client en server (drieweg-handshake; extra round-trip-latentie op een externe host).
  • MySQL stuurt een handshake-pakket, de client antwoordt met een gebruikersnaam/wachtwoord en de server authenticeert dit.
  • Als SSL/TLS is ingeschakeld, vindt er bovendien een versleutelingshandshake plaats.
  • De server wijst een thread toe om de verbinding te bedienen en initialiseert de sessievariabelen.

Voor één verbinding kan dit een paar milliseconden duren, maar op een server die honderden aanvragen per seconde verwerkt, vergroot het betalen van deze kosten bij elke aanvraag zowel de latentie als de kans dat je snel tegen de max_connections-limiet van MySQL aanloopt. Precies hier komt de verbindingspool van pas: hij opent verbindingen één keer en in plaats van ze na gebruik te sluiten, geeft hij ze terug aan de pool, zodat de volgende aanvraag een gereedstaande verbinding hergebruikt.

Wat doet een connection pool precies?

Een verbindingspool is een component die een set vooraf geopende, gebruiksklare databaseverbindingen in het geheugen houdt. Wanneer de applicatie om een verbinding vraagt, leent de pool een inactieve uit; als het werk klaar is, wordt de verbinding niet gesloten maar teruggegeven aan de pool. Typische instellingen zijn:

  • Minimale poolgrootte: het minste aantal verbindingen dat permanent open blijft (voorkomt cold-start-latentie).
  • Maximale poolgrootte: het meeste aantal verbindingen dat tegelijk is toegestaan; dit moet onder MySQL's max_connections blijven.
  • Idle timeout: hoe lang een ongebruikte verbinding behouden blijft voordat hij wordt gesloten.
  • Verbindingsvalidatie: controleren of een verbinding nog leeft voordat hij wordt uitgegeven (meestal een eenvoudige ping of SELECT 1).

Het resultaat: de handshake-kosten worden over de levensduur van de applicatie slechts zo vaak betaald als de poolgrootte, niet voor duizenden aanvragen. Dit verlaagt de latentie per aanvraag en elimineert vrijwel de belasting van het aanmaken/vernietigen van threads op de MySQL-server.

PHP / Laravel: persistente verbindingen

Het klassieke model van PHP, "elke aanvraag begint van nul", maakt een traditionele pool lastig, omdat het proces sterft zodra de aanvraag eindigt. De oplossing zijn persistente verbindingen, die de verbinding in leven houden zolang de PHP-FPM-processen leven. Met PDO:

$pdo = new PDO($dsn, $user, $pass, [
    PDO::ATTR_PERSISTENT => true,
    PDO::ATTR_ERRMODE    => PDO::ERRMODE_EXCEPTION,
]);

In Laravel doe je hetzelfde in config/database.php:

'mysql' => [
    'driver'  => 'mysql',
    // ...
    'options' => [
        PDO::ATTR_PERSISTENT => true,
    ],
],

Een kanttekening: persistente verbindingen houden een aparte verbinding aan voor elke PHP-FPM-worker. Als je workeraantal hoog is, kan het totale aantal verbindingen de limiet van MySQL overschrijden. Plan je workeraantal en max_connections samen. Wil je een echte pool (groottebeheer, gezondheidscontroles), dan is ProxySQL voor je PHP-processen plaatsen een sterkere oplossing.

Node.js: een echt poolvoorbeeld

Omdat Node single-process is en lang leeft, past een verbindingspool hier van nature. Met de mysql2-bibliotheek:

const mysql = require('mysql2/promise');

const pool = mysql.createPool({
  host: '127.0.0.1',
  user: 'game',
  password: process.env.DB_PASS,
  database: 'server',
  connectionLimit: 20,
  waitForConnections: true,
  queueLimit: 0,
});

const [rows] = await pool.query(
  'SELECT level FROM players WHERE id = ?', [playerId]
);

pool.query() neemt bij elke aanroep een verbinding uit de pool, voert de query uit en geeft de verbinding automatisch terug. Voor bewerkingen die dezelfde verbinding vereisen, zoals transacties, pak je er handmatig een met pool.getConnection() en geef je hem terug met connection.release() als je klaar bent — het vergeten van release is de meest voorkomende fout die een pool uitput.

De gameserver-kant (C++)

Op een Metin2-gebaseerde of zelfgeschreven C++-gameserver is er meestal geen kant-en-klare ORM; je beheert de pool zelf. De logica is hetzelfde: bij het opstarten open je N verbindingen en plaats je ze in een thread-safe wachtrij; als een thread een query moet uitvoeren, haalt hij een verbinding uit de wachtrij en zet die terug als hij klaar is. Belangrijke punten:

  • Thread-veiligheid: bescherm de toegang tot de pool met een mutex of een lock-free wachtrij. Twee threads kunnen niet tegelijk dezelfde MySQL-verbinding gebruiken.
  • Herverbinden: wanneer wait_timeout verstrijkt, sluit MySQL de inactieve verbinding en krijg je een "MySQL server has gone away"-fout. Controleer met mysql_ping() voordat je hem uitgeeft, en verbind opnieuw als hij dood is.
  • Dimensionering: stem de pool af op het aantal DB-threads in de gamecore; meer verbindingen dan nodig verspilt alleen MySQL-geheugen.

De pool correct dimensioneren

De meest gemaakte fout is aannemen dat "meer verbindingen altijd beter is". Het tegendeel is waar: te veel gelijktijdige verbindingen vertragen de boel door meer context switches en lock-contentie in MySQL. Een praktisch startpunt is een relatief kleine pool gebaseerd op het aantal CPU-cores en de schijfconcurrentie (voor de meeste applicaties zijn enkele verbindingen per core genoeg). Verifieer door te meten:

SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Threads_running';
SHOW STATUS LIKE 'Max_used_connections';

Is Threads_running consistent hoog, dan is het knelpunt niet de pool maar je query's; los eerst trage query's en ontbrekende indexen op. Botst Max_used_connections tegen max_connections, verlaag dan je poollimieten of je hebt echt meer capaciteit nodig.

Veelgestelde vragen

Heeft MySQL een ingebouwde connection pool?

Nee, de klassieke verbindingspool is een client-side concept. Aan de serverkant bieden MySQL Enterprise en MariaDB een "thread pool"-plug-in; die bedient verbindingen met een klein aantal threads maar verwijdert de kosten van het opzetten van een verbinding niet. De twee lossen verschillende problemen op: de thread pool pakt de thread-explosie op de server aan, de connection pool de opzetkosten aan de clientkant.

Is een persistente verbinding hetzelfde als een connection pool?

Niet helemaal. Een persistente verbinding sluit een verbinding na een aanvraag niet en hergebruikt hem, maar het beheer van grootte en gezondheid is beperkt. Een echte pool biedt beleid zoals minimale/maximale grootte, idle timeout, gezondheidscontroles en wachtrijbeheer. In PHP gebruik je een middleware zoals ProxySQL voor een sterkere pool.

Wanneer heb ik ProxySQL nodig?

ProxySQL is zinvol wanneer je meerdere applicatieservers hebt en het totale aantal verbindingen centraal wilt begrenzen, query's wilt routeren of lees- en schrijfbewerkingen wilt splitsen. Voor kleine single-serverprojecten is een pool in de applicatie meestal genoeg.

Verstikken je databaseverbindingen je server? Heb je hulp nodig bij het opzetten en dimensioneren van een connection pool of bij MySQL-tuning voor je gameserver of webproject, neem dan contact met me op; ik bekijk je huidige opzet en stel een concreet verbeterplan op.

Bu kategorideki tüm yazılar →

Devamı için