PHPackages                             kosadchiy/laravel-parallel-db - 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. [Database &amp; ORM](/categories/database)
4. /
5. kosadchiy/laravel-parallel-db

ActiveLibrary[Database &amp; ORM](/categories/database)

kosadchiy/laravel-parallel-db
=============================

Laravel package for in-process parallel SQL execution via native pgsql/mysqli async APIs.

v0.2.0(1mo ago)1518MITPHPPHP ^8.3CI passing

Since Mar 14Pushed 1mo agoCompare

[ Source](https://github.com/Kosadchiy/laravel-parallel-db)[ Packagist](https://packagist.org/packages/kosadchiy/laravel-parallel-db)[ Docs](https://github.com/Kosadchiy/laravel-parallel-db)[ RSS](/packages/kosadchiy-laravel-parallel-db/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (2)Dependencies (14)Versions (7)Used By (0)

Laravel Parallel DB Queries
===========================

[](#laravel-parallel-db-queries)

[![CI](https://github.com/Kosadchiy/laravel-parallel-db/actions/workflows/ci.yml/badge.svg)](https://github.com/Kosadchiy/laravel-parallel-db/actions/workflows/ci.yml)[![PHP 8.3+](https://camo.githubusercontent.com/278621fc49c71cdc38414030199af0284d7323979cad78496bfafbf7050ee4bc/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e332532422d3737374242342e737667)](https://www.php.net/)

Parallel SQL queries for Laravel using native async database APIs:

- PostgreSQL: `ext-pgsql` (`pg_send_query_params`, `pg_socket`, `pg_consume_input`, `pg_connection_busy`, `pg_get_result`)
- MySQL: `ext-mysqli` (`MYSQLI_ASYNC`, `mysqli_poll`, `reap_async_query`)

No child processes, no queues, no ReactPHP, Amp, or Swoole.

Status: beta. The package is usable, but APIs and behavior may still evolve and are still being tested in real-world scenarios.

Why
---

[](#why)

When one request needs multiple independent SQL calls, sequential execution adds latency. This package runs them in parallel on separate DB connections in the same PHP process.

Typical use cases:

- building dashboards that need several expensive aggregate queries for different widgets;
- assembling analytics pages that combine totals, trends, leaderboards, and recent activity;
- fetching independent datasets for a single API response, such as users, transactions, invoices, and alerts;
- loading admin or reporting screens where multiple tables, counters, and summaries can be queried independently;
- reducing end-to-end latency in read-heavy endpoints that would otherwise wait on one slow query after another.

This package is most useful when queries are independent, read-heavy, and latency-bound. For tiny queries or cold-start paths, the overhead of extra connections, polling, and result collection can outweigh the benefit and even make the request slower. It is also a poor fit for strongly dependent query chains or workloads that require a shared transaction.

High-level design
-----------------

[](#high-level-design)

### Core components

[](#core-components)

- `ParallelDatabaseManager` - Laravel-facing entrypoint, accepts mixed query inputs.
- `QueryCompiler` - compiles builder payloads into `CompiledQuery`.
- `ParallelExecutor` - bounded scheduler + timeout + result collection.
- `DriverRegistry` - maps DB driver names to async implementations.
- `PostgresAsyncDriver`, `MySqlAsyncDriver` - native async transport.
- `ConnectionConfigResolver` + `*ConnectionFactory` - per-query dedicated connection creation.
- `ConnectionPool` - optional process-local reuse of idle async connections between runs.

### Execution flow

[](#execution-flow)

1. User passes map of queries (`Builder`, `Eloquent`, or `Closure` returning one of them).
2. Compiler normalizes all inputs to `CompiledQuery`.
3. Executor keeps pending/running sets and starts up to `maxConcurrency` queries on dedicated connections.
4. Drivers poll readiness (`stream_select()` for PostgreSQL sockets, `mysqli_poll()` for MySQL links).
5. Ready queries are reaped and transformed into `QueryResult`.
6. Completed connections are returned to the pool when pooling is enabled.
7. Executor continues until all queries are finished or timeout is reached.

Laravel versions
----------------

[](#laravel-versions)

- Laravel `12.x`
- PHP `8.3+`

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

[](#installation)

```
composer require kosadchiy/laravel-parallel-db
```

Optional extensions by driver:

- `ext-pgsql`
- `ext-mysqli`

Publish config:

```
php artisan vendor:publish --tag=parallel-db-config
```

Configuration
-------------

[](#configuration)

```
return [
    'default_max_concurrency' => 3,
    'default_timeout_ms' => 500,
    'error_mode' => 'fail_fast', // fail_fast|collect
    'pool' => [
        'enabled' => true,
        'max_idle_per_key' => 3,
    ],

    'drivers' => [
        'pgsql' => ['enabled' => true],
        'mysql' => ['enabled' => true],
    ],
];
```

- `default_max_concurrency` - default per-run concurrency limit used by `DB::parallel()`.
- `default_timeout_ms` - default batch timeout in milliseconds for the whole parallel run.
- `error_mode` - whether to stop on the first failure (`fail_fast`) or return failed entries alongside successful ones (`collect`).
- `pool.enabled` - enables process-local reuse of idle async connections between runs.
- `pool.max_idle_per_key` - caps how many idle pooled connections are retained per connection config.
- `drivers.pgsql.enabled` - enables the PostgreSQL async driver.
- `drivers.mysql.enabled` - enables the MySQL async driver.

Usage
-----

[](#usage)

### Default DB manager path

[](#default-db-manager-path)

```
$result = DB::parallel()->run([
    'users' => DB::table('users')->where('active', true),
    'servers' => DB::table('servers')->where('status', 'ok'),
]);
```

### Accessing collections and models

[](#accessing-collections-and-models)

```
$result = DB::parallel()->run([
    'users' => User::query()->where('active', true),
    'transactions' => DB::table('transactions')->latest()->limit(10),
]);

$users = $result['users']->toEloquentCollection(User::class);
$transactions = $result['transactions']->toCollection();
```

### Using closures

[](#using-closures)

```
$status = 'paid';
$from = now()->subDays(30);
$limit = 10;

$result = DB::parallel()->run([
    'top_users' => fn () => User::query()
        ->select('id', 'name')
        ->withCount('transactions')
        ->whereHas('transactions', fn ($query) => $query->where('status', $status))
        ->orderByDesc('transactions_count')
        ->limit($limit),
    'recent_revenue' => function () use ($status, $from) {
        return DB::table('transactions')
            ->selectRaw('date(created_at) as day, sum(amount) as total')
            ->where('status', $status)
            ->where('created_at', '>=', $from)
            ->groupByRaw('date(created_at)')
            ->orderBy('day');
    },
]);
```

### With options

[](#with-options)

```
use Kosadchiy\LaravelParallelDb\Enum\ErrorMode;

$result = DB::parallel(
    maxConcurrency: 3,
    timeoutMs: 1000,
    errorMode: ErrorMode::COLLECT,
)->run([
    'users' => User::query()->where('active', true),
    'servers' => Server::query()->where('status', 'ok'),
]);
```

### Via specific connection

[](#via-specific-connection)

```
$result = DB::connection('pgsql')->parallel()->run([
    'users' => User::query()->where('active', true),
]);
```

Result format
-------------

[](#result-format)

Each input key maps to `QueryResult`:

- `sql`
- `bindings`
- `type`
- `rows`
- `rowCount`
- `lastInsertId`
- `durationMs`
- `connectionDriver`
- `success`
- `error`

Connection pooling
------------------

[](#connection-pooling)

Idle async connections are pooled by default and reused across `run()` calls inside the same PHP process.

Connection pooling is especially useful in long-running environments such as Laravel Octane, where worker processes can reuse async connections across many requests. Keep in mind that the pool is process-local: each worker may retain up to `max_idle_per_key` idle connections per connection config, so total open connections should be sized with your worker count and database limits in mind.

For long-running workers and higher connection counts, consider a database-side pooler such as PgBouncer for PostgreSQL or ProxySQL for MySQL.

- pool key is based on driver/config;
- one pooled connection can still serve only one active query at a time;
- hot runs should show lower connection overhead;
- this is process-local reuse, not a shared external pool.
- `maxConcurrency` limits concurrently running queries per `run()`;
- `max_idle_per_key` limits how many idle pooled connections are retained for one connection config;
- if pooling is enabled, a long-lived process can keep more open connections than `maxConcurrency`, because completed connections may stay idle in the pool for reuse.

Error behavior
--------------

[](#error-behavior)

- `fail_fast`: first failed query stops the batch and throws `ParallelQueryFailedException`.
- `collect`: executor continues and returns failed entries with `success=false`.

Write-query caveats
-------------------

[](#write-query-caveats)

Writes are allowed (`INSERT/UPDATE/DELETE/DDL`), but:

- every query uses a separate connection;
- no shared transaction across the batch;
- global rollback is impossible;
- commit ordering across queries is not guaranteed.

Use parallel writes only when this behavior is acceptable.

Driver notes
------------

[](#driver-notes)

### PostgreSQL

[](#postgresql)

- Uses parameterized async calls (`pg_send_query_params`) with `?` -&gt; `$1..$N` conversion.
- Wait uses socket readiness (`pg_socket` + `stream_select`).

### MySQL

[](#mysql)

- Uses `MYSQLI_ASYNC` + `mysqli_poll` + `reap_async_query`.
- The MySQL async path does not use prepared statements and interpolates bindings with `mysqli::real_escape_string`, which is a transport-level limitation of this approach.
- Bindings are interpolated with `mysqli::real_escape_string` and strict typing rules.

Limitations and controversial points
------------------------------------

[](#limitations-and-controversial-points)

1. Async API parity differs between `ext-pgsql` and `ext-mysqli`.
2. The MySQL async path does not use prepared statements; bindings are interpolated with escaping as a transport-level limitation of this approach.
3. `stream_select()` has platform limits and FD-scaling caveats for very large sets.
4. Core results are returned as raw rows, but `QueryResult` provides explicit `toCollection()` and `toEloquentCollection()` helpers for post-processing.
5. Parallel writes are independent operations, not an atomic group transaction.

Benchmark
---------

[](#benchmark)

Synthetic PostgreSQL benchmark setup:

- 7 independent queries per run;
- 5 queries with configurable `pg_sleep(...)` delay;
- 2 regular `SELECT` queries;
- 2 warmup runs, then 10 measured iterations;
- sequential and parallel paths consume the same result sets.

This benchmark is intentionally synthetic and latency-bound. It is useful for showing when overlapping independent query latency helps, but it should not be read as a universal production speedup claim. Real-world gains depend on query cost, result size, connection overhead, database load, pooling behavior, and `maxConcurrency`.

Pool settings also affect warm-run results. With the default config, up to `3` idle connections per connection key are retained between runs.

Observed trends from the local test environment:

- higher `maxConcurrency` is not always better; the best value depends on the workload;
- longer per-query latency increased the benefit of parallel execution;
- for tiny queries, connection setup, polling, and result collection overhead could outweigh the gains.

Suggested benchmark matrix for comparing different latency profiles and scheduler settings:

query latency (ms)maxConcurrencysequential avg msparallel avg msspeedup xNotes501310.82302.871.03Near-sequential control case, as expected502313.10181.111.73Noticeable gain with limited parallel fan-out503309.33124.232.49Best result so far for this workload on the test environment505296.44141.142.10Faster than sequential, but slower than `maxConcurrency=3` here507293.76176.831.66Higher fan-out regressed on this workload and environment203158.6063.872.48Lower-latency workload still benefits strongly at the same sweet spot205135.65100.631.35Extra fan-out helps less when per-query latency is shorter1003571.07225.662.53Strong gain on a more latency-bound workload1005560.07183.813.05Best result so far; higher fan-out paid off for longer waits0513.7459.840.23Tiny queries: connection, polling, and collection overhead dominatedCold-start overhead looks different and should be measured separately. In the following run, pooling was disabled and `warmup=0`, so the async path had to pay full connection setup cost. For this kind of cold tiny-query scenario, `p50` is more representative than `avg`, because a single startup outlier can skew the sequential mean.

query latency (ms)maxConcurrencypoolwarmupsequential p50 msparallel p50 msspeedup xNotes03off013.78151.270.09Cold tiny-query path was dominated by connection setup and async transport overheadCold-start threshold sweep for the same environment (`pool=off`, `warmup=0`, `maxConcurrency=3`):

query latency (ms)sequential p50 msparallel p50 msNotes013.78151.27Parallel overhead dominated547.58168.23Parallel overhead still dominated20132.91200.12Parallel was still slower on the cold path50286.02257.41Cold-start overhead started to pay off100565.02376.12Strong cold-path win on a latency-bound workloadTakeaway from these synthetic benchmarks:

- warm runs benefited earlier, because pooled connections reduced setup overhead;
- cold runs with `pool=off` showed that parallel execution was not worthwhile for tiny queries;
- in this environment, the cold-path break-even point appeared somewhere between `20ms` and `50ms` of per-query latency;
- the optimal `maxConcurrency` depended on the workload: around `3` for shorter waits and higher for more latency-bound queries.

Testing
-------

[](#testing)

Unit tests cover:

- query compilation from Builder/Eloquent/Closure;
- bounded concurrency;
- timeout handling;
- fail-fast and collect-errors behavior;
- key-preserving result mapping.

Run:

```
composer test
```

Integration tests cover:

- public API query shapes (`Query\Builder`, `Eloquent\Builder`, `Closure`);
- raw compiled write queries through `ParallelExecutor`;
- `collect` vs `fail_fast`;
- timeout behavior;
- transaction visibility across separate async connections.

Run:

```
composer test:integration
```

Notes:

- the integration suite uses `orchestra/testbench`;
- it requires real PostgreSQL and MySQL instances plus `ext-pgsql` and `ext-mysqli`;
- when those services are unavailable locally, the integration suite skips cleanly;
- in CI, integration tests run against dedicated PostgreSQL and MySQL service containers.

###  Health Score

42

—

FairBetter than 89% of packages

Maintenance92

Actively maintained with recent releases

Popularity16

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity43

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 ~31 days

Total

3

Last Release

40d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/88c7f833f8ab04ecf86a7589afcdfa7a87a00ce4e8af2c53c804bd35d6843389?d=identicon)[Kosadchiy](/maintainers/Kosadchiy)

---

Top Contributors

[![Kosadchiy](https://avatars.githubusercontent.com/u/35429123?v=4)](https://github.com/Kosadchiy "Kosadchiy (11 commits)")

---

Tags

asynclaraveldatabasemysqlpostgresqlparallel

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleECS

Type Coverage Yes

### Embed Badge

![Health badge](/badges/kosadchiy-laravel-parallel-db/health.svg)

```
[![Health](https://phpackages.com/badges/kosadchiy-laravel-parallel-db/health.svg)](https://phpackages.com/packages/kosadchiy-laravel-parallel-db)
```

###  Alternatives

[kirschbaum-development/eloquent-power-joins

The Laravel magic applied to joins.

1.6k29.9M42](/packages/kirschbaum-development-eloquent-power-joins)[mongodb/laravel-mongodb

A MongoDB based Eloquent model and Query builder for Laravel

7.1k8.0M88](/packages/mongodb-laravel-mongodb)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.1M337](/packages/psalm-plugin-laravel)[yajra/laravel-oci8

Oracle DB driver for Laravel via OCI8

8723.1M23](/packages/yajra-laravel-oci8)[glushkovds/phpclickhouse-laravel

Adapter of the most popular library https://github.com/smi2/phpClickHouse to Laravel

2051.4M2](/packages/glushkovds-phpclickhouse-laravel)[itpathsolutions/dbstan

Database Standardization and Analysis Tool for Laravel

442.1k](/packages/itpathsolutions-dbstan)

PHPackages © 2026

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