Profiling a running deployment¶
Capture a CPU profile, a jemalloc heap profile or tokio runtime counters from a running ingester, compactor or query server. A released image cannot do this: the routes are compiled out of every published build, and the build that has them stays inert until you set an environment variable. Plan for a separate image, a restart of the role you want to profile, and a rollback afterwards.
The routes ride the metrics port (9100 ingester, 9101 compactor, 9105 query server) because that is the one HTTP listener every role shares. They are never mounted on the ingest or query API ports.
Before you start¶
- Either a Siglake checkout at the commit you want to profile and Docker, or
access to the registry the
profiling-imageworkflow pushes to. - Permission to change the image and environment of one role and restart it.
- A way to reach the metrics port of the pod you profile, such as
kubectl port-forward. - Read Network policies first. The metrics port checks no token, and an armed profiler hands a CPU profile's stack traces and a heap profile's allocation sites to anything that can reach the pod.
Build the PROFILING=1 image¶
Run this from the root of a Siglake checkout:
docker build -f deploy/Dockerfile \
--build-arg PROFILING=1 \
--build-arg BUILD_CACHE_ID=profiling \
-t siglake:prof-local .
PROFILING=1 compiles siglake-cli and siglake-query-server with the
profiling feature, which is off by default, and rebuilds jemalloc with
--enable-prof. It also adds --cfg tokio_unstable for the full tokio counter
set, keeps frame pointers, and skips the strip step so profiles resolve to
file and line. That debug information is most of why the image is about
1.95 GB, against 115 to 123 MB per stripped binary in the released build.
The profiling-image GitHub Actions workflow builds the same image on
workflow_dispatch and pushes it to the project's own registry as
siglake:prof-<short sha>. It never runs on a merge. A prof- tag is not a
release artifact and no public registry carries one, so treat it as a
throwaway for one investigation.
Arm the routes with SIGLAKE_PPROF_ENABLED=1¶
Before you change the Helm values, record query.image.repository,
query.image.tag and the entries in query.extraEnv. To profile a query
server, set both fields in the query-specific image override and add the
variable:
query:
image:
repository: <registry>/siglake
tag: prof-<sha>
extraEnv:
- name: SIGLAKE_PPROF_ENABLED
value: "1"
This override restarts query pods on the profiling image. Ingester, compactor
and schema-migration hook images keep their global image values. Set both
query image fields because an empty field inherits its global counterpart.
The process reads SIGLAKE_PPROF_ENABLED once, while it builds the metrics
router, so the setting takes effect on the restart and not before. 1 and
true arm the routes; any other value, a misspelling included, leaves them
unmounted.
The chart has no image override for the ingester or compactor. To profile one
of those roles, put the variable in ingester.extraEnv or
compactor.extraEnv and set the global image.repository and image.tag.
That global change also selects the profiling image for the other role and the
schema-migration hook. A query pod inherits it unless query.image already
overrides both fields.
Start the process with heap sampling on¶
The heap route needs one more variable, and the process must already have it at startup:
query:
extraEnv:
- name: SIGLAKE_PPROF_ENABLED
value: "1"
- name: _RJEM_MALLOC_CONF
value: "prof:true,prof_active:true"
The _ prefix is required: tikv-jemalloc-sys builds jemalloc with a
prefixed symbol namespace and ignores a plain MALLOC_CONF. jemalloc reads the
setting once at startup and a dump reports only allocations sampled while
prof.active was true, so you cannot turn sampling on later and dump
immediately. Get this wrong and /debug/pprof/heap answers 412 while the CPU
and runtime routes look healthy. The CPU and runtime routes do not need it.
Capture a profile¶
In one terminal, open a path to the metrics port. Port forwarding gives you access; it restricts nobody else.
In a second terminal, call one route at a time. Each file stays under a
temporary name until curl receives a complete, successful response. A failed
capture removes its temporary file and leaves an earlier profile untouched.
set -eu
CPU_TMP=''
HEAP_TMP=''
cleanup() {
[ -z "$CPU_TMP" ] || rm -f "$CPU_TMP"
[ -z "$HEAP_TMP" ] || rm -f "$HEAP_TMP"
}
trap cleanup EXIT
trap 'exit 1' HUP INT TERM
CPU_TMP=$(mktemp ./cpu.pb.gz.XXXXXX)
curl -fSs --connect-timeout 5 --max-time 45 -o "$CPU_TMP" \
"http://localhost:9105/debug/pprof/profile?seconds=30"
mv "$CPU_TMP" cpu.pb.gz
CPU_TMP=''
HEAP_TMP=$(mktemp ./heap.jeprof.XXXXXX)
curl -fSs --connect-timeout 5 --max-time 60 -o "$HEAP_TMP" \
http://localhost:9105/debug/pprof/heap
mv "$HEAP_TMP" heap.jeprof
HEAP_TMP=''
curl -fSs --connect-timeout 5 --max-time 15 \
"http://localhost:9105/debug/pprof/runtime?seconds=5"
trap - EXIT HUP INT TERM
| Route | Returns | Window |
|---|---|---|
/debug/pprof/profile?seconds=N |
On-CPU samples at 99 Hz as gzipped pprof protobuf, symbolized in the process | Default 30 s, clamped to 1 to 600 s |
/debug/pprof/heap |
A jemalloc heap profile as jeprof text |
Point in time |
/debug/pprof/runtime?seconds=N |
Tokio runtime counters as JSON, including a tokio_unstable field saying which counter set the build has |
Default 2 s, clamped to 1 to 60 s |
The CPU and runtime routes hold the request open for the whole window, so set a
client timeout above seconds. The CPU profile carries its own symbols and
opens with go tool pprof cpu.pb.gz. The heap profile does not: jeprof needs
the same binary, so record the image digest you captured from.
Read the response codes¶
| Code | Meaning |
|---|---|
404 |
Not mounted. The image lacks the profiling feature, or SIGLAKE_PPROF_ENABLED is unset or set to something other than 1 or true. A released image always answers this. |
409 |
Another capture is in flight. One profile runs at a time, CPU or heap: a heap dump walks allocator state, which is unsafe beside the CPU profiler's SIGPROF handler. The ticket is released when the request ends, a client hangup included. |
412 |
The heap route only. The process did not start with _RJEM_MALLOC_CONF=prof:true,prof_active:true, or the binary has no jemalloc profiling support. The body names what is missing. |
A 404 on every route is the check to run before a long capture: it says this
build or this process cannot profile at all, which is different from a capture
that returns nothing interesting.
Roll back when you are done¶
For a query server, restore the recorded query.image.repository and
query.image.tag. If the query image inherited both global values before this
procedure, remove the override or set both fields to empty strings. Remove the
SIGLAKE_PPROF_ENABLED and _RJEM_MALLOC_CONF entries from query.extraEnv,
but keep every entry that was there before profiling.
For an ingester or compactor, restore the recorded global image values and
remove the two profiling entries from that role's extraEnv. Leaving the
profiling image in place costs disk and pull time on every scheduling decision.
Leaving the routes armed exposes an unauthenticated profiler on the metrics
port. Neither opt-in is scheduled to become a default.