Skip to content

Commit 5fa0f78

Browse files
committed
feat: add read/write connection splitting with load balancing
- Automatic query routing: SELECTs to read replicas, DML to write master - Three load balancing strategies: RoundRobin, Random, and Weighted - Sticky writes for read-after-write consistency - Force write mode to read from master when needed - Transaction support (automatically uses master) - Health checks and automatic failover - ConnectionType enum for type-safe connection classification - LoadBalancerInterface with three built-in implementations - ConnectionRouter class for managing read/write routing logic - QueryBuilder::forceWrite() method to force reads from master - PdoDb::enableReadWriteSplitting() method to activate feature - PdoDb::enableStickyWrites() for read-after-write consistency - PdoDb::getConnectionRouter() to access router instance - Comprehensive test coverage (13 new tests, 24 assertions) - Complete documentation in documentation/05-advanced-features/ - Three example files demonstrating all features - Updated README.md with new feature section
1 parent a6f9b38 commit 5fa0f78

19 files changed

+2477
-87
lines changed

CHANGELOG.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
1110
---
1211

1312
## [2.6.2] - 2025-10-25

README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
Built on top of PDO with **zero external dependencies**, it offers:
1515
- **Fluent Query Builder** - Intuitive, chainable API for all database operations
1616
- **Cross-Database Compatibility** - Automatic SQL dialect handling (MySQL, PostgreSQL, SQLite)
17+
- **Read/Write Splitting** - Horizontal scaling with master-replica architecture and load balancing
1718
- **JSON Operations** - Native JSON support with consistent API across all databases
1819
- **Query Caching** - PSR-16 integration for result caching (10-1000x faster queries)
1920
- **Advanced Pagination** - Full, simple, and cursor-based pagination with metadata
@@ -42,6 +43,7 @@ Inspired by [ThingEngineer/PHP-MySQLi-Database-Class](https://github.com/ThingEn
4243
- [PostgreSQL Configuration](#postgresql-configuration)
4344
- [SQLite Configuration](#sqlite-configuration)
4445
- [Connection Pooling](#connection-pooling)
46+
- [Read/Write Splitting](#readwrite-splitting)
4547
- [Quick Start](#quick-start)
4648
- [Basic CRUD Operations](#basic-crud-operations)
4749
- [Filtering and Joining](#filtering-and-joining)
@@ -314,6 +316,81 @@ $users = $db->connection('mysql_main')->find()->from('users')->get();
314316
$stats = $db->connection('pgsql_analytics')->find()->from('stats')->get();
315317
```
316318

319+
### Read/Write Splitting
320+
321+
Scale horizontally with master-replica architecture. Automatically route reads to replicas and writes to master:
322+
323+
```php
324+
use tommyknocker\pdodb\PdoDb;
325+
use tommyknocker\pdodb\connection\loadbalancer\RoundRobinLoadBalancer;
326+
327+
$db = new PdoDb();
328+
329+
// Enable read/write splitting with load balancer
330+
$db->enableReadWriteSplitting(new RoundRobinLoadBalancer());
331+
332+
// Add write connection (master)
333+
$db->addConnection('master', [
334+
'driver' => 'mysql',
335+
'host' => 'master.db.local',
336+
'username' => 'user',
337+
'password' => 'pass',
338+
'dbname' => 'myapp',
339+
'type' => 'write',
340+
]);
341+
342+
// Add read connections (replicas)
343+
$db->addConnection('replica-1', [
344+
'driver' => 'mysql',
345+
'host' => 'replica1.db.local',
346+
'username' => 'user',
347+
'password' => 'pass',
348+
'dbname' => 'myapp',
349+
'type' => 'read',
350+
]);
351+
352+
$db->addConnection('replica-2', [
353+
'driver' => 'mysql',
354+
'host' => 'replica2.db.local',
355+
'username' => 'user',
356+
'password' => 'pass',
357+
'dbname' => 'myapp',
358+
'type' => 'read',
359+
]);
360+
361+
$db->connection('master');
362+
363+
// SELECT queries automatically go to read replicas
364+
$users = $db->find()->from('users')->get();
365+
366+
// INSERT/UPDATE/DELETE automatically go to write master
367+
$id = $db->find()->table('users')->insert(['name' => 'John', 'email' => 'john@example.com']);
368+
369+
// Force a SELECT to read from master
370+
$user = $db->find()->from('users')->forceWrite()->where('id', $id)->getOne();
371+
372+
// Enable sticky writes (reads go to master for 60s after writes)
373+
$db->enableStickyWrites(60);
374+
```
375+
376+
**Load Balancing Strategies:**
377+
- `RoundRobinLoadBalancer` - Distributes requests evenly in circular order
378+
- `RandomLoadBalancer` - Randomly selects a replica
379+
- `WeightedLoadBalancer` - Distributes proportionally based on weights
380+
381+
**Key Features:**
382+
- Automatic query routing (SELECTs → replicas, DML → master)
383+
- Sticky writes for read-after-write consistency
384+
- Multiple load balancing strategies
385+
- Health checks and automatic failover
386+
- Transaction support (always uses master)
387+
388+
See:
389+
- [Documentation: Read/Write Splitting](documentation/05-advanced-features/read-write-splitting.md)
390+
- [Example: Basic Setup](examples/15-read-write-splitting/01-basic-setup.php)
391+
- [Example: Sticky Writes](examples/15-read-write-splitting/02-sticky-writes.php)
392+
- [Example: Load Balancers](examples/15-read-write-splitting/03-load-balancers.php)
393+
317394
---
318395

319396
## Quick Start

0 commit comments

Comments
 (0)