InsightClickIQ on Google Cloud

Teams on Google Cloud usually ask the same first question: our data is in BigQuery, so why would click analytics keep its own database? The answer is latency. A single console screen in InsightClickIQ runs a handful of queries, an insight strip and often a journey or zone analysis, and people click through screens the way they browse a site. That workload wants a database that answers in tenths of a second, which is Postgres at small and mid scale and ClickHouse at large scale. BigQuery is where the data should go to be joined with everything else, and the post comes back to that at the end.

The shape

InsightClickIQ on Google Cloud: Cloud Run, Cloud SQL or AlloyDB, Cloud Storage, Secret Manager, Vertex AI, BigQuery over the Parquet exportInsightClickIQ on Google CloudInteractive reads stay on Postgres (or ClickHouse). BigQuery is where data goes to be joined, not where the console reads.Your websiteclickiq.js snippet, ~8 KB gzip+ lazy rrweb recorderMobile appsiOS · Android · React Nativesame /api/ingest/batchConsole + agentsbrowser console, MCPclients (bearer token)COMPUTEHTTPS load balancerTLS, Cloud ArmorAPI on Cloud Runbuilt withINSIGHT_PLATFORM=gcp;detected from K_SERVICEmin instances 1 and CPUalways allocated: thebackground loop runsService accountADC for GCS, SecretManager and VertexMANAGED DATACloud SQL or AlloyDBAlloyDB: columnar engineRLS on org, partitionsRead pool / replicaconsole reads, set byCLICKIQ_READ_DATABASE_…Cloud Storagegs://bucketNearline / ColdlineClickHouse Cloudoptional, large tierevent table onlyPLATFORM SERVICESSecret Managergcp-sm://Vertex AIor rulesBigQueryParquet exportDashed: optional. The warehouse export writes Parquet to Cloud Storage hourly or daily (console: Exports).BigQuery reads that Parquet as a hive-partitioned external table: no BigQuery dialect in the product.
The Google Cloud shape. Interactive reads stay on Cloud SQL or AlloyDB, with ClickHouse Cloud added for the event table only at the large tier. BigQuery is drawn dashed because it is optional: the warehouse export writes Parquet to Cloud Storage and BigQuery reads it as an external table.
PieceOn Google CloudConfiguration
The APICloud Run (or GKE). Image built with INSIGHT_PLATFORM=gcp; the platform library also recognises Cloud Run from K_SERVICE.Minimum instances 1, CPU always allocated — see below. Health check /api/health.
The databaseCloud SQL for PostgreSQL at small and mid scale; AlloyDB when you want the longest Postgres-only runway.INSIGHT_DATABASE_URL on a private IP, password via INSIGHT_DATABASE_SECRET=gcp-sm://projects/…/secrets/…, TLS required.
Console readsA Cloud SQL read replica or an AlloyDB read pool.CLICKIQ_READ_DATABASE_URL. Unset, reads go to the primary.
Object storeA Cloud Storage bucket.CLICKIQ_REPLAY_STORE=gs://bucket/clickiq, application default credentials.
The modelVertex AI.INSIGHT_LLM_PROVIDER=vertex, INSIGHT_GCP_PROJECT, INSIGHT_GCP_LOCATION (default us-central1), INSIGHT_CHAT_MODEL (default gemini-2.5-pro).
Large tierClickHouse Cloud on GCP, or self-managed on GKE.CLICKIQ_EVENT_STORE=clickhouse; only event moves.

The service account is the only principal: application default credentials cover Cloud Storage, Secret Manager and Vertex, so nothing in the service's environment is a key.

The Cloud Run setting that matters

Cloud Run's defaults are built for request-driven services: CPU is allocated only while a request is being handled, and an idle service scales to zero. InsightClickIQ is mostly request-driven, but not entirely. The API process also runs the background loop that makes the product feel watched over rather than queried.

Background roundHow oftenWhat stops if it does not run
Alertsevery minuteThreshold alerts are not evaluated; nobody is notified.
Signalsevery 30 minutesAnomalies, frustration hot spots, error spikes and slow pages are not detected, and their impact estimates go stale.
DigestsdailyThe Overview's “This week, explained” is written on first read instead of waiting there.
Scheduled agent runshourly checkDaily and weekly playbooks (weekly review, install health) never start.
PartitionsdailyFuture monthly partitions of the event table are not created ahead of time.
Retentiondaily at 03:00 localExpired events, sessions, people and replay objects are not removed.
CLOUD RUN DEFAULTSScale to zero, CPU only during requests

Ingest and the console work, so the deployment looks healthy. But between requests the process has no CPU, and with no traffic it has no process at all. Signals stop appearing and retention silently stops running.

WHAT TO SETMinimum instances 1, CPU always allocated

One instance always has CPU for the background loop. With several instances, each round takes a Postgres advisory lock, so one instance does the work and the rest skip it. No separate worker service.

Cloud SQL or AlloyDB

Both run the same schema, the same row-level security and the same monthly partitions, and the product does not care which you pick. The difference is how long Postgres alone carries you. The storage review's guidance is that Postgres with partitions works up to about 100 million events a month, after which 30-day scans of the event table make the console slow. AlloyDB's columnar engine accelerates exactly those scans, which is why we describe it as the longest Postgres-only runway of any cloud. We have not benchmarked AlloyDB ourselves, so treat the extra headroom as something to measure on your data rather than a number.

TrafficEvents a month13 monthsGoogle Cloud shape
Pilot, internal app, small site0.1–2 M1–25 GBCloud SQL db-custom-2-8192 (2 vCPU, 8 GB) or AlloyDB 2 vCPU
Mid-size product or store2–30 M25–350 GBCloud SQL or AlloyDB at 4–8 vCPU / 32 GB, a read replica or read pool
Large, many tenants30–300 M0.3–3.5 TBAlloyDB, measured; then ClickHouse Cloud on GCP for events
Very large> 300 Mmulti-TBClickHouse for events; Postgres for sessions, people, content

Those volumes come from a measured footprint: about 0.9 KB per event with indexes (271,000 events took 246 MB). The running demo, after partitioning and an index diet, stores its October partition — 228,798 events — in 167 MB.

Journeys
The Journeys page for Northwind: AI insights across 8,240 sessions at a 10.55% conversion rate, a golden path from /pricing to /signup, a loop on product pages and a cart-to-checkout leak
The heaviest read in the console. Journeys mines every session's sequence in the window — here 8,240 sessions — to find the golden path (/pricing → /signup, 246 sessions, all converted) and the leaks. On 271,000 events it takes 6–12 seconds today, and it grows with the rows in the window.

Where the time goes, and why it decides the tier

Most console queries on the demo's 271,000 events take 0.1–0.5 seconds; the 30-day overview 1.2 seconds; a person-trait breakdown 1.3 seconds; forms about 8 seconds; journeys 6–12. These grow roughly linearly with the rows in the selected window. That is the useful way to think about sizing on any cloud: not total storage, but how many rows a 30-day screen has to scan.

0.1–0.5 smost console queries on 271k events
6–12 sjourneys today, linear in rows
957 → 54 mstrait breakdown, Postgres to ClickHouse
~100 Mevents a month before ClickHouse
Performance
The Performance page with Core Web Vitals at the 75th percentile: LCP 3.4 s needs improvement, INP 213 ms, CLS 0.020 good, FCP 1.2 s, TTFB 280 ms, with trend charts and a slowest-pages table led by / at 4.7 s
Percentiles are computed in the database. p75 LCP of 3.4 s across 30 days is a percentile_cont on Postgres and a quantileExactInclusive on ClickHouse; both interpolate, so the number does not change when the engine does.

Cloud Storage for replays and PDFs

Replay bodies are gzipped rrweb chunks, about 50–300 KB per recorded session, and they are the largest bytes the product writes. They go to the bucket named in CLICKIQ_REPLAY_STORE; Postgres keeps an index row per chunk. A lifecycle rule that moves objects to Nearline after 30 days and Coldline after 90 matches how replays are used: watched within days of a signal or an investigation, rarely after. The retention round deletes them, and tolerates objects that are already gone, when the project's window ends or a person is erased.

Report snapshots store their PDF in the same store. Server-side PDFs need headless Chromium in the image, which the stock image does not include; without it the endpoint answers 501 and the console prints from the browser instead.

Vertex AI, and the rules path

The model plans validated query specifications against the project's real event names and writes the words around results the engine computed; it never writes SQL and never sees raw events. With Vertex that is gemini-2.5-pro unless you name another model. If Vertex is unavailable or not yet approved, set CLICKIQ_AI=false: insight strips, signals, the digest, investigations and the agent playbooks answer from their rules paths, labelled “Rules” in the console.

Dashboards · Web analytics
The Web analytics template dashboard for Northwind: visitors 6,697, sessions 8,242, bounce rate 12.5%, average session 1m 12s, a visitors-and-pageviews chart over 30 days, top pages and referrers
Every widget is one query against the read replica. The insight strip above them is honest about the demo: the previous 30 days have no comparable data, and it says so instead of reporting a percentage rise.

Country without Cloudflare

The ingest service reads the visitor's country from edge headers when they are present (CF-IPCountry, CloudFront's viewer headers, X-AppEngine-Country, or a plain X-Country), then from a MaxMind database if CLICKIQ_GEOIP_DB names one, and last from the browser's time zone. On a Google Cloud HTTPS load balancer, a custom request header such as X-Country: {client_region} gives you country-level accuracy without shipping a GeoIP file. The client IP is used for that lookup only; it is not stored on the event.

BigQuery: the right place for joins

The case for BigQuery is real: you want click behaviour next to ad spend, CRM stages and revenue, and that join belongs in the warehouse. It is the wrong engine for the console itself — slower per query than an interactive workload wants, and it would need a dialect of its own in the query module, which we have chosen not to build.

Getting the data into BigQuery

The warehouse export (console Exports) copies events, sessions and people hourly or daily as Parquet to gs://bucket/prefix, partitioned by date, and GET /api/exports/schema returns the BigQuery DDL for a hive-partitioned external table: no product-side dialect. Aggregates also come out through the query API (POST /api/query) or the MCP server's run_query tool, both of which return the same validated results the console shows.

On Google Cloud, Postgres answers the console and BigQuery answers the business. Keeping those jobs apart is what keeps both fast.

Checklist

  • Cloud Run: minimum instances 1, CPU always allocated, ingress through the load balancer, a service account with Storage Object Admin on one bucket, Secret Manager Secret Accessor on the named secrets, and Vertex AI User.
  • Cloud SQL or AlloyDB on a private IP, TLS required, automated backups with point-in-time recovery, a restore tested quarterly.
  • An ordinary application role (NOSUPERUSER NOBYPASSRLS) with statement_timeout = 30s and work_mem = 64MB, so row-level security binds and a heavy screen cannot hold a connection for minutes.
  • Residency by deployment: one database and one bucket per region, never a database shared across regions.

AWS →  ·  Azure →  ·  The event engine, in detail →

← All posts   Request a demo