Build Today Is Tomorrow's Legacy System
I know that sounds dramatic. But after almost a decade of building and maintaining web applications, I’ve seen it happen to every team that didn’t think about scale early.
The app that starts as a weekend MVP becomes the product that raises a Series A. The internal tool that handles 50 concurrent users suddenly needs to handle 5,000. The “we’ll optimize later” decision becomes a 3-month refactor that pushes your roadmap back a quarter.
Laravel 13 ships features that directly address this problem — before you hit the wall. The Reverb database driver, performance improvements from PHP 8.3, and the ecosystem tooling around benchmarking give you a solid foundation to build on. This final post in our series is about using them wisely.
Performance & Scaling
Real-Time Without Redis: The Reverb Database Driver
Before Laravel Reverb existed, teams had two options for WebSockets: pay for Pusher (great, but expensive at scale) or self-host their own WebSocket server (painful, complex). Then Laravel Reverb arrived as a first-party, self-hosted WebSocket server.
But Reverb still required Redis for horizontal scaling — sharing WebSocket connection state across multiple server instances. For many teams, especially smaller ones, this meant provisioning a separate Redis cluster just to get real-time features working across multiple nodes.
Laravel 13 ships a native database driver for Reverb, which means teams can implement WebSocket functionality without adding Redis to their infrastructure. For smaller applications and teams that want to keep their stack lean, this removes a real dependency.
How It Works
Configure the Reverb database driver in `config/broadcasting.php`:
// config/broadcasting.php — Laravel 13
'reverb' => [
'driver' => 'reverb',
'key' => env('REVERB_APP_KEY'),
'secret' => env('REVERB_APP_SECRET'),
'app_id' => env('REVERB_APP_ID'),
'options' => [
'host' => env('REVERB_HOST', 'localhost'),
'port' => env('REVERB_PORT', 8080),
'scheme' => env('REVERB_SCHEME', 'http'),
],
'scaling' => [
'driver' => 'database', // 👈 New in Laravel 13 — no Redis needed
],
],
# .env
BROADCAST_CONNECTION=reverb
REVERB_APP_ID=your_app_id
REVERB_APP_KEY=your_app_key
REVERB_APP_SECRET=your_app_secret
REVERB_HOST=localhost
REVERB_PORT=8080
REVERB_SCHEME=http
# Start the server:
# php artisan reverb:start
A Real-Time Notification Feature — End to End
// 1. Define the event — app/Events/OrderStatusUpdated.php
use Illuminate\Broadcasting\Channel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class OrderStatusUpdated implements ShouldBroadcast
{
public function __construct(
public readonly Order $order,
public readonly string $newStatus
) {}
public function broadcastOn(): Channel
{
return new Channel('orders.' . $this->order->user_id);
}
public function broadcastAs(): string
{
return 'order.updated';
}
}
// 2. Fire the event from your controller
public function updateStatus(Order $order, string $status): JsonResponse
{
$order->update(['status' => $status]);
broadcast(new OrderStatusUpdated($order, $status))->toOthers();
return response()->json(['status' => 'updated']);
}
// 3. Listen on the frontend — Laravel Echo + Reverb
import Echo from 'laravel-echo';
const echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT,
});
echo.channel(`orders.${userId}`)
.listen('.order.updated', (e) => {
console.log(`Order ${e.order.id} is now: ${e.newStatus}`);
updateOrderUI(e.order);
});
When to Use Database vs Redis for Reverb
| Scenario | Recommended Driver |
|---|---|
| Single server, ≤ 1,000 concurrent connections | Database ✅ |
| Multi-server deployment, moderate traffic | Database ✅ (Laravel 13) |
| High-throughput: 10,000+ concurrent connections | Redis (Redis is still recommended) |
| Teams wanting minimal infrastructure | Database ✅ |
| Laravel Cloud (managed) | Managed Reverb clusters |
For business leaders
Performance Tuning
Why Benchmarking Matters More Than Intuition
Built-In Benchmarking With `Benchmark::measure()`
use Illuminate\Support\Benchmark;
// Measure a single operation
$result = Benchmark::measure(fn () => User::with('orders')->get());
// Returns: [result, duration_in_ms]
// Compare multiple approaches
[$simpleTime, $eagerTime] = Benchmark::measure([
'simple' => fn () => User::all(),
'with_orders' => fn () => User::with('orders')->get(),
]);
// Output: ['simple' => 12.4ms, 'with_orders' => 31.2ms]
The Performance Audit Checklist for Laravel 13
Here’s the checklist I run on every production Laravel app before any scaling conversation:
Database Query Analysis
# Enable query logging in dev
DB::enableQueryLog();
// ... your operation
$queries = DB::getQueryLog();
// Sort by query time — find your slowest queries
N+1 Detection
// config/database.php — enable in development
'strict' => true, // Forces N+1 to throw exceptions in dev
// Or use Laravel Debugbar (package)
// or run: php artisan db:show for schema analysis
Eager Loading Audit
// Bad — N+1 queries
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name; // One query per post
}
// Good — 2 queries total
$posts = Post::with('author')->get();
foreach ($posts as $post) {
echo $post->author->name;
}
Cache Hit Rate Monitoring
// Track cache hits vs misses with Laravel Pulse (built-in monitoring)
php artisan pulse:check
// Or manually track in your service
$cached = Cache::get($key);
if (!$cached) {
metrics()->increment('cache.miss');
$cached = $this->expensiveOperation();
Cache::put($key, $cached, now()->addMinutes(15));
} else {
metrics()->increment('cache.hit');
}
Cache Hit Rate Monitoring
composer require laravel/octane
php artisan octane:install --server=frankenphp
php artisan octane:start --workers=4 --max-requests=500
Explore project snapshots or discuss custom web solutions.
Make it work, make it right, make it fast — in that order.
Thank You for Spending Your Valuable Time
I truly appreciate you taking the time to read blog. Your valuable time means a lot to me, and I hope you found the content insightful and engaging!
Frequently Asked Questions
Yes. The database driver is production-ready for small-to-medium applications. It uses your existing MySQL or PostgreSQL instance — the same database your app already depends on. For high-throughput apps (10,000+ concurrent connections), Redis remains the recommended scaling backend.
Laravel Pulse (first-party, free) for application metrics. Laravel Telescope for local/staging debugging. Cloudways Monitoring, Datadog, or New Relic for infrastructure-level visibility. The combination covers request performance, query analysis, and infrastructure health.
A well-tuned single server with Laravel Octane and PHP 8.3 can handle significant traffic. Consider horizontal scaling when your average response time exceeds 200ms under load, or when your database becomes the bottleneck rather than PHP. The Laravel Cloud platform handles auto-scaling transparently if you want to avoid managing this yourself.
Laravel Cloud offers fully managed WebSocket infrastructure powered by Laravel Reverb clusters. It's excellent for teams that want to minimize operations overhead. Self-hosting (via Forge + DigitalOcean/AWS) gives more control at potentially lower cost for teams with infrastructure expertise.
N+1 query prevention. It's the most common Laravel performance issue, it's easy to introduce accidentally, and fixing it with eager loading is one of the highest-ROI optimizations you can make. Use `DB::enableQueryLog()` in development to catch it. The Eloquent documentation at laravel.com/docs/13.x/eloquent-relationships covers eager loading in depth.
Comments are closed