PHPackages                             bensedev/laravel-inflight-query-lock - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Caching](/categories/caching)
4. /
5. bensedev/laravel-inflight-query-lock

ActiveLibrary[Caching](/categories/caching)

bensedev/laravel-inflight-query-lock
====================================

Deduplicate concurrent identical queries using distributed locks and async execution

v1.0.0(7mo ago)163[1 PRs](https://github.com/BenSeDev/laravel-inflight-query-lock/pulls)MITPHPPHP ^8.4

Since Oct 8Pushed 6mo agoCompare

[ Source](https://github.com/BenSeDev/laravel-inflight-query-lock)[ Packagist](https://packagist.org/packages/bensedev/laravel-inflight-query-lock)[ RSS](/packages/bensedev-laravel-inflight-query-lock/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (12)Versions (4)Used By (0)

Laravel Inflight Query Lock
===========================

[](#laravel-inflight-query-lock)

[![Latest Version on Packagist](https://camo.githubusercontent.com/ac97115238f447657ad21d72d22aa8b0ab4f9312b27c179eac2620cf3d65e896/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f62656e73656465762f6c61726176656c2d696e666c696768742d71756572792d6c6f636b2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/bensedev/laravel-inflight-query-lock)[![Total Downloads](https://camo.githubusercontent.com/a30992b24adbeef0ffd2a87c24f80c3e84dfe1cf8b4b8a3ed3bed6a73a33d1ce/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f62656e73656465762f6c61726176656c2d696e666c696768742d71756572792d6c6f636b2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/bensedev/laravel-inflight-query-lock)

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
------------

[](#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
-----------

[](#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
------------

[](#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
--------

[](#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 &amp; Query Builder** - Works with both Eloquent models and raw queries
- 📊 **Optional logging** - Track cache hits, misses, and query execution

Requirements
------------

[](#requirements)

- PHP 8.4+
- Laravel 12.0+
- Redis (for distributed locking and caching)

Installation
------------

[](#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
-------------

[](#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

[](#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
-----

[](#usage)

### With Eloquent Models

[](#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

[](#use-cases)

#### 1. Analytics Dashboards

[](#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

[](#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

[](#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

[](#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
------------

[](#how-it-works)

1. **Query Hash Generation**: Creates unique hash from SQL + bindings + connection
2. **Cache Check**: Looks for existing cached result
3. **Lock Acquisition**: Attempts to acquire distributed lock
4. **Async Execution**: If lock acquired, dispatches job to execute query
5. **Polling**: Requests poll cache until result is available
6. **Result Distribution**: All requests receive the same cached result

### Sequence Diagram

[](#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
--------------

[](#best-practices)

### 1. Choose Appropriate TTL

[](#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

[](#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

[](#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

[](#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
--------------------------

[](#performance-considerations)

### When to Use

[](#when-to-use)

✅ **Good candidates:**

- Slow queries (&gt; 500ms)
- High concurrency endpoints
- Analytics/reporting queries
- Public APIs with traffic spikes

❌ **Avoid for:**

- Simple queries (&lt; 100ms)
- Real-time data requirements
- Low-traffic endpoints
- Write operations

### Overhead

[](#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
-------

[](#testing)

```
composer test
```

Run PHPStan:

```
composer analyse
```

Format code:

```
composer format
```

Troubleshooting
---------------

[](#troubleshooting)

### Queries not being cached

[](#queries-not-being-cached)

1. Check Redis connection
2. Verify queue workers are running
3. Enable logging to debug
4. Check `cache_store` configuration

### Timeout errors

[](#timeout-errors)

Increase lock timeout:

```
INFLIGHT_QUERY_LOCK_TIMEOUT=60
```

### High poll overhead

[](#high-poll-overhead)

Adjust poll interval:

```
INFLIGHT_QUERY_POLL_INTERVAL=200000  # 200ms
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for recent changes.

Contributing
------------

[](#contributing)

Contributions are welcome! Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

Security
--------

[](#security)

If you discover any security issues, please report them via the GitHub issue tracker with the "security" label.

Credits
-------

[](#credits)

- [bensedev](https://github.com/bensedev)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE) for more information.

###  Health Score

37

—

LowBetter than 83% of packages

Maintenance65

Regular maintenance activity

Popularity10

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity55

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~4 days

Total

2

Last Release

212d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/09202caa58f25c9b9ca1b8de309e02aca5c605478fcbb5193b2ac301f0c8cc55?d=identicon)[bensedev](/maintainers/bensedev)

---

Top Contributors

[![BenSeDev](https://avatars.githubusercontent.com/u/27857775?v=4)](https://github.com/BenSeDev "BenSeDev (6 commits)")

---

Tags

laravelperformancecachequerylockcoalescingdeduplicationinflight

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/bensedev-laravel-inflight-query-lock/health.svg)

```
[![Health](https://phpackages.com/badges/bensedev-laravel-inflight-query-lock/health.svg)](https://phpackages.com/packages/bensedev-laravel-inflight-query-lock)
```

###  Alternatives

[spatie/laravel-responsecache

Speed up a Laravel application by caching the entire response

2.8k8.2M51](/packages/spatie-laravel-responsecache)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k12.1M99](/packages/laravel-pulse)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9682.1M97](/packages/roots-acorn)[anahkiasen/flatten

A package for the Illuminate framework that flattens pages to plain HTML

33313.0k](/packages/anahkiasen-flatten)[laragear/cache-query

Remember your query results using only one method. Yes, only one.

272122.8k](/packages/laragear-cache-query)[dragon-code/laravel-cache

An improved interface for working with cache

6844.8k10](/packages/dragon-code-laravel-cache)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
