Every knob, with its default and the reason the default is what it is.
Read through functions rather than Application.get_env/3 at call sites, so
there is one place to look when a number needs explaining and one place to
change when it needs tuning.
Summary
Functions
Flush the ingest buffer once this many bytes have accumulated.
Flush the ingest buffer at least this often, whatever the byte count.
Honour DNT: 1. Courtesy only — DNT has no legal force and Safari removed it.
Honour the Sec-GPC: 1 request header.
Ingest endpoint mount point. Configurable because a fixed path is what filter lists block.
Drop events once the buffer holds this many.
Window in which a repeat page view for the same session and path is treated as the same view.
Ingest rate limit: {max_events, window_ms} per IP. Far above a human, far below a script.
The host application's Ecto repo. Required by Pixelex.Store.Postgres.
Days of raw events kept before the partition is dropped. Rollups are kept indefinitely.
:repo persists salts (survives restart, safest operationally) or :memory
keeps them only in ETS (Ackee's posture — a database backup can never be
replayed to reconstruct browsing history, at the cost of severing every open
session on restart).
How often the salt ETS cache reloads from the store.
Delete salts older than this. Two rotations' worth, so previous is always available.
Inactivity after which a session is considered over. 30 minutes is the industry convention.
Which site a request belongs to.
Storage adapter. See Pixelex.Store.
Today, in UTC.
Functions
Flush the ingest buffer once this many bytes have accumulated.
100KB is Plausible's threshold. Large enough that a bulk insert amortises the round-trip, small enough that a crash loses a fraction of a second of events.
Flush the ingest buffer at least this often, whatever the byte count.
Honour DNT: 1. Courtesy only — DNT has no legal force and Safari removed it.
Honour the Sec-GPC: 1 request header.
Defaults on. GPC is a legally binding opt-out under CCPA/CPRA and a growing set of US state laws, unlike DNT which never had legal force. Checked server-side because the client cannot be trusted to check it and an ad-blocker may have stripped the script that would have.
Ingest endpoint mount point. Configurable because a fixed path is what filter lists block.
Drop events once the buffer holds this many.
Analytics must never apply backpressure to a request. When the store is down or slow the correct behaviour is to lose events, loudly, rather than to grow a queue until the node dies with the rest of the application inside it.
Window in which a repeat page view for the same session and path is treated as the same view.
Sized for the LiveView dead-render / connected-render pair, which arrive within a few hundred milliseconds of each other. A genuine reload takes longer; a real navigation changes the path.
Ingest rate limit: {max_events, window_ms} per IP. Far above a human, far below a script.
The host application's Ecto repo. Required by Pixelex.Store.Postgres.
Days of raw events kept before the partition is dropped. Rollups are kept indefinitely.
:repo persists salts (survives restart, safest operationally) or :memory
keeps them only in ETS (Ackee's posture — a database backup can never be
replayed to reconstruct browsing history, at the cost of severing every open
session on restart).
How often the salt ETS cache reloads from the store.
Not the rotation period — this only converges the cache across nodes after whichever node ran the rotation wrote the new salt.
Delete salts older than this. Two rotations' worth, so previous is always available.
Inactivity after which a session is considered over. 30 minutes is the industry convention.
Which site a request belongs to.
Resolution order: an explicit override, then config :pixelex, :site_id
(a string, or a fun(host)), then the request host itself — which is the
right default for a custom-domain product, where the host is the tenant.
Why this is one function and not two
Pixelex.Plug records the dead render and Pixelex.LiveView records the
connected one, and the second is dropped as a duplicate of the first. That
check compares the visitor hash, which is keyed on the site. Resolve the site
differently in the two places and the hashes differ, the duplicate check
never fires, and every LiveView page view is counted twice — with no
error anywhere, just numbers that are quietly double.
So both call this. If you pass :site_id to one of them, pass the same value
to the other, or set it here once and pass it to neither.
Storage adapter. See Pixelex.Store.
@spec today() :: Date.t()
Today, in UTC.
Indirected through config for one reason: salt rotation happens at a day
boundary, and the behaviour at that boundary — a session surviving it — is
the single most consequential thing in this library and the easiest to break
without noticing. Code that cannot be tested at midnight is code that is
wrong at midnight. Tests set :clock to a zero-arity function; nothing else
ever should.