itsimiro/laravel-chunk-cursor
Iterate large Eloquent result sets with cursor-based chunking instead of offset pagination. Process millions of rows in constant memory with stable ordering, avoiding skipped or duplicated records as data changes. Simple drop-in methods for Laravel query builders.
Architecture fit
chunk()/chunkById() are offset/keyset-paginated (N queries, re-planning cost on deep offsets), while cursor() streams a single result set but does not support eager loading, producing N+1 queries inside the loop. This package advertises chunked cursor iteration with eager loading — a genuine ergonomic win for ETL, exports, reindexing, and nightly aggregation jobs.lazy() and lazyById(), which provide bounded-memory iteration with eager loading via chunkById semantics. The incremental value of this package over the framework baseline is therefore narrow and must be measured, not assumed. Treat "does this beat lazyById() on our data shape?" as the gating question.Integration feasibility
Technical risk
with() relations are loaded per chunk via load(), chunk size directly controls query fan-out and memory ceiling — misconfiguration trades memory savings for query storms.Key questions to resolve before adoption
id), or offset-based under the hood?select() subsets, joins, groupBy, and whereHas?with() on each chunk query, or load() post-hydration?lazyById() at our real chunk sizes?Stack fit
Migration path
chunk() calls wholesale. Introduce an internal ChunkedIterator contract (e.g., LargeDatasetIterator::each(callable $fn, int $chunkSize)) with two implementations: one delegating to the package, one to native lazyById(). This gives a one-line rollback and removes the vendor from our public API.cursor() currently causes N+1 storms.Compatibility
composer.json, add a CI job that runs the package's code path against our supported Laravel/PHP matrix, and add a PHPStan baseline. Since the package is unmaintained-in-practice, plan to vendor or fork under MIT if a framework upgrade breaks it — the small surface area makes that a day of work, not a quarter.Sequencing
lazyById() and chunk() on a production-shaped table (≥10M rows) with eager-loaded relations. Capture peak memory, query count, wall time, and correctness (row count parity, no dupes/skips).Maintenance
Support
Scaling
lazyById() may be noise.chunkSize=1000 and 3 relations, that is 4 queries per 1000 rows — fine, but tune chunk size against query-per-second capacity on the replica, not just memory.Failure modes
Ramp-up
chunk(). The non-obvious cost is conceptual — teaching the team when cursor chunking is correct (stable ordering key, no mid-scan mutation, idempotent consumers) versus when lazyById() is simply the right default. Budget half a day of team education plus the internal design note.TPM recommendation: adopt only as an experiment behind an internal abstraction, with a hard gate on the benchmark spike. The package solves a real problem but shows every indicator of a hobby project; the expected value is positive only if we can prove a measurable win over Laravel's built-in lazyById() and can absorb a fork at short notice.
How can I help you explore Laravel packages today?