InsightClickIQ on AWS

Click analytics has a reputation for needing a big data platform: a stream processor, a columnar warehouse, a queue, a team to keep them talking. InsightClickIQ on AWS is one API container, one Postgres database and one S3 bucket. The AI comes from Bedrock, and the secrets live in Secrets Manager. That is the whole deployment until your traffic is very large, and the point at which it stops being enough is measurable in advance.

This post walks through the AWS shape piece by piece, with the sizes we measured on the running product rather than guesses, and the two settings that matter most when the first real traffic arrives.

What runs where

InsightClickIQ on AWS: one API container, RDS or Aurora PostgreSQL, S3, Secrets Manager, Bedrock, optional ClickHouse CloudInsightClickIQ on AWSOne container image built with INSIGHT_PLATFORM=aws. Everything else is a managed service in your account.Your websiteclickiq.js snippet, ~8 KB gzip+ lazy rrweb recorderMobile appsiOS · Android · React Nativesame /api/ingest/batchConsole + agentsbrowser console, MCPclients (bearer token)COMPUTECloudFront / ALBTLS, CloudFront-Viewer-CountryAPI on ECS Fargateingest, query engine,replay, heatmaps, AI,background rounds(signals every 30 min,retention at 03:00)serves /sdk/clickiq.jsTask roleno access keys: S3,Secrets Manager, BedrockMANAGED DATAAurora / RDS Postgresvia RDS Proxy; RLS on orgmonthly event partitionsRead replicaconsole reads, set byCLICKIQ_READ_DATABASE_…S3 bucketreplay chunks + PDFslifecycle: Glacier > 90 dClickHouse Cloudoptional, large tierevent table onlyPLATFORM SERVICESSecrets Manageraws-sm:// refsAmazon BedrockClaude; rules if unreachablePurple: reached through the task role. Dashed: added only above roughly 100 M events a month.Sessions, people, dashboards and replays stay where they are when the event table moves.
The AWS shape. The snippet, the mobile SDKs and the console all talk to one API container. Everything it keeps is in your account: events and state in Postgres, replay bodies and report PDFs in S3, credentials in Secrets Manager, and the model in Bedrock.

The API is a single image. The Dockerfile takes a build argument, INSIGHT_PLATFORM=aws, which pulls in the AWS extras of the shared platform library (boto3 for S3, Secrets Manager and Bedrock) and nothing for other clouds. The same container also serves the browser SDK at /sdk/clickiq.js, so the deployment that receives events is the one that hands out the script. There is no separate CDN origin for the snippet to drift out of step with.

PieceOn AWSWhat it holds
The APIECS Fargate (or EKS), behind an ALB, optionally with CloudFront in front. One process per task by default (WEB_CONCURRENCY=1), non-root, health check on /api/health.Ingest, the query engine, sessions and replay, heatmaps, every AI feature, and the background rounds: alerts each minute, signals every 30 minutes, partitions and retention daily.
The databaseRDS for PostgreSQL at the small and mid tiers, Aurora PostgreSQL when you want I/O-optimised storage and several readers. RDS Proxy as the pooler.The clickiq schema: events in monthly partitions, sessions, people, dashboards, reports, segments, signals, investigations and the AI caches, all under row-level security.
Read replicaAn RDS read replica or an Aurora reader, named in CLICKIQ_READ_DATABASE_URL.Nothing of its own. Console reads (queries, overview, journeys, heatmaps, forms, errors, performance, events) go there so they stop competing with ingest.
Object storeAn S3 bucket: CLICKIQ_REPLAY_STORE=s3://bucket/clickiq-replays.Gzipped rrweb replay chunks, typically 50–300 KB per recorded session, and report PDFs. Never in Postgres.
SecretsSecrets Manager, read by reference: INSIGHT_DATABASE_SECRET=aws-sm://…#password.The database password, the ClickHouse password if you add it, the token signing keys. Resolved values are cached for five minutes, so a request never waits on a lookup.
The modelAmazon Bedrock. The default is Claude; the console's Ask page names the model it is using.Nothing. The model reads compact results the engine has already computed and writes the words. It never writes SQL.

The task role is the security model

The URL in INSIGHT_DATABASE_URL carries no password, and the configuration holds no access keys. The platform library refuses to treat a secret value as configuration: if INSIGHT_DATABASE_PASSWORD is set, GET /api/health/config reports a warning saying secrets belong in the platform's store. On Fargate, S3, Secrets Manager and Bedrock are all reached through the task role, so nothing long-lived sits in the task definition, the image or the database.

What to grant the task role

Read and write on the replay prefix of one bucket. GetSecretValue on the named secrets. InvokeModel on the Bedrock model you chose. That is the list: the API never lists buckets, never touches IAM, and does not need internet egress beyond the AWS endpoints, which VPC endpoints can serve.

The rest of the security story is inside the database. The application connects as an ordinary role, clickiq_app, created NOSUPERUSER NOBYPASSRLS so row-level security actually binds it. Every product table carries an organization column and a policy on it; routes scope every query as well, so RLS is the second line rather than the only one. Keep the API role away from rds_superuser, which bypasses nothing by itself but invites someone to grant more later.

Sites & apps · Northwind Outfitters
The project page for Northwind Outfitters showing a verified install, the script tag, the write key, the MCP connection card and settings with replay at 100% of sessions, inputs masked and retention 395 days
The install check reads from Postgres; the settings decide what lands in S3. Replay at 100% of sessions with inputs masked, and retention of 395 days: the daily retention round uses that number to drop old partitions and delete expired replay objects from the bucket.

How big Postgres gets

Only one table grows quickly: event. Everything else in the schema is either one row per session (one for roughly every 25–50 events), one row per visitor, or console content that stays small. The storage review measured the event table at about 0.9 KB per event including its indexes: 271,000 events took 246 MB. After the monthly partitioning migration and the index diet that came with it, the running demo is lighter: the October partition holds 228,798 events in 167 MB, about 0.73 KB each. We size with the 0.9 KB figure to leave room.

TrafficEvents a monthPostgres for 13 monthsAWS shape
Internal app, pilot, small site0.1–2 M1–25 GBRDS db.t4g.medium or db.m7g.large (2 vCPU, 8 GB), 100 GB gp3
Mid-size product or store2–30 M25–350 GBRDS or Aurora at 4–8 vCPU / 32 GB, monthly partitions, a read replica
Large site, many tenants30–300 M0.3–3.5 TBPostgres holds to about 100 M a month; above that, ClickHouse for event
Very large / multi-tenant SaaS> 300 Mmulti-TBClickHouse Cloud on AWS for events; Postgres keeps sessions, people and content

Thirteen months is the default because it lets a year-on-year comparison see the same month last year. Projects are created with a retention of 395 days; shorten it per project and the retention round drops whole month partitions that no project still needs, which is instant and leaves no bloat behind, then deletes the remainder in 50,000-row batches.

0.9 KBper event with indexes, sizing figure
234 GB13 months at 20 M events a month
~14 GBreplays a month at a 10% sample
1API task to start; add more as ingest grows

S3 holds the largest bytes

Replays are where the bytes are. Each recorded session is a sequence of rrweb chunks sent every five seconds or at about 512 KB, each under 2 MB, gzipped when stored. Postgres only keeps an index row per chunk; the bodies go to whatever CLICKIQ_REPLAY_STORE names. On a laptop that is a directory; on AWS it is an s3:// ref, and nothing else changes. The demo store's 80-odd recorded sessions plus a handful of report PDFs take 2.4 MB on disk, because the generated sessions are short; real ones sit in the 50–300 KB range the storage review measured.

Session replay
A session replay of the Northwind store with the player on the left, error and rage-click markers on the progress bar, and a 39-event timeline on the right
The player streams the chunks back from S3. The timeline on the right is Postgres; the page on the left is rrweb data stored in the bucket. The red and orange marks on the progress bar are the error and the rage click the timeline lists.

Three bucket settings are worth making on day one:

  • Block public access and use SSE-S3 or SSE-KMS. The API reads and writes with the task role; nothing needs a public URL.
  • A lifecycle rule to Glacier for replays older than 90 days. People rarely watch a three-month-old session, and when they do it is for an investigation that can wait for a restore.
  • Versioning only if you need it. Most customers accept that a lost replay is re-recorded by the next visitor; the few who need it can turn on versioning or cross-region replication.

The model: Bedrock, and the answer when it is down

Every AI surface in the console — the insight strip on each page, the “Explain” on every chart, signal narratives, the weekly digest, investigations, Ask, the agent playbooks — calls one function in the platform library. With INSIGHT_LLM_PROVIDER=bedrock (the default on AWS) it reaches Claude through the task role. The demo runs Claude Sonnet 4.6 on Bedrock, and the Ask page says so in its header.

Ask AI
The Ask AI page with the badge Claude Sonnet 4.6 on Bedrock, a drivers table showing orders by path, browser, device type and country, five checkout sessions with errors, and suggested follow-up questions
The model plans; the engine counts. “Why did orders drop this week?” became validated queries against Postgres (orders 165 → 111, the drop carried by /checkout, mobile down 28) and a list of checkout sessions with errors. Bedrock wrote the sentences around them.

Two properties matter for the AWS bill and for availability. First, the model never sees raw events: it receives compacted query results, so token use tracks the size of an answer, not the size of your traffic. Second, every AI feature has a rules path. If Bedrock is throttled, unreachable from the VPC, or switched off with CLICKIQ_AI=false, the same screens answer from rule templates and are labelled “Rules” rather than “AI”. The numbers do not change, because the model never produced them.

Latency, honestly

A cold insight strip is a synchronous model call: our QA pass measured 7–13 seconds per call on Bedrock. Results are cached for ten minutes (two for Overview and Live), so a second visit is instant. Moving these calls off the request path is on the list; until then, budget for it when you demo a cold console.

Postgres settings that matter

THE DEFAULT RDS PARAMETER GROUPUnbounded statements, small work_mem

A 30-day journeys scan can run as long as it likes and sorts spill to disk early. One analyst's heavy query competes with ingest on the writer.

WHAT CLICKIQ SETSBounded, pooled, and partition-aware

statement_timeout = 30s and work_mem = 64MB on the application role; random_page_cost = 1.1 for SSD; autovacuum scale factor 0.02 per event partition; the background rounds lift the timeout for themselves.

These come from the role defaults script the project ships, and they translate directly to an RDS parameter group or ALTER ROLE statements on the managed instance. RDS Proxy in front is safe because the tenant is set per transaction with set_config(…, true), which is exactly what transaction-mode pooling needs. Turn on INSIGHT_DATABASE_SSLMODE=require, keep the instance in a private subnet, and keep automated backups at 7–35 days with point-in-time recovery. Test a restore every quarter; it is the only backup that counts.

When you run more than one API task, the background loop does not run twice. Each round takes a Postgres advisory lock on a dedicated connection; one task does the round and the others skip it. Scaling out is therefore a matter of task count, not of designating a “worker”.

When to add ClickHouse Cloud

The event table is the only thing that ever moves. The query engine compiles every chart, widget, alert, AI answer and MCP tool through one module, and that module has two dialects: Postgres and ClickHouse. Switching is configuration: CLICKIQ_EVENT_STORE=clickhouse, a CLICKIQ_CLICKHOUSE_URL on port 8443 with TLS, and the password as aws-sm://… in CLICKIQ_CLICKHOUSE_SECRET. Give the console a read-only ClickHouse user and ingest a writer through CLICKIQ_CLICKHOUSE_WRITE_URL.

Option for event at scaleFit for the consoleNotes
ClickHouse Cloud on AWSYes — supportedThe dialect is built and verified: the same specs answer identically on both engines, 1.3–17× faster per statement in our test.
Self-managed ClickHouse on EC2 or EKSYesSame dialect; you run upgrades, backups and storage.
Redshift ServerlessBatch onlySlower per query for an interactive console and would need its own dialect. Not built.
Athena over ParquetBatch onlyGood for ad-hoc analysis of an export; too slow for a console that runs dozens of queries per screen.

The migration order is the part to follow exactly: run ClickHouse, set CLICKIQ_EVENT_DUAL_WRITE=true while Postgres is still the backend so new events land in both, backfill history with scripts/backfill_clickhouse.py (re-runnable; it skips ids already copied), flip CLICKIQ_EVENT_STORE=clickhouse, and keep dual-write on. Sessions, people, replays and some reads stay on Postgres, which is why dual-write stays.

Below about 100 million events a month, the AWS answer is RDS or Aurora, S3 and a replica. ClickHouse is a configuration change you make when the numbers say so, not an architecture you start with.

What your security team will ask

  • Where does the data live? In your account, in one region. Residency means one deployment, a database and a bucket, per region; the console is region-agnostic. Do not share a database across regions.
  • What leaves the browser? No input values, ever. Replay masks every input by default, [data-private] text is sent as asterisks, and [data-clickiq-block] elements are neither recorded nor captured.
  • Can a person be erased? Yes. DELETE /api/people/{id} removes every id they were seen under, their sessions, events and the replay objects in S3.
  • Where does country come from? From CloudFront's viewer-country headers when they are forwarded, else a MaxMind database if you mount one (CLICKIQ_GEOIP_DB), else the browser's time zone. The IP address is not stored as an event field.

What this deployment does not include

  • Daily rollups. Partitions, retention, the index diet and the read replica shipped; rollup tables are the next storage item and are not built yet.
  • Multi-region active-active. One region per deployment. That is a residency feature as much as a limit.

The warehouse export has shipped since this was first written: events, sessions and people as Parquet to s3://bucket/prefix, hourly or daily, and GET /api/exports/schema returns the Athena DDL with partition projection.

The same product on Azure →  ·  How the event engine switches →

← All posts   Request a demo