Skip to content

PR preview envs and local dev run the full page-cache loop against prod ClickHouse — now 19.7 cores, ~39% of the cluster #794

Description

@nikw9944

Summary

lake_dev_reader — the credential lake's own README tells developers to use for local development against production ClickHouse — consumed 12.5 cores, 27.8% of the entire prod cluster, for at least a day. One developer's local k3d stack, running the full page-cache refresh loop against prod data.

It stopped at 20:53Z on 2026-08-20 when the developer shut it down. Nothing about the setup prevents the next person from reproducing it.

What happened

A local api pod (api-fcfb7459c-fhjj9, k3d, pod CIDR 10.42.0.0/16) connected as lake_dev_reader and ran lake's page-cache refresh loop — all ~39 entries — against prod ClickHouse.

Measured over a 15-minute window while live:

pod runs cores read
api-fcfb7459c-fhjj9 3,673 12.48 4.61 TiB
indexer-6694979854-j4d59 57 0.02 2.56 GiB

4.61 TiB per 15 minutes is ~18 TiB/hour. Over a 5.9h window it was 84,773 queries at 11.8 cores sustained. It had already been running the previous day at ~6.6 cores (16.8% of cluster), so it roughly doubled.

The queries were byte-for-byte prod's own most expensive page-cache entries, at the same cost per run:

shape cores interval cpu/run read/run prod cpu/run
metro-pair join 1.30 90s 117.1 49.4 GiB ~113.6
jitter_improvement_pct 1.23 82s 100.6 44.2 GiB ~97.8
validators listing 0.74 10s 7.5 3.9 GiB ~7.5
bandwidth series 0.68 41s 27.9 7.6 GiB ~27.9
edge scoreboard 0.61 75s 45.9 15.7 GiB ~402 (24h variant)

Top 10 shapes were only 5.85 of the 12.48 cores — the other 53% is the long tail of remaining page-cache entries. No single query is at fault; the refresh loop is.

Local instances also refresh faster than prod: metro-pair at 90s here versus prod's 107s, because a local cycle has less competing work and completes sooner.

Why this is a documented path, not a mistake

lake_dev_reader is not an ad-hoc credential. It is named in three places in this repo:

  • README.md:166REMOTE_CH_USER=lake_dev_reader
  • .env.example:176 — same
  • scripts/setup-remote-tables.sh:60 — printed as setup guidance

Nothing in any of them warns that running the stack against prod imposes production-scale load. A developer following the README exactly reproduces this.

Proposed fixes

1. Disable the page-cache worker for local --use-remote runs. This is the actual fix — the refresh loop is where all 12 cores went. --no-worker already exists (api/main.go:244, help text "for prod where it runs standalone"). A local instance pointed at prod almost never needs a background loop pre-warming 39 entries. Either default it on when --use-remote is set, or document it prominently.

2. Quota the user as a backstop, so no future instance can do this regardless of flags:

ALTER USER lake_dev_reader SETTINGS
    max_execution_time = 60,
    max_bytes_to_read  = 20000000000;   -- 20 GB

The two heaviest shapes read 49.4 and 44.2 GiB per run, so 20 GB stops them outright rather than slowing them.

⚠️ Check this against our own traffic first. Five of our pods share this credential — lake-api-558cf9695f-5ngwc, lake-api-79796bf5c8-vx9sg, lake-indexer-7f748ddb87-57n9j, lake-indexer-5999968459-7hhnf, lake-indexer-7559c77454-xxzl7 — for the remoteSecure() proxy tables described in CLAUDE.md:50. They totalled 2.29 cores over 7,824 queries, far lighter than the local instance, so a 20 GB ceiling should clear them. Verify before applying. The cleaner alternative is a separate per-developer user, leaving lake_dev_reader for our own services.

3. Warn in the README at the point where the credential is introduced.

Note on attribution

Identifying the source took several hours and three wrong conclusions. The trap worth recording: system.query_log.initial_address is self-reported by the client; address is the real TCP peer. The local pod reported 10.42.0.19, which looked like an in-VPC address and sent the investigation through AWS route tables, VPC peerings and PrivateLink endpoints — none of which were involved. address gave the true external IP immediately, and client_hostname named the pod. Query those two first.

Related: malbeclabs/infra#1123 — ClickHouse users are created by hand with no version control or audit trail, so there was no record of who provisioned this credential or when.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions