kefyusuf / laravel-shard
Modular database sharding for Laravel: deterministic shard routing with optional Redis map and shard-aware queues.
Requires
- php: ^8.2
- illuminate/database: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- illuminate/queue: ^10.0|^11.0|^12.0|^13.0
- illuminate/redis: ^10.0|^11.0|^12.0|^13.0
- laravel/pulse: ^1.8
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^10.5|^11.5|^12.0
- predis/predis: ^2.0
Suggests
- illuminate/queue: Required when the queue module is enabled (shard-aware jobs)
- illuminate/redis: Required when the redis module is enabled (persistent shard map)
- predis/predis: Required when the redis module is enabled (persistent shard map)
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-15 10:21:50 UTC
README
A modular shard locator for Laravel applications that provides deterministic shard routing for shard-key-aware Eloquent workflows. Redis-backed maps and shard-aware queues are optional modules.
Measured (2026-10-15): 65 *Test.php files · 6 releases, latest v5.0.1 · CI: Tests (276 tests, 725 assertions, 1 skipped on the PHP 8.2–8.4 × Laravel 10–13 matrix), Docker Tests, Code Quality, Consumer Smoke · 5 ADRs in docs/adr/ holding the routing, bucket-count, write-fence, cross-shard-commit and replica-selection decisions · mutation score: not measured. Production status is governed by docs/PRODUCTION_ROADMAP.md, whose R1–R6 risk list is the current gate, not this feature list.
✨ Features
- Deterministic Shard Routing: Route shard-key-aware reads and writes to the correct shard
- Redis-Based Lookup: Persistent key-to-shard mappings stored in Redis
- Multiple Strategies: Support for Modulo, Consistent Hashing, Range-based, and Virtual Bucket sharding
- Request Routing: Route middleware and shard-aware builders for single-shard operations
- Model Integration: Easy integration using the
Shardabletrait - Performance Monitoring: Built-in monitoring and health checks
- Cross-Shard Operations: Support for cross-shard queries and aggregations
- Laravel Integration: Native Laravel service provider with Artisan commands
- Octane Compatible: Locator state is flushed between Octane worker requests automatically
- Laravel Pulse Ready: Shard routing distribution recorded and visualized on the Pulse dashboard when Pulse is installed
- Tenancy Bridge: Use
stancl/tenancyorspatie/laravel-multitenancywith the tenant id as shard key (REDIS_SHARD_TENANCY_DRIVER) - Read Replicas: Map per-shard replica connections — reads go to the replica, writes stay on the shard
- Cross-Shard Transactions:
ShardManager::transaction()coordinates begin/commit/rollback across shards (best-effort, documented) - Write-Fenced Rebalancing: resumable row moves with source-change detection and conditional deletion; virtual buckets limit planned moves to the assigned buckets
📋 Requirements
- PHP 8.2, 8.3, or 8.4
- Laravel 10.x, 11.x, 12.x, or 13.x
- Multiple database connections configured
Optional modules
| Module | Default | Needs | Provides |
|---|---|---|---|
core |
always on | — | strategies, Shardable, builders, CLI |
redis |
on | predis + illuminate/redis |
persistent key→shard map (RedisShardLocator) |
queue |
off | illuminate/queue |
shard-aware jobs (ShardAwareJob, RestoreShardContext) |
With redis disabled the package falls back to ArrayShardLocator (process-local map) and deterministic strategy routing. No Redis server is required.
// config/redis_sharding.php 'modules' => [ 'core' => true, 'redis' => env('REDIS_SHARD_MODULE_REDIS', true), 'queue' => env('REDIS_SHARD_MODULE_QUEUE', false), ],
📚 Documentation
- Quick Start Guide - Get started in 5 minutes
- API Reference - 22 sections:
ShardManager,ShardLocator, theShardable/CrossShardQueryabletraits, the strategies, five of the ten registered artisan commands (shard:create,shard:status,shard:health,shard:analyze,shard:rebalance), configuration and exceptions - Rebalance Operations - Dry-run, gradual moves, idempotent rebalance, write fencing, virtual buckets
- Cross-Shard Transactions -
ShardManager::transaction()semantics and reconciliation patterns - Migration Guide - Migrate existing applications
- Performance Guide - Optimization and benchmarks
- Release Runbook - Package + consumer release checklist
- Production Roadmap - Architecture comparison, production readiness gaps, milestones, and rollout gates
- Example Application - Multi-tenant SaaS example
- Load Testing - Performance testing tools
🚀 Installation
Install the package via Composer:
composer require kefyusuf/laravel-shard
The package will automatically register its service provider thanks to Laravel's package auto-discovery.
Publish the configuration file:
php artisan vendor:publish --provider="Laravel\RedisShard\RedisShardServiceProvider" --tag="config"
Run the migrations:
php artisan migrate
🐳 Local Development (Docker)
The repository ships with a Docker stack that bundles PHP (with the redis extension via PECL), Composer, and a Redis 7 service so you can run the full test suite without installing anything on the host.
make build # build the PHP image (defaults to PHP 8.3) make install # install composer dependencies inside the container make test # run PHPUnit make phpstan # run PHPStan static analysis make cs # run php-cs-fixer in dry-run mode make matrix # build and test against PHP 8.2, 8.3, 8.4
Override the PHP version via PHP_VERSION (e.g. make PHP_VERSION=8.4 test).
⚙️ Configuration
After publishing the config file, configure your shards in config/redis_sharding.php:
return [ // Optional modules — core is always on 'modules' => [ 'core' => true, 'redis' => env('REDIS_SHARD_MODULE_REDIS', true), // persistent key→shard map 'queue' => env('REDIS_SHARD_MODULE_QUEUE', false), // shard-aware queued jobs ], 'redis_connection' => 'default', 'default_strategy' => 'consistent_hashing', 'strategies' => [ 'modulo' => Laravel\RedisShard\Strategies\ModuloStrategy::class, 'consistent_hashing' => Laravel\RedisShard\Strategies\ConsistentHashingStrategy::class, 'range_based' => Laravel\RedisShard\Strategies\RangeBasedStrategy::class, ], 'connections' => [ 'shard1' => [ 'driver' => 'mysql', 'host' => '127.0.0.1', 'port' => '3306', 'database' => 'shard_1', 'username' => 'root', 'password' => 'secret', ], 'shard2' => [ 'driver' => 'mysql', 'host' => '127.0.0.1', 'port' => '3306', 'database' => 'shard_2', 'username' => 'root', 'password' => 'secret', ], // Add more shards as needed ], ];
Important Constraints
- Shard-aware query routing is deterministic only when the builder can resolve a single shard from the shard key, currently via explicit shard connection binding,
find, shard-key=predicates, and shard-keywhereIn(...)predicates. - Queries that cannot be routed safely now fail fast instead of silently falling back to the default connection.
- Authoritative key-to-shard mappings are persisted in Redis and no longer expire automatically.
- Auto-increment primary keys require an explicit shard key value, request shard binding, or explicit connection before insert.
modulois suitable only for fixed shard topologies. Adding or removing shards withmodulorequires planned data migration.
Known limitations
- The default rebalance mover moves one row per key. Use an actual database unique constraint on the key in
rebalance.table_key_columns; more than one matching source row causes an exception before any copy or delete. This runtime check does not establish schema uniqueness or prevent concurrent duplicate inserts. A non-unique tenant key does not define a supported tenant-wide move. - With write fencing enabled, source deletion compares all copied column values in a single conditional
DELETE. Strings use bytewise comparisons on SQLite, MySQL/MariaDB, and PostgreSQL; numeric and null values use SQL equality andIS NULL. SQLite does not useSELECT ... FOR UPDATE. An unsupported driver's string snapshot is rejected before copying when source deletion and fencing are enabled. Binary/blob and other unsupported column types may need a custom mover; SQLite regression tests do not validate every production database or type. - Rebalance is not a distributed transaction or an online resharding protocol. A fence retains the source row, but the command can still update its mapping to the target. Quiesce writes during moves and reconcile retained source rows and both shard copies before cleanup; retries can overwrite target values.
- Disabling write fencing removes the source-change check. Cross-shard transactions remain best-effort and cannot guarantee atomic commits across databases.
- A queued job with a table and key uses the current locator mapping when one exists and never writes its captured mapping back to the locator. If the mapping is missing, or the context has no key, it retains its captured connection and cannot detect a moved record. The selected connection must still be configured.
See Rebalance Operations for the move and retry behavior, and Production Roadmap for the remaining production readiness work.
Supported Query Patterns
| Pattern | Behavior |
|---|---|
Model::find($id), findMany([...]) |
Routes by primary key when the shard key is the primary key |
where($shardKey, '=', $value) |
Routes to one shard |
whereIn($shardKey, [...]) |
Routes to one or more shards and merges results when needed |
get, first, count, exists, value, pluck |
Shard-aware when the query is deterministic |
paginate, simplePaginate, chunk |
Shard-aware when the query is deterministic |
update, delete, touch, increment, decrement |
Shard-aware when the query is deterministic |
upsert |
Shard-aware when every row contains a resolvable shard key value |
Fail-Fast Query Patterns
- Queries without an explicit shard connection or shard-key predicate
- Shard-key predicates using unsupported operators such as
whereBetween(...) - Queries that mix shard-key routing with
orWhere(...) upsert(...)payloads that omit the shard key or contain unresolvable shard-key values
These paths now throw ShardingException instead of silently using the default connection.
Locator Fallback Store
If you want shard lookups to survive a Redis outage beyond the current PHP process, configure redis_sharding.locator.fallback_store with a Laravel cache store that is independent from Redis.
- Recommended:
databaseorfile - Acceptable: any shared non-Redis cache backend with separate failure characteristics
- Not useful for outage isolation: a cache store backed by the same Redis cluster
- Test-only:
array, because it is process-local
Example:
'locator' => [ 'local_cache_limit' => 10000, 'circuit_breaker_seconds' => 5, 'fallback_store' => env('REDIS_SHARD_LOCATOR_FALLBACK_STORE', 'database'), ],
Health / metrics endpoint
Enable a JSON health probe for load balancers and uptime checks (no Redis required):
// config/redis_sharding.php 'metrics' => [ 'enabled' => env('REDIS_SHARD_METRICS', true), 'path' => env('REDIS_SHARD_METRICS_PATH', '/shard-health'), 'middleware' => ['web'], // protect in production ],
curl -s https://app.example.com/shard-health
# {"status":"ok","summary":{"total":2,"up":2,"down":0}, ...}
Status is ok (all shards reachable), degraded (partial), or down (none reachable).
Add ?detail=1 for a full diagnostics payload (latency, distribution balance, modules, issues), or use the console:
php artisan shard:report # table
php artisan shard:report --format=json
Programmatic access:
use Laravel\RedisShard\Metrics\ShardHealthReport; use Laravel\RedisShard\Metrics\ShardDiagnosticReport; $light = app(ShardHealthReport::class)->toArray(); $full = app(ShardDiagnosticReport::class)->toArray();
🎯 Quick Start
The steps below match the package consumer smoke test (examples/smoke.php), which installs this package into a fresh Laravel app on every CI run.
1. Install
composer require kefyusuf/laravel-shard
php artisan vendor:publish \
--provider="Laravel\RedisShard\RedisShardServiceProvider" \
--tag=config
2. Enable only the modules you need
// config/redis_sharding.php 'modules' => [ 'core' => true, // always on: strategies + Shardable + builders 'redis' => false, // true → persistent key→shard map in Redis 'queue' => true, // shard-aware queued jobs ], 'connections' => [ 'shard1' => ['driver' => 'sqlite', 'database' => database_path('shard1.sqlite')], 'shard2' => ['driver' => 'sqlite', 'database' => database_path('shard2.sqlite')], ],
With redis disabled the package uses ArrayShardLocator and deterministic strategy routing — no Redis server required.
3. Add sharding to your models
Use the Shardable trait and override getShardKeyName() (do not redeclare $shardKey; the trait already defines it):
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Laravel\RedisShard\Traits\Shardable; class User extends Model { use Shardable; protected $fillable = ['name', 'email', 'password']; public function getShardKeyName(): string { return 'email'; // defaults to primary key when omitted } }
4. Use shard-aware queries
// Create a user - routed using the shard key value $user = User::create([ 'name' => 'John Doe', 'email' => 'john@example.com', 'password' => bcrypt('password'), ]); // Single-shard lookup by shard key $user = User::where('email', 'john@example.com')->first(); // Single-shard bulk lookup by shard key $users = User::whereIn('email', ['john@example.com', 'jane@example.com'])->get(); // Resolve the shard connection directly $shardConnection = ShardManager::getShardConnection('users', 'john@example.com');
5. Dispatch shard-aware jobs (queue module)
use App\Jobs\SyncUserProfile; use function Laravel\RedisShard\Queue\dispatchSharded; dispatchSharded(new SyncUserProfile(), $user); // or SyncUserProfile::dispatchSharded($user);
RestoreShardContext job middleware rebinds the shard connection on the worker before handle() runs. For a context with a table and key, it uses the current locator mapping when one exists instead of the connection captured at dispatch. It never registers or recreates that mapping. If the mapping is missing, or the context has no key, it uses the captured connection; this fallback cannot detect migration. The selected connection must be configured.
6. Use middleware for request routing
Route::get('/users/{email}', 'UserController@show') ->middleware('shard:users,email'); // Group routes with shard middleware Route::middleware(['shard:users,email'])->group(function () { Route::get('/users/{email}/profile', 'UserController@profile'); Route::get('/users/{email}/orders', 'UserController@orders'); Route::put('/users/{email}', 'UserController@update'); });
7. Verify the wiring
php vendor/kefyusuf/laravel-shard/examples/smoke.php /path/to/your-app
# smoke ok shard=shard1 locator=Laravel\RedisShard\Locators\ArrayShardLocator
🔧 Sharding Strategies
The package supports four different sharding strategies:
1. Modulo Strategy
Simple modulo-based distribution. Use only when the shard list is effectively static.
'default_strategy' => 'modulo',
Pros: Simple, predictable distribution Cons: Adding/removing shards remaps most keys and requires explicit data migration
2. Consistent Hashing Strategy
Uses consistent hashing algorithm for better distribution when shards are added/removed.
'default_strategy' => 'consistent_hashing',
Pros: Minimal data movement when scaling, good distribution Cons: A rehash still displaces ~half the keys; resharding is all-or-nothing per run
3. Range-Based Strategy
Distributes data based on key ranges.
'default_strategy' => 'range_based',
Pros: Good for time-series or sequential data Cons: Can create hotspots if data isn't evenly distributed
4. Virtual Bucket Strategy (Recommended for resharding)
A key hashes into a fixed bucket (crc32 % N, default 1024) and a persistent bucket→shard map decides placement. Buckets never move implicitly — you move them explicitly and rebalance carries only that bucket's keys (~1/N of the data per move).
'default_strategy' => 'virtual_bucket', 'virtual_buckets' => ['count' => env('REDIS_SHARD_VBUCKETS', 1024)],
php artisan shard:bucket-status # distribution overview php artisan shard:bucket 512 shard2 # move bucket 512 to shard2 php artisan shard:rebalance users # carry only that bucket's keys
See docs/REBALANCE.md for the full workflow.
🎛️ Artisan Commands
Shard Management
# Create a new shard with validation php artisan shard:create shard3 --driver=mysql --host=127.0.0.1 --port=3306 --database=shard_3 --username=root --password=secret # Install package (run migrations and setup) php artisan redis-shard:install # Rebalance data across shards (dry-run first, then --limit=N for gradual moves) php artisan shard:rebalance users --dry-run php artisan shard:rebalance users --limit=100 php artisan shard:rebalance users
Virtual Bucket Management
# Bucket distribution overview (table or --format=json) php artisan shard:bucket-status # Assign a virtual bucket to a shard (then rebalance to move its data) php artisan shard:bucket 512 shard2
Monitoring & Analysis
# Check overall shard status php artisan shard:status # Check specific table distribution php artisan shard:status --table=users # Check specific shard details php artisan shard:status --shard=shard1 # Output in JSON format php artisan shard:status --format=json
Health Monitoring
# Check shard health php artisan shard:health # Check health and auto-fix issues php artisan shard:health --fix # Output health report in JSON php artisan shard:health --format=json
Performance Analysis
# Analyze overall distribution php artisan shard:analyze # Analyze specific table php artisan shard:analyze users # Test specific strategy performance php artisan shard:analyze --strategy=consistent_hashing --sample-size=10000 # Output analysis in JSON php artisan shard:analyze --format=json
JSON Output Quick Reference
All commands that support output formatting only accept --format=table or --format=json.
php artisan redis-shard:install --skip-migrate --format=json php artisan shard:create shard4 --driver=sqlite --host=localhost --port=1 --database=database/shard4.sqlite --username=ignored --skip-migrate --format=json php artisan shard:status --format=json php artisan shard:cleanup --dry-run --format=json php artisan shard:health --format=json php artisan shard:analyze --format=json php artisan shard:rebalance users --dry-run --format=json
Common payload conventions:
summary.status:ok,error, or command-specific lifecycle value (dry_run,completed, etc.)error: Present when command fails in JSON mode- Detail blocks by command:
install:summary.published_config,summary.ran_migrations,summary.skipped_migrationscreate:summary.shard,summary.driver,summary.database,summary.createdstatus:summary+shardsortablesdetail arrays depending on optionscleanup:summary+reporthealth:summary+issues(+fixwhen--fixis used)analyze:summary+results/analysisrebalance:summary(+movesin dry-run mode)
📊 Monitoring & Performance
Built-in Monitoring System
ShardMonitor has two methods, collectMetrics() and getMetrics(), and returns the per-shard structure below:
use Laravel\RedisShard\Monitoring\ShardMonitor; $monitor = app(ShardMonitor::class); $metrics = $monitor->collectMetrics(); // Structure returned by collectMetrics() $metrics = [ 'timestamp' => '2024-01-15T10:30:00Z', 'shards' => [ 'shard1' => [ 'status' => 'active', 'record_count' => 15000, 'connection_status' => 'healthy', 'disk_usage' => ['size_mb' => 245.6, 'table_count' => 12], 'query_performance' => ['avg_query_time_ms' => 2.3, 'status' => 'fast'] ] ], 'distribution' => [ 'users' => [ 'total_keys' => 45000, 'balance_score' => 94.2, 'std_deviation' => 1.8 ] ], 'performance' => [ 'redis_latency' => 0.8, 'shard_lookup_time' => 1.2, 'cache_hit_rate' => 89.5 ], 'health' => [ 'overall_health' => 'healthy', 'healthy_shards' => 3, 'total_shards' => 3, 'health_percentage' => 100.0 ] ];
Advanced Health Monitoring
// Check specific shard connectivity $isHealthy = $monitor->checkShardConnection('shard1'); // 'healthy', 'slow', 'unhealthy' // Get detailed performance metrics $performance = $monitor->measureQueryPerformance('shard1'); // Monitor Redis connectivity $redisStatus = $monitor->checkRedisHealth(); // 'healthy', 'unhealthy'
Caching & Performance Optimization
use Laravel\RedisShard\Cache\ShardCache; $cache = app(ShardCache::class); // Cache shard connections for faster lookups $cache->putShardConnection('users', 'john@example.com', 'shard1'); $connection = $cache->getShardConnection('users', 'john@example.com'); // Cache metadata for performance $cache->putShardMetadata('shard1', $metadata); // Get cache statistics $stats = $cache->getStats();
🔄 Advanced Usage
Cross-Shard Query Builder
The CrossShardQueryable trait adds 12 static methods — crossShard, searchAcrossShards, findAcrossShards, countAcrossShards, aggregateAcrossShards, searchLikeAcrossShards, recentAcrossShards, paginateAcrossShards, batchUpdateAcrossShards, batchDeleteAcrossShards, getShardDistribution, executeOnAllShards:
// Use the CrossShardQueryable trait in your models use Laravel\RedisShard\Traits\Shardable; use Laravel\RedisShard\Traits\CrossShardQueryable; class User extends Model { use Shardable, CrossShardQueryable; } // Simple cross-shard queries $activeUsers = User::crossShard()->where('status', 'active')->get(); $recentUsers = User::crossShard()->orderBy('created_at', 'desc')->limit(10)->get(); // Cross-shard aggregations $totalUsers = User::crossShard()->count(); $averageSalary = User::crossShard()->avg('salary'); $maxAge = User::crossShard()->max('age'); // Complex queries with multiple conditions $results = User::crossShard() ->where('status', 'active') ->whereLike('name', '%john%') ->orderBy('created_at', 'desc') ->limit(50) ->get();
Convenient Cross-Shard Methods
// Search across all shards $users = User::searchAcrossShards('email', 'john@example.com'); // Find first match across shards $user = User::findAcrossShards('email', 'john@example.com'); // Get aggregated statistics $stats = User::aggregateAcrossShards('salary'); // Returns: ['count' => 1000, 'sum' => 50000, 'avg' => 50, 'min' => 20, 'max' => 100] // Search with LIKE pattern $users = User::searchLikeAcrossShards('name', '%john%'); // Get recent records across shards $recentUsers = User::recentAcrossShards(20, 'created_at'); // Paginate across shards $paginated = User::paginateAcrossShards(1, 15); // Returns: ['data' => Collection, 'total' => 1000, 'per_page' => 15, ...]
Batch Operations Across Shards
// Batch update across all shards $updatedCount = User::batchUpdateAcrossShards( ['status' => 'inactive'], // conditions ['status' => 'archived'] // updates ); // Batch delete across shards $deletedCount = User::batchDeleteAcrossShards(['status' => 'spam']); // Get shard distribution $distribution = User::getShardDistribution(); // Returns detailed distribution with counts and percentages per shard // Execute custom operations on all shards $results = User::executeOnAllShards(function ($model, $shard) { return $model->where('created_at', '>', now()->subDays(7))->count(); });
Manual Shard Selection
// Create user on specific shard $user = new User(['name' => 'John', 'email' => 'john@example.com']); $user->setConnection('shard2'); $user->save(); // Query specific shard directly $users = User::on('shard1')->where('status', 'active')->get(); // Get shard information for a model $user = User::find(1); $shardInfo = $user->getShardInfo(); // Returns: ['shard_connection' => 'shard1', 'shard_key' => 'email', 'shard_key_value' => 'john@example.com']
🛡️ Configuration Validation
ConfigValidator::validate() runs six checks over the redis_sharding config array and is called from the service provider during boot whenever connections is non-empty, in every environment including testing; strict_validation (default true) decides whether a failure throws ConfigurationException or only warns:
use Laravel\RedisShard\Validation\ConfigValidator; // Automatic validation on service provider boot // Manual validation try { ConfigValidator::validate(config('redis_sharding')); } catch (\Laravel\RedisShard\Exceptions\ConfigurationException $e) { echo $e->getMessage(); } // Get configuration recommendations $recommendations = ConfigValidator::getRecommendations(config('redis_sharding')); foreach ($recommendations as $recommendation) { echo $recommendation['type'] . ': ' . $recommendation['message']; }
What each validator checks
validateRedisConnection()— the Redis connection entryvalidateStrategies()— the strategy classes and the interfaces they implementvalidateConnections()andvalidateConnectionConfig()— every named connection and its own settingsvalidateAutoProvisioning()— the auto-provisioning blockvalidateCacheTtl()— the cache TTLgetRecommendations()— returns one['type' => …, 'message' => …]entry per finding
🧪 Testing
65 *Test.php files: 40 under tests/Unit, 23 under tests/Feature, 2 under tests/Integration. The Tests workflow runs them across PHP 8.2–8.4 and Laravel 10–13 and reports 276 tests, 725 assertions, 1 skipped (read from its log on 2026-10-15).
Test Suites
# Run all tests (276 in the CI matrix; needs a local Redis — the predis lanes error without one) vendor/bin/phpunit # Run a specific suite vendor/bin/phpunit --testsuite=Unit # Run with coverage vendor/bin/phpunit --coverage-html coverage
Test Categories
Unit Tests:
ModuloStrategyTest- Tests modulo distribution algorithmConsistentHashingStrategyTest- Tests consistent hashing with shard changesRangeBasedStrategyTest- Tests range-based distribution
Integration Tests:
ShardManagerTest- Tests shard management functionalityShardLocatorTest- Tests Redis-based shard location- Cross-component interaction testing
Feature Tests:
CreateShardCommandTest- Tests shard creation command- End-to-end workflow testing
- Command validation and error handling
Testing Your Implementation
// Test shard distribution $distribution = []; for ($i = 1; $i <= 1000; $i++) { $shard = ShardManager::getShardConnection('users', $i); $distribution[$shard] = ($distribution[$shard] ?? 0) + 1; } // Test cross-shard queries $totalUsers = User::crossShard()->count(); $shardCounts = User::getShardDistribution(); // Test health monitoring $monitor = app(\Laravel\RedisShard\Monitoring\ShardMonitor::class); $health = $monitor->collectMetrics()['health'];
🏗️ Real-World Examples
E-commerce Application
// Product model with sharding by category class Product extends Model { use Shardable; protected ?string $shardKey = 'category_id'; } // Order model sharded by user class Order extends Model { use Shardable; protected ?string $shardKey = 'user_email'; public function user() { // Cross-shard relationship return User::findAcrossShards('email', $this->user_email); } } // Get sales analytics across all shards $analytics = Order::crossShard() ->where('created_at', '>=', now()->subDays(30)) ->selectRaw('DATE(created_at) as date, SUM(total) as daily_total') ->groupBy('date') ->get();
Multi-Tenant SaaS Application
// Tenant-based sharding class TenantUser extends Model { use Shardable; protected ?string $shardKey = 'tenant_id'; } // Route with tenant-aware sharding Route::middleware(['shard:tenant_users,tenant_id'])->group(function () { Route::get('/tenant/{tenant_id}/users', 'TenantController@users'); Route::post('/tenant/{tenant_id}/users', 'TenantController@createUser'); }); // Cross-tenant analytics (admin only) $tenantStats = TenantUser::executeOnAllShards(function ($model, $shard) { return [ 'shard' => $shard, 'active_users' => $model->where('status', 'active')->count(), 'total_users' => $model->count(), ]; });
Social Media Platform
// Posts sharded by user class Post extends Model { use Shardable, CrossShardQueryable; protected ?string $shardKey = 'user_id'; } // Get trending posts across all shards $trendingPosts = Post::crossShard() ->where('created_at', '>=', now()->subHours(24)) ->where('likes_count', '>', 100) ->orderBy('likes_count', 'desc') ->limit(50) ->get(); // User feed with cross-shard data $userFeed = Post::crossShard() ->whereIn('user_id', $followingUserIds) ->orderBy('created_at', 'desc') ->limit(20) ->get();
📋 Best Practices
1. Choosing the Right Shard Key
// ✅ Good: High cardinality, evenly distributed protected ?string $shardKey = 'user_email'; protected ?string $shardKey = 'user_id'; // ❌ Avoid: Low cardinality, uneven distribution protected ?string $shardKey = 'status'; // Only few values protected ?string $shardKey = 'country'; // Uneven distribution
2. Monitoring and Maintenance
// Regular health checks Schedule::command('shard:health --fix')->hourly(); // Monitor distribution balance Schedule::command('shard:analyze --format=json')->daily(); // Collect metrics for monitoring systems $metrics = app(\Laravel\RedisShard\Monitoring\ShardMonitor::class)->collectMetrics();
3. Handling Relationships
// Keep related data on the same shard class User extends Model { use Shardable; protected ?string $shardKey = 'email'; public function profile() { // Profile uses same shard key return $this->hasOne(UserProfile::class, 'user_email', 'email'); } } // For cross-shard relationships, use explicit queries public function orders() { return Order::searchAcrossShards('user_email', $this->email); }
4. Performance Optimization
// Use caching for frequently accessed data $user = Cache::remember("user:{$email}", 3600, function () use ($email) { return User::findAcrossShards('email', $email); }); // Batch operations for efficiency $results = User::batchUpdateAcrossShards( ['status' => 'inactive'], ['last_activity' => now()] );
🤝 Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Setup
# Clone the repository git clone https://github.com/kefyusuf/laravel-shard.git # Install dependencies composer install # Run tests vendor/bin/phpunit # Check code style vendor/bin/php-cs-fixer fix --dry-run
🔧 Troubleshooting
Common Issues
1. Configuration Validation Errors
# Check configuration php artisan shard:health # Common fix: Ensure all required fields are present 'connections' => [ 'shard1' => [ 'driver' => 'mysql', 'host' => '127.0.0.1', // Required 'port' => '3306', // Required 'database' => 'shard_1', // Required 'username' => 'root', // Required 'password' => 'secret', ], ],
2. Redis Connection Issues
# Test Redis connectivity redis-cli ping # Check Laravel Redis configuration php artisan tinker >>> app('redis')->ping()
3. Shard Distribution Problems
# Analyze current distribution php artisan shard:analyze users # Rebalance if needed php artisan shard:rebalance users --strategy=consistent_hashing
4. Performance Issues
# Check shard performance
php artisan shard:health
Debug Mode
// Enable debug logging config(['app.debug' => true]); // Check shard assignment $shard = ShardManager::getShardConnection('users', 'test@example.com'); logger()->info('User assigned to shard', ['shard' => $shard]); // Monitor query performance DB::listen(function ($query) { logger()->info('Query executed', [ 'sql' => $query->sql, 'time' => $query->time, 'connection' => $query->connectionName, ]); });
Performance Tuning
// Monitor and adjust based on metrics $metrics = app(\Laravel\RedisShard\Monitoring\ShardMonitor::class)->collectMetrics();
📚 Additional Resources
- Examples Directory - 6 files:
User.php,UserController.php,routes/web.php,LoadTestingExample.php,smoke.php,ExampleApplication.md - Test Suite - 65
*Test.phpfiles; CI lanes: Tests, Docker Tests, Code Quality, Consumer Smoke - Configuration Reference - the options the package actually reads
- API Documentation -
docs/API.md(22 sections) plus the topic docs listed above
🆕 Changelog
See CHANGELOG.md for the full release history (latest: v5.0.1 — rebalance source-key validation, conditional source deletion, and current-mapping restoration for keyed queued jobs).
📝 License
This package is open-sourced software licensed under the MIT license.
🙏 Acknowledgments
- Laravel Framework for the excellent foundation
- Redis for fast key-value storage
- The PHP community for continuous inspiration
Made for the Laravel community.
For questions, issues, or contributions, please visit our GitHub repository.