Scaling
At high volume the bottleneck is the Matomo server, not this client. Matomo's own QueuedTracking plugin does roughly a few hundred hits per second per worker; a Laravel application can generate hits far faster than that.
So the package does not promise throughput it does not control. What it promises is four properties, and gives you the tool to verify them on your own hardware:
- Non-blocking — nothing is sent on the request path unless you choose
sync. - Loss-free — a batch is removed only after Matomo confirms a
200; anything undeliverable ends up in the dead-letter queue, never dropped. - Bulk-coalesced — many hits become one HTTP request.
- Bounded-memory — buffer reads stream line by line, so draining a large spool never loads the whole thing into memory.
Measure it
php artisan matomo:load-sim --hits=100000 --driver=redis # discards the sends, measures the client
php artisan matomo:load-sim --hits=10000 --against=real # end-to-end against your Matomo
The simulator fires synthetic hits through the real pipeline — the same payload builder, the same buffer, the same flusher — and reports enqueue throughput, flush throughput, the exact number of Bulk requests it took, and peak memory.
By default it discards the sends, so nothing reaches Matomo and what you measure is
your application. --against=real sends to the configured instance, which is how you
find out whether your Matomo, not your app, is the limit. --driver exercises a
buffer driver other than the configured one, and --batch overrides the Bulk batch
size, so you can compare sizes before changing configuration.
Run it before raising batch.size and before choosing between the database,
redis and file drivers. The numbers differ per deployment enough that a general
recommendation would be worthless.
Recycle a long-running drainer
php artisan matomo:work --max-time=3600 --memory=256
Under a supervisor, a drainer should exit on its own terms rather than grow until
something kills it. --max-time stops it after roughly that many seconds;
--memory stops it once its memory use exceeds that many megabytes. The supervisor
restarts it, and the atomic buffer claims mean nothing in flight is lost across the
restart.
The file driver's boundary
The file driver spools hits as JSONL and claims a batch by atomic rename. Reads are
streamed, so memory stays bounded regardless of spool size — but a claim still
rewrites the remaining queue, so draining a very large spool costs one rewrite per
claim.
That is a deliberate boundary, not a defect to work around: the file driver targets
modest volume and single-node deployments. At scale, use database or redis.
The redis driver's boundary
The buffer has no upper bound. push() is an unconditional RPUSH, and there is no
setting that caps it — batch.max_per_flush limits how fast the buffer is drained, not
how large it gets. So a Matomo outage turns straight into Redis memory, at whatever rate
your traffic produces hits.
The counter-pressure that does exist is slow by design. A batch dead-letters only after
its full attempt window, which on the shipped defaults is batch.max_attempts (25)
against a per-minute flush — roughly twenty-five minutes to shed fifty hits. That is the
right behavior for a blip and the wrong shape for an outage that lasts hours.
Two things follow, and neither is a setting you can turn on:
- Watch the queue length.
LLEN matomo-analytics:buffer(through your Redis prefix) is the number to alarm on.HitsDeadLetteredtells you hits are already being parked; the length tells you before that. - Size the instance for your worst outage, not for steady state. The
databasedriver spills to disk and degrades; theredisdriver does not.
And the eviction precondition applies here too — see the durability section.
Compose with QueuedTracking on the Matomo side
The transmission modes control delivery on your application's side. On a busy self-hosted Matomo, also install Matomo's QueuedTracking plugin. It queues incoming hits on the Matomo server (in Redis or MySQL) and processes them with a background worker, so the tracking endpoint answers in milliseconds instead of writing to the database on the request path.
Matomo Cloud already does this for you.
The two layers compose, and need no extra configuration in this package:
batchmode here sends fewer, larger Bulk requests; QueuedTracking accepts each one instantly and writes asynchronously. That is the most efficient combination at high volume.- Because hits leave your application fast and QueuedTracking absorbs spikes, you get end-to-end backpressure without ever blocking a user response.
On the Matomo host, enable the plugin and run its processor on a schedule — a
core:archive-style worker, or ./console queuedtracking:process — per the plugin's
own documentation. None of that is configured from here; it is Matomo-side operations.