Search by

kefyusuf / laravel-shard

kefyusuf

Modular database sharding for Laravel: deterministic shard routing with optional Redis map and shard-aware queues.

Package info

github.com/kefyusuf/laravel-shard

pkg:composer/kefyusuf/laravel-shard

Statistics

Installs: 69

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v5.0.1 2026-10-03 17:07 UTC

README

Tests Code Quality Latest Stable Version Total Downloads License PHP Version Require

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 Shardable trait
  • 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/tenancy or spatie/laravel-multitenancy with 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

🚀 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-key whereIn(...) 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.
  • modulo is suitable only for fixed shard topologies. Adding or removing shards with modulo requires 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 and IS NULL. SQLite does not use SELECT ... 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: database or file
  • 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_migrations
    • create: summary.shard, summary.driver, summary.database, summary.created
    • status: summary + shards or tables detail arrays depending on options
    • cleanup: summary + report
    • health: summary + issues (+ fix when --fix is used)
    • analyze: summary + results/analysis
    • rebalance: summary (+ moves in 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 entry
  • validateStrategies() — the strategy classes and the interfaces they implement
  • validateConnections() and validateConnectionConfig() — every named connection and its own settings
  • validateAutoProvisioning() — the auto-provisioning block
  • validateCacheTtl() — the cache TTL
  • getRecommendations() — 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 algorithm
  • ConsistentHashingStrategyTest - Tests consistent hashing with shard changes
  • RangeBasedStrategyTest - Tests range-based distribution

Integration Tests:

  • ShardManagerTest - Tests shard management functionality
  • ShardLocatorTest - 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

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. 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.php files; 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.