Skip to content

Architecture

browser ──► POST /api/event ──► Collector ──► EventSink ──► analytics DB (visits, events, event_props)
│ ├─ DatabaseSink (default: write in-request, stitches visits)
│ └─ QueueSink (buffer → worker → DatabaseSink)
dashboard (Preact, no build) ──► JSON API ──► Stats engine (Period + Filters) ──► analytics DB
└─ metadata DB (users, sites, goals, shared links, shields)
  • Visits, not sessions. One row per visit, stitched on write with a 30-minute inactivity timeout.
  • Two logical databases. Hot, append-only tables use TIDEMARK_ANALYTICS_DB. Everything else uses the default connection. Tables are keyed by site_id and indexed on time.
  • Stateless app tier. Sessions and cache can move to Redis or the database, and any number of PHP workers can serve requests.
  • Swappable seams. EventSink (writes) and Stats\Stats (reads) are the only places that know about the storage engine. A columnar implementation can replace them without touching controllers or the UI.
  • No build step. The dashboard is Preact loaded as plain modules.
  1. Start with SQLite or MySQL and TIDEMARK_INGEST=sync.
  2. If writes slow your pages, set TIDEMARK_INGEST=queue and add the cron job.
  3. Move the analytics tables to their own server with TIDEMARK_ANALYTICS_DB.
  4. Use retention to cap table growth.
© 2026 Tidemark · MIT licenseThese docs set no cookies and run no trackers.