|
| 1 | +--- |
| 2 | +title: "Replica resource usage" |
| 3 | +description: "The per-process memory, swap and disk observations a cluster replica reports about itself, and how to interpret them." |
| 4 | +menu: |
| 5 | + main: |
| 6 | + parent: "monitor" |
| 7 | + identifier: "monitor-replica-resource-usage" |
| 8 | + weight: 18 |
| 9 | +--- |
| 10 | + |
| 11 | +Every process of a cluster replica reports its own resource usage through |
| 12 | +[`mz_introspection.mz_cluster_replica_resource_usage`](/reference/system-catalog/mz_introspection/#mz_cluster_replica_resource_usage). |
| 13 | +Each row is one measurement, taken from one source, reported as that source |
| 14 | +gave it. Sources measure overlapping but distinct quantities, and the |
| 15 | +differences between them are informative, so no row is a combination of two |
| 16 | +others. Deciding which number is "the" memory usage of a replica, or how close |
| 17 | +it is to its limit, is left to queries over the relation. |
| 18 | + |
| 19 | +A metric whose name ends in `peak` is a high-water mark since the process |
| 20 | +started, and the rest are instantaneous. Peaks the operating system maintains |
| 21 | +itself are exact, and are unaffected by how often the replica reads them. Peaks |
| 22 | +folded from samples are marked as such below and can miss a spike shorter than |
| 23 | +the sampling interval, which makes them lower bounds. An observation the |
| 24 | +replica could not read is absent rather than zero, so which metrics appear |
| 25 | +depends on the platform and the kernel version. |
| 26 | + |
| 27 | +## Sources |
| 28 | + |
| 29 | +| Source | Reads | Measures | |
| 30 | +|---------------|-----------------------------------------|-------------------------------------------------------------------------------------------| |
| 31 | +| `cgroup` | the process's cgroup v2 interface files | the whole container, and the accounting that limit enforcement and the OOM killer act on | |
| 32 | +| `proc_status` | `/proc/self/status` | this process only, with resident memory broken down by backing | |
| 33 | +| `rusage` | `getrusage(RUSAGE_SELF)` | this process only | |
| 34 | +| `statvfs` | the replica's scratch filesystem | the filesystem as a whole, absent where disk is provided as swap | |
| 35 | + |
| 36 | +## Metrics |
| 37 | + |
| 38 | +| Source | Metric | Meaning | |
| 39 | +|---------------|-------------------|--------------------------------------------------------------------------------------------------------------------------------| |
| 40 | +| `cgroup` | `memory_current` | Memory charged to the cgroup: anonymous, page cache, kernel and socket memory. | |
| 41 | +| `cgroup` | `memory_peak` | High-water mark of `memory_current`, maintained by the kernel. | |
| 42 | +| `cgroup` | `memory_max` | The cgroup's memory limit. | |
| 43 | +| `cgroup` | `swap_current` | Swap charged to the cgroup, including pages already read back whose swap slot is still allocated. | |
| 44 | +| `cgroup` | `swap_peak` | High-water mark of `swap_current`, maintained by the kernel. | |
| 45 | +| `cgroup` | `swap_max` | The cgroup's swap limit. | |
| 46 | +| `cgroup` | `anon` | The part of `memory_current` backed by no file. | |
| 47 | +| `cgroup` | `file` | Page cache charged to the cgroup. | |
| 48 | +| `cgroup` | `shmem` | Shared memory and tmpfs pages. | |
| 49 | +| `cgroup` | `swapcached` | Pages resident in memory whose swap slot is still allocated. Counted in both `anon` and `swap_current`. | |
| 50 | +| `cgroup` | `kernel` | Kernel memory charged to the cgroup. | |
| 51 | +| `cgroup` | `slab` | Kernel slab allocations, part of `kernel`. | |
| 52 | +| `cgroup` | `sock` | Socket buffer memory. | |
| 53 | +| `cgroup` | `events_max` | Times an allocation hit `memory_max`. See the caveat below before using this as a limit-hit signal. | |
| 54 | +| `cgroup` | `events_oom_kill` | Processes in the cgroup killed by the OOM killer. | |
| 55 | +| `proc_status` | `vm_rss` | Resident set size of this process, the sum of `rss_anon`, `rss_file` and `rss_shmem`. | |
| 56 | +| `proc_status` | `rss_anon` | Resident memory backed by no file. The replica's own memory. | |
| 57 | +| `proc_status` | `rss_file` | Resident file-backed memory, largely this binary's text. Shared between replicas and charged to whichever cgroup first faulted it in. | |
| 58 | +| `proc_status` | `rss_shmem` | Resident shared memory. | |
| 59 | +| `proc_status` | `vm_swap` | This process's pages currently in swap. Excludes swap-cached pages, so it reads below `cgroup` `swap_current`. | |
| 60 | +| `proc_status` | `vm_swap_peak` | Maximum `vm_swap` over samples, so a lower bound on the true peak. | |
| 61 | +| `proc_status` | `heap` | `vm_rss` plus `vm_swap`, the quantity a replica is limited on. | |
| 62 | +| `proc_status` | `heap_peak` | Maximum `heap` over samples, so a lower bound on the true peak. | |
| 63 | +| `rusage` | `max_rss` | Peak resident set size. Maintained by the kernel, but refreshed only at internal checkpoints, so it can read below a concurrent `vm_rss`. | |
| 64 | +| `statvfs` | `fs_used` | Used bytes of the filesystem, which on a shared filesystem counts writes this replica never made. | |
| 65 | +| `statvfs` | `fs_used_peak` | Maximum `fs_used` over samples, so a lower bound on the true peak. | |
| 66 | + |
| 67 | +## Interpreting |
| 68 | + |
| 69 | +Values from different sources are not interchangeable, and adding them together |
| 70 | +generally produces a number that means nothing. In particular: |
| 71 | + |
| 72 | +* **How close is this replica to its memory limit?** Compare `cgroup` |
| 73 | + `memory_current` against `memory_max`. **Did it ever reach it?** Compare |
| 74 | + `memory_peak` against `memory_max`. A `memory_peak` at the limit means the |
| 75 | + replica ran out of RAM and spilled to swap, even if the current reading is |
| 76 | + comfortable. |
| 77 | +* **How close is it to its heap limit?** Compare `proc_status` `heap` against |
| 78 | + [`mz_internal.mz_cluster_replica_metrics`](/reference/system-catalog/mz_internal/#mz_cluster_replica_metrics)'s |
| 79 | + `heap_limit`. `heap_peak` bounds the high-water mark from below, and no source |
| 80 | + bounds it from above: the `cgroup` peaks describe a smaller quantity, and |
| 81 | + `max_rss` lags. |
| 82 | +* **Do not use `events_max` as a limit-hit signal.** Where swap is configured it |
| 83 | + stays at zero even for a replica pinned at its ceiling, because reclaim |
| 84 | + succeeds by swapping instead of failing. `events_oom_kill` does report kills. |
| 85 | +* **How much memory does this replica itself account for?** Use `proc_status` |
| 86 | + `rss_anon`. Do not use `vm_rss`: it includes `rss_file`, which is charged to |
| 87 | + another cgroup and so runs a roughly constant amount above the replica's own |
| 88 | + charge. |
| 89 | +* **Do not add `memory_current` and `swap_current`.** A page read back from swap |
| 90 | + is counted in both, and `swapcached` reports how much is in that state. |
| 91 | +* **Where disk is provided as swap**, disk usage appears as `swap_current` and |
| 92 | + there are no `statvfs` rows at all. |
| 93 | +* **These readings live and die with the replica process.** A restart resets |
| 94 | + every peak. For history that survives restarts, see |
| 95 | + [`mz_internal.mz_cluster_replica_metrics_history`](/reference/system-catalog/mz_internal/#mz_cluster_replica_metrics_history). |
| 96 | + |
| 97 | +## Example |
| 98 | + |
| 99 | +Introspection relations are replica-local: a query reads the replica that |
| 100 | +serves it, so pin both the cluster and the replica. This reports how close each |
| 101 | +process came to a memory-limiter kill, comparing the quantity the limiter |
| 102 | +enforces against the limit it enforces: |
| 103 | + |
| 104 | +```mzsql |
| 105 | +SET cluster = <cluster_name>; |
| 106 | +SET cluster_replica = <replica_name>; |
| 107 | +
|
| 108 | +SELECT |
| 109 | + u.process_id, |
| 110 | + round((max(u.value) FILTER (WHERE u.metric = 'heap'))::numeric / 1073741824, 2) AS heap_gib, |
| 111 | + round((max(u.value) FILTER (WHERE u.metric = 'heap_peak'))::numeric / 1073741824, 2) AS heap_peak_gib, |
| 112 | + round(m.heap_limit::numeric / 1073741824, 2) AS limit_gib, |
| 113 | + round(100 * (max(u.value) FILTER (WHERE u.metric = 'heap_peak'))::numeric / m.heap_limit, 1) AS peak_pct |
| 114 | +FROM mz_introspection.mz_cluster_replica_resource_usage u |
| 115 | +JOIN mz_cluster_replicas r |
| 116 | + ON r.name = current_setting('cluster_replica') |
| 117 | + AND r.cluster_id = (SELECT id FROM mz_clusters WHERE name = current_setting('cluster')) |
| 118 | +JOIN mz_internal.mz_cluster_replica_metrics m |
| 119 | + ON m.replica_id = r.id AND m.process_id = u.process_id |
| 120 | +WHERE u.source = 'proc_status' |
| 121 | +GROUP BY u.process_id, m.heap_limit |
| 122 | +ORDER BY u.process_id; |
| 123 | +``` |
| 124 | + |
| 125 | +`peak_pct` is a lower bound, because `heap_peak` is: a spike shorter than the |
| 126 | +sampling interval can slip through it. No source provides a matching upper |
| 127 | +bound. The query stays within `proc_status` deliberately. The `cgroup` metrics |
| 128 | +measure a different quantity, and `memory_current` plus `swap_current` |
| 129 | +double-counts every swap-cached page. |
0 commit comments