Connections, Replicas, and Pooling#
Introduction#
This guide covers read/write split behavior, replica selection strategies, and pooled connection lifecycle.
Connection Access#
$conn = DB::connection(); // default
$fresh = DB::freshConnection(); // uncached
Use freshConnection() when you explicitly need a new underlying PDO
instance and do not want shared connection reuse.
Connection Runtime Methods#
Runtime controls are available on Connection instances:
lifecycle hooks:
beforeConnect(),afterConnect(),beforeReconnect(),afterReconnect(),onConnectionFailure()query comment context:
setQueryCommentContext(),mergeQueryCommentContext(),clearQueryCommentContext(),getQueryCommentContext()execution helpers:
setFetchMode(),withoutQueryEvents(),stream(),yieldRows(),readOnlyTransaction()
Read/Write Split#
DB::addConnection([
'driver' => 'mysql',
'database' => 'app_db',
'username' => 'app_user',
'password' => 'secret',
'sticky' => true,
'read_strategy' => 'round_robin',
'read_latency_ttl' => 15,
'read_probe_sample_size' => 0,
'read_session_read_only' => false,
'read' => [
['host' => 'replica1.internal', 'weight' => 1],
['host' => 'replica2.internal', 'weight' => 3],
],
'write' => [
['host' => 'primary.internal'],
],
], 'main');
In this setup:
Writes use the write channel.
Reads use replicas when available.
With
sticky=true, reads switch to write PDO after a write on that connection to reduce read-after-write inconsistency windows.For SQLite read replicas, DBLayer applies
PRAGMA query_only = ONon read handles to prevent accidental writes through read PDO.
Note
Sticky read-after-write behavior is request-scope consistency behavior. It is useful for immediate read-back of writes, but increases read load on the primary/write connection.
Read Strategies#
randomround_robinleast_latencyweighted
Replica telemetry:
$info = DB::connection('main')->getReadReplicaInfo();
Strategy Behavior Summary#
random: random healthy replica selection.round_robin: deterministic rotation.least_latency: probe healthy replicas and choose fastest response. Winner is cached forread_latency_ttlseconds.read_probe_sample_sizecan bound first-pass probes for large pools.weighted: weighted random using per-replicaweight.
When a replica fails, DBLayer applies cooldown-based suppression before retry.
Using Multiple Database Connections#
Use named connections for operational separation:
DB::addConnection([...], 'primary');
DB::addConnection([...], 'reporting');
$users = DB::table('users', 'primary')->get();
$events = DB::table('user_events', 'reporting')->get();
Pooling#
DB::poolManager([
'max_connections' => 10,
'idle_timeout' => 60,
'max_lifetime' => 3600,
'health_check_interval' => 30,
]);
DB::withPooledConnection(function ($pooled) {
return $pooled->select('select 1');
}, 'main');
Operational Notes#
max_connectionsbounds total open pooled connections.idle_timeoutevicts idle connections.max_lifetimerotates old connections.health_check_intervalcontrols probe cadence.
Use pool stats to tune these settings under real workload:
$stats = DB::pool()->getStats();
Warning
Oversizing max_connections can overwhelm downstream databases.
Tune against real connection limits and query concurrency.
Pool Limitations and Fit#
PHP-FPM request lifecycles usually do not benefit much from pooling.
Long-running workers and CLI daemons benefit more from pooled reuse.
Pool health checks are interval-based and batched, not full scans on every acquire path.