allnetru/laravel-sharding
A Laravel package for horizontal database sharding. Route queries and Eloquent models across multiple database connections by shard key, manage sharded migrations, and scale large datasets while keeping a familiar Laravel workflow.
This package shards keyed queries in Laravel: a query that filters on the shard key (e.g. where('user_id', 5)) can be routed to a single shard, while an unkeyed query fans out and reads every shard.
pin_by_key.user_id, tenant_id, account_id — and route that model through the package. Keep the rest of your models on the default connection until the sharded path is proven.readsOneShard() is the internal predicate behind both behaviours; understanding it early explains most of the package's rules.The canonical first query is a keyed count or fetch:
User::on('shard')->where('user_id', 5)->count();
If that returns from a single shard, your setup is correct.
Keyed queries as the default. Treat every sharded query as keyed until you have a reason otherwise. Passing the shard key into the where() clause is what allows the package to pin the query to one shard, and it is the only shape that reliably supports grouped, distinct, and having aggregates.
Aggregates on keyed queries. As of v0.6.4, a distinct, grouped, or having aggregate on a keyed query is answered from its single shard:
// Now works: key names one shard, nothing to combine
User::where('user_id', 5)->distinct()->count('letter_id');
// Also taken from the shard whole, not as sum/count pairs
User::where('user_id', 5)->groupBy('team_id')->avg('score');
Previously both of these threw «cannot be combined across shards» because the refusal looked only at query shape. Now it consults readsOneShard() — the same check the raw-aggregate refusal uses — so the refusal only fires when the query genuinely spans shards.
Fan-out is a deliberate choice, not an accident. An unkeyed aggregate is still refused. If you need a cross-shard total, compute it explicitly — a queued job that iterates shards and sums the results, or a materialised rollup table — rather than expecting the package to add things up for you. Aggregates that are not additive across shards cannot be reconstructed from per-shard answers anyway.
Plan around pin_by_key. When pin_by_key is on, keyed queries stay pinned. When it is off, a keyed query fans out again and the multi-shard refusal comes back. Likewise, during a rebalance a keyed query may touch more than one shard, and the refusal correctly reapplies. Code that assumes "keyed means single shard" must tolerate these windows.
Grouped aggregates should be fetched whole. A grouped avg() taken as a sum and a count from two separate queries can mix groups — the two result sets need not contain the same groups in the same order. v0.6.4 answers it from the shard as a single operation; prefer that path and avoid hand-rolling sum/count pairs against grouped queries.
Integration tips. Route report queries through a service class that explicitly declares its shard scope. Where a query must span shards, isolate it in one place with a comment explaining why, so the next developer does not assume it is keyed.
readsOneShard() semantics rather than reading the SQL.pin_by_key off changes aggregate behaviour. Tests written with pinning enabled can pass while production, with pinning off, throws the multi-shard refusal. Assert against the real config.having aggregates — manual per-shard loops, DB::connection() escapes — they can likely be removed. Re-run your aggregate tests with pinning both on and off, and again under a simulated rebalance, since the new behaviour is conditional on all three.How can I help you explore Laravel packages today?