bensedev/laravel-inflight-query-lock
Composer 安装命令:
composer require bensedev/laravel-inflight-query-lock
包简介
Deduplicate concurrent identical queries using distributed locks and async execution
README 文档
README
Deduplicate concurrent identical queries using distributed locks and async execution. When multiple requests trigger the same slow query simultaneously, only one executes while others wait for the cached result.
Origin Story
This package was born out of necessity during numerous migration projects where we encountered heavy database queries that couldn't be easily optimized through traditional means like pagination or query simplification. Time and resource constraints forced creative solutions, and this approach proved effective in production environments. Rather than keeping this solution to ourselves, we're sharing it with the community in hopes it helps others facing similar challenges.
The Problem
Imagine 100 concurrent requests all hitting an analytics dashboard that runs the same expensive query:
// Each request executes this independently $stats = Order::where('status', 'completed') ->whereBetween('created_at', [now()->subDays(30), now()]) ->with('customer', 'items') ->get();
Result: 100 identical queries hammer your database, causing:
- High database load
- Slow response times
- Potential timeouts
- Resource exhaustion
The Solution
With Inflight Query Lock:
$stats = Order::where('status', 'completed') ->whereBetween('created_at', [now()->subDays(30), now()]) ->with('customer', 'items') ->inflight(ttl: 600) // Cache for 10 minutes ->get();
Result:
- 1st request acquires lock and dispatches async job
- Job executes query and caches result
- Remaining 99 requests wait and receive the same cached result
- Only 1 database query executed
Features
- 🔒 Distributed locking - Prevents duplicate query execution across multiple servers
- ⚡ Async execution - Query runs in background job, freeing up web workers
- 🎯 Automatic deduplication - Identical queries are coalesced using unique hashes
- 💾 Redis-backed - Uses Redis for both caching and locking
- 🔄 Eloquent & Query Builder - Works with both Eloquent models and raw queries
- 📊 Optional logging - Track cache hits, misses, and query execution
Requirements
- PHP 8.4+
- Laravel 12.0+
- Redis (for distributed locking and caching)
Installation
Install via Composer:
composer require bensedev/laravel-inflight-query-lock
Publish the configuration file:
php artisan vendor:publish --tag=inflight-query-lock-config
Configuration
Configure in config/inflight-query-lock.php:
return [ // Cache store (must support locks, e.g., Redis) 'cache_store' => env('INFLIGHT_QUERY_CACHE_STORE', 'redis'), // Prefix for cache keys 'cache_prefix' => env('INFLIGHT_QUERY_CACHE_PREFIX', 'inflight_query'), // Maximum time to wait for query result (seconds) 'lock_timeout' => env('INFLIGHT_QUERY_LOCK_TIMEOUT', 30), // Time between cache polls (microseconds) 'poll_interval' => env('INFLIGHT_QUERY_POLL_INTERVAL', 100000), // 100ms // Queue for async query execution 'queue' => env('INFLIGHT_QUERY_QUEUE', 'default'), // Queue connection 'queue_connection' => env('INFLIGHT_QUERY_QUEUE_CONNECTION', null), // Default TTL for cached results (seconds) 'default_ttl' => env('INFLIGHT_QUERY_DEFAULT_TTL', 3600), // Enable logging 'enable_logging' => env('INFLIGHT_QUERY_ENABLE_LOGGING', false), ];
Environment Variables
Add to your .env:
INFLIGHT_QUERY_CACHE_STORE=redis INFLIGHT_QUERY_QUEUE=queries INFLIGHT_QUERY_LOCK_TIMEOUT=30 INFLIGHT_QUERY_ENABLE_LOGGING=true
Usage
With Eloquent Models
Add the trait to your model:
use Bensedev\LaravelInflightQueryLock\Traits\HasInflightLock; use Illuminate\Database\Eloquent\Model; class Order extends Model { use HasInflightLock; }
Use the inflight() method in queries:
// Cache for 10 minutes (600 seconds) $orders = Order::where('status', 'completed') ->inflight(ttl: 600) ->get(); // With relationships $orders = Order::with('customer', 'items') ->where('total', '>', 1000) ->inflight(ttl: 300) ->get(); // Complex queries $stats = Order::selectRaw('DATE(created_at) as date, COUNT(*) as count, SUM(total) as revenue') ->where('status', 'completed') ->groupBy('date') ->inflight(ttl: 3600) ->get();
Use Cases
1. Analytics Dashboards
// Heavy aggregation query $dashboardStats = Order::query() ->selectRaw(' COUNT(*) as total_orders, SUM(total) as revenue, AVG(total) as avg_order_value ') ->where('created_at', '>=', now()->subDays(30)) ->inflight(ttl: 600) // 10 minutes ->first();
2. Report Generation
// Expensive report query $report = Transaction::with(['user', 'product', 'invoice']) ->whereBetween('created_at', [$startDate, $endDate]) ->orderBy('created_at') ->inflight(ttl: 1800) // 30 minutes ->get();
3. Public API Endpoints
// Heavily-accessed public endpoint Route::get('/api/stats', function () { return Product::with('category') ->withCount('orders') ->having('orders_count', '>', 100) ->inflight(ttl: 300) // 5 minutes ->get(); });
4. Admin Panels
// Slow admin queries $users = User::with(['orders', 'subscriptions', 'payments']) ->whereHas('orders', fn ($q) => $q->where('status', 'pending')) ->inflight(ttl: 120) // 2 minutes ->get();
How It Works
- Query Hash Generation: Creates unique hash from SQL + bindings + connection
- Cache Check: Looks for existing cached result
- Lock Acquisition: Attempts to acquire distributed lock
- Async Execution: If lock acquired, dispatches job to execute query
- Polling: Requests poll cache until result is available
- Result Distribution: All requests receive the same cached result
Sequence Diagram
Request 1 → Check cache (miss) → Acquire lock → Dispatch job → Poll cache → Get result
Request 2 → Check cache (miss) → Lock busy → Poll cache → Get result
Request 3 → Check cache (miss) → Lock busy → Poll cache → Get result
...
Request 100 → Check cache (miss) → Lock busy → Poll cache → Get result
Background Job → Execute query → Store in cache → Release lock
Best Practices
1. Choose Appropriate TTL
// Short TTL for frequently changing data $recentOrders = Order::latest()->inflight(ttl: 60)->limit(10)->get(); // Long TTL for stable data $categories = Category::with('products')->inflight(ttl: 3600)->get();
2. Use Dedicated Queue
// config/queue.php 'connections' => [ 'redis' => [ 'driver' => 'redis', 'connection' => 'default', 'queue' => env('REDIS_QUEUE', 'default'), 'queues' => [ 'default' => ['driver' => 'redis'], 'queries' => ['driver' => 'redis'], // Dedicated for inflight queries ], ], ],
INFLIGHT_QUERY_QUEUE=queries
3. Enable Logging During Development
INFLIGHT_QUERY_ENABLE_LOGGING=true
Monitor logs for:
- Cache hits/misses
- Lock acquisition
- Query execution timing
4. Monitor Queue Workers
Ensure queue workers are running:
php artisan queue:work --queue=queries
Use Horizon for monitoring:
composer require laravel/horizon php artisan horizon:install php artisan horizon
Performance Considerations
When to Use
✅ Good candidates:
- Slow queries (> 500ms)
- High concurrency endpoints
- Analytics/reporting queries
- Public APIs with traffic spikes
❌ Avoid for:
- Simple queries (< 100ms)
- Real-time data requirements
- Low-traffic endpoints
- Write operations
Overhead
- Overhead per request: ~1-2ms (cache check + lock attempt)
- First request: Query execution time + job dispatch (~10-50ms)
- Subsequent requests: Poll interval × iterations (~100ms average)
Testing
composer test
Run PHPStan:
composer analyse
Format code:
composer format
Troubleshooting
Queries not being cached
- Check Redis connection
- Verify queue workers are running
- Enable logging to debug
- Check
cache_storeconfiguration
Timeout errors
Increase lock timeout:
INFLIGHT_QUERY_LOCK_TIMEOUT=60
High poll overhead
Adjust poll interval:
INFLIGHT_QUERY_POLL_INTERVAL=200000 # 200ms
Changelog
Please see CHANGELOG for recent changes.
Contributing
Contributions are welcome! Please see CONTRIBUTING for details.
Security
If you discover any security issues, please report them via the GitHub issue tracker with the "security" label.
Credits
License
The MIT License (MIT). Please see License File for more information.
bensedev/laravel-inflight-query-lock 适用场景与选型建议
bensedev/laravel-inflight-query-lock 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 3 次下载、GitHub Stars 达 16, 最近一次更新时间为 2025 年 10 月 13 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「performance」 「cache」 「query」 「laravel」 「lock」 「coalescing」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 bensedev/laravel-inflight-query-lock 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 bensedev/laravel-inflight-query-lock 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 bensedev/laravel-inflight-query-lock 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
g4 application profiler package
repository php library
CSS/Javascript Minificator, Compressor and Concatenator for TYPO3 - highly configurable frontend asset optimization for CSS/JS merging, minification and compression with optional body parsing, async/defer loading, inline output, data-ignore exclusions, SRI integrity validation/calculation, external
WordPress mu-plugin to remove jQuery Migrate from the list of jQuery dependencies and to allow jQuery to enqueue before </body> instead of in the <head>.
Create link to static resources with cache-breaking segment based on md5 of the file
Query filtering in your frontend
统计信息
- 总下载量: 3
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 16
- 点击次数: 26
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-10-13