Pixelex.Config (Pixelex v0.1.0)

Copy Markdown View Source

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_bytes()

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_ms()

Flush the ingest buffer at least this often, whatever the byte count.

honor_dnt?()

Honour DNT: 1. Courtesy only — DNT has no legal force and Safari removed it.

honor_gpc?()

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_path()

Ingest endpoint mount point. Configurable because a fixed path is what filter lists block.

max_buffer()

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.

pageview_dedupe_ms()

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.

rate_limit()

Ingest rate limit: {max_events, window_ms} per IP. Far above a human, far below a script.

repo()

The host application's Ecto repo. Required by Pixelex.Store.Postgres.

retention_days()

Days of raw events kept before the partition is dropped. Rollups are kept indefinitely.

salt_persistence()

: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).

salt_refresh_ms()

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.

salt_ttl_hours()

Delete salts older than this. Two rotations' worth, so previous is always available.

session_timeout_ms()

Inactivity after which a session is considered over. 30 minutes is the industry convention.

site_id(host, override \\ nil)

@spec site_id(String.t() | nil, term()) :: String.t() | nil

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.

store()

Storage adapter. See Pixelex.Store.

today()

@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.