Skip to main content

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. HitsDeadLettered tells you hits are already being parked; the length tells you before that.
  • Size the instance for your worst outage, not for steady state. The database driver spills to disk and degrades; the redis driver 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:

  • batch mode 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.