Skip to main content

Transmission modes

How hits reach Matomo is one setting, and nothing at a call site changes when you change it:

MATOMO_MODE=batch
ModeBehavior
queue (default)A request's hits are sent as one queued Bulk request on terminate.
syncSent immediately — handy for the CLI, tests, or very low volume.
batchHits are buffered across requests and flushed in large Bulk batches — the most resource-efficient option.

queue — the default

Hits accumulate in a per-request buffer as you call the facade. When the framework terminates the request, they are dispatched as one queued job carrying one Bulk request. Ten tracked events in a request means one HTTP call to Matomo, made by a worker after the response has been sent.

'queue' => [
'connection' => env('MATOMO_QUEUE_CONNECTION'), // null = default connection
'queue' => env('MATOMO_QUEUE', 'matomo'),
'tries' => 5,
'backoff' => [30, 120, 300, 900],
'retry_until_minutes' => 1440,
],

Your worker must serve the matomo queue. This is the one operational requirement, and it is the most common reason a correctly configured package produces no data:

php artisan queue:work --queue=matomo,default

The dedicated queue name is deliberate — analytics should never sit behind a slow mail job, and a Matomo outage should never starve your application's real work. Point MATOMO_QUEUE at default if you would rather not run a second queue.

Retries escalate: 30 seconds, 2 minutes, 5 minutes, 15 minutes, and then hold at that last step, for up to queue.tries attempts. A batch that spends its budget is moved to the dead-letter store rather than being retried further. queue.retry_until_minutes is the outer bound and, on the defaults, is never reached. See reliability for what happens when they run out.

sync — send immediately

Every hit is one HTTP request, made inline. That blocks the caller for the duration of the request to Matomo, which is why it is not the default.

It is the right mode for a CLI command where there is no response to protect, for a low-volume application where a queue worker is more infrastructure than the traffic justifies, and inside tests where you want the send to have happened by the time the assertion runs.

batch — buffer across requests

Hits go into a durable cross-request buffer instead of being dispatched per request. A separate process drains the buffer and sends it in large Bulk batches. At volume this is by far the most efficient option: hundreds of hits from dozens of requests become one HTTP call.

'batch' => [
'driver' => env('MATOMO_BATCH_DRIVER', 'database'), // database|redis|file|array
'size' => env('MATOMO_BATCH_SIZE', 200),
'flush_interval' => env('MATOMO_BATCH_INTERVAL', 60),
'max_per_flush' => 2000,
'stale_after_minutes' => 15,
'max_attempts' => env('MATOMO_BATCH_MAX_ATTEMPTS', 25),
],

The drivers

DriverStoreUse it when
databaseA table in your database (the default)You want durability with no extra infrastructure.
redisA Redis list, drained as a reliable queueYou have Redis, want the buffer off your primary database, and the instance is set to noeviction or a volatile-* policy — see below.
fileA JSONL spool on disk, claimed by atomic renameA single node, or when neither of the above is available.
arrayMemory, for the current request onlyTests. Never in production — the process exit loses the hits.

database needs the migration. The package registers its migrations automatically, so php artisan migrate is enough — see installation.

All three durable drivers claim a batch before sending and only remove it once Matomo confirms a 200, so a crash mid-send re-delivers rather than loses. A claim that is never acknowledged is reclaimed after stale_after_minutes.

The redis driver needs a non-evicting instance

That durability holds for redis only while Redis is not allowed to evict the buffer's keys. They carry no TTL — they are pending work, not cache — so under allkeys-lru, allkeys-lfu or allkeys-random they are as evictable as anything else in the keyspace, and an eviction looks like nothing: the claim comes back empty, matomo:flush prints Flushed 0 Matomo hit(s). and exits zero, which is what an idle minute prints too.

matomo:test reads the policy and warns when it is one of the three. The full explanation is in reliability.

Draining the buffer

In batch mode the package registers a scheduled matomo:flush for you — it runs every minute and is guarded so two runs cannot overlap. Make sure your scheduler runs; without it, batch mode fills the buffer and sends nothing.

For a high-volume file or Redis spool, a long-running drainer beats a per-minute schedule:

php artisan matomo:work --max-time=3600 --memory=256

matomo:work loops with flush_interval seconds between passes. --max-time and --memory let a supervisor recycle it before it grows unbounded, the same pattern queue:work uses. Run one instance — the atomic claims make concurrent runs safe from double-sending, but they do not make them useful.

--once and --max-runs are there for a cron-driven or test-driven drain.

Sizing

size is how many hits go into one Bulk request; max_per_flush caps how many a single flush pass will move in total, so one pass over a large backlog cannot run unbounded. Raising size reduces HTTP calls but makes each one bigger — and a Bulk request that exceeds what your Matomo accepts fails as a unit. The default of 50 is conservative; measure with the load simulator before raising it.

Choosing

Start on queue. It is correct for the vast majority of applications, needs only a worker you probably already run, and never blocks a response.

Move to batch when the number of Bulk requests per minute becomes the thing you want to reduce — typically once you are tracking most page views server-side on a busy site. It costs you a buffer to operate and a scheduler to watch, and it widens the window between an event happening and Matomo knowing about it.

Use sync for the CLI and for tests.

In queue and batch mode, set MATOMO_TOKEN. Without a token Matomo timestamps each hit when it arrives, and in these modes that can be minutes — or, after a retry or a dead-letter replay, hours — after the event actually happened. See installation.