Skip to content

Fix per-user memory tracker drift from system log elements - #110708

Merged
Algunenano merged 12 commits into
ClickHouse:masterfrom
Algunenano:system-log-self-contained-elements
Jul 24, 2026
Merged

Fix per-user memory tracker drift from system log elements#110708
Algunenano merged 12 commits into
ClickHouse:masterfrom
Algunenano:system-log-self-contained-elements

Conversation

@Algunenano

@Algunenano Algunenano commented Jul 16, 2026

Copy link
Copy Markdown
Member

System log elements (query_log, query_views_log, part_log, text_log, ...) are built in a query thread but destroyed later by the SystemLog flush thread, which cannot credit the freed bytes back to the per-user memory tracker. Any memory an element owned that was charged to the user leaked onto the per-user tracker permanently while the user had at least one running query, eventually tripping max_memory_usage_for_user with phantom memory.

This makes system log elements self-contained and builds them under a memory-tracker blocker (via a fill-in-place SystemLogBase::add), so their allocations are charged only to the global total tracker and are freed with the element by the flush thread. A compile-time check (Common/SelfContained.h, using boost::pfr) enforces self-containment and fails the build with a clear message if a system log element ever holds a shared_ptr, weak_ptr, raw pointer or exception_ptr.

This fixes the system-log-element contributor to the per-user memory tracker drift. Other contributors to the same skew (e.g. memory from the StringLowCardinality(String) conversion during INSERT) are separate and out of scope here.

Related: #81541

Changelog category (leave one):

  • Bug Fix (user-visible misbehavior in an official stable release)

Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):

Fixed drift of the per-user memory tracker caused by system log records (e.g. query_log, query_views_log) being accounted to the query that produced them but freed later by a background thread, which could eventually trigger a false max_memory_usage_for_user limit for users with continuous workloads.

…r drift

System log elements (query_log, query_views_log, part_log, text_log, ...) are
built in a query thread but destroyed later by the SystemLog flush thread, which
cannot credit the freed bytes back to the per-user MemoryTracker. Memory an
element owns that was charged to the user therefore leaked onto the per-user
tracker permanently while the user had a running query, eventually tripping
max_memory_usage_for_user with phantom memory.

SystemLogBase::add now takes a callback that fills the element in place: it is
default-constructed under a MemoryTrackerBlockerInThread and moved into the queue,
so its allocations are charged to the global total tracker only. SystemLogQueue::push
is private, so add() is the single entry point (this also covers text_log via
OwnSplitChannel).

Log elements are made self-contained (they own all their memory, with no shared or
borrowed heap): profile snapshots stored by value, changed settings and async-read
counters as owning name->value maps, ClientInfo addresses as optional<SocketAddress>,
error_log traces as UInt64, and BackupLogElement copies only the loggable fields out
of BackupOperationInfo. Coordination::ErrorCounter drops its redundant (always
mutex-guarded) atomics so it is a plain trivially-copyable value.

A compile-time check (Common/SelfContained.h, using boost::pfr) enforces that every
system log element is self-contained and fails the build with a clear message if one
(transitively) holds a shared_ptr, weak_ptr, raw pointer or exception_ptr.

Adds a differential integration regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Algunenano Algunenano added the memory When memory usage is higher than expected label Jul 16, 2026
@clickhouse-gh

clickhouse-gh Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Workflow [PR], commit [899287e]

Summary:

job_name test_name status info comment
Integration tests (amd_msan, 7/8) FAIL
test_replicated_database/test.py::test_replicated_table_structure_alter FAIL cidb, issue ISSUE EXISTS
Upgrade check (amd_release) FAIL
Error message in clickhouse-server.log (see upgrade_error_messages.txt) FAIL cidb IGNORED
Bugfix validation (unit tests) ERROR
Compile before-binary (ninja unit_tests_dbms, without the fix) ERROR IGNORED

AI Review

Summary

This PR moves system-log element construction under a memory-tracker blocker and replaces several shared/borrowed carriers with owned copies, which addresses the main max_memory_usage_for_user drift mechanism. The remaining problem is that the new compile-time "self-contained" proof still has two current holes, so the PR is overstating the guarantee it now provides.

Findings

⚠️ Majors

  • [src/Common/SystemLogBase.cpp:57-67] [dismissed by author -- https://github.com/Fix per-user memory tracker drift from system log elements #110708#discussion_r3614375879] The new static gate no longer proves the invariant the PR description claims. Field, Array, Map, and Tuple are whitelisted wholesale even though Field::CustomType is backed by std::shared_ptr, so a log element that ever starts carrying Array{CustomType{...}} will still pass ASSERT_SELF_CONTAINED_LOG_ELEMENT. The comment acknowledges the hole, but the code and PR text still present this as an enforcement mechanism, not just a best-effort tripwire. Suggested fix: recurse into Field or explicitly reject the CustomType branch instead of trusting these containers wholesale.
  • [src/Common/SelfContained.h:117-123] [dismissed by author -- https://github.com/Fix per-user memory tracker drift from system log elements #110708#discussion_r3629904231] std::string_view is still treated as automatically self-contained, but system.text_log stores one in the queued row today. logToSystemTextLogQueue copies Poco::Message::getFormatString() into TextLogElement::message_format_string, Poco::Message itself stores that as another borrowed view, and TextLogElement::appendToBlock reads it later on the flush thread. That means the PR still blesses a non-owning log field in a live system log type, so the "self-contained log element" contract is still false in current code. Suggested fix: store message_format_string as an owning String and stop admitting generic std::string_view through the trivially-copyable shortcut.
Final Verdict

Status: ⚠️ Request changes

@clickhouse-gh clickhouse-gh Bot added the pr-bugfix Pull request with bugfix, not backported by default label Jul 16, 2026
Algunenano and others added 2 commits July 16, 2026 18:10
Converting SystemLog add(elem) to the fill-in-place add([&]{...}) callback
moved guarded-member access into a lambda, which Clang's thread-safety
analysis cannot see the lock through. Read/move the guarded previous values
into locals around the callback in ErrorLog and MetricLog. Also consume the
unused sleeper request in the drift integration test to satisfy ruff.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The new fill-in-place SystemLogBase::add default-constructs the element,
which tidy's member-init check flags when an aggregate's implicit
constructor leaves POD fields uninitialized. Value-initialize the element
(also safer, since the fill callback need not set every field); this
covers every log element except the KafkaDetails alternative of a
default-constructed std::variant, which needs its own in-class
initializers. Also pass current_address (now an optional) directly to
Session::authenticate instead of dereferencing and re-wrapping it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread src/Interpreters/MetricLog.cpp Outdated
…llectors

The mutex is held; thread-safety analysis simply cannot see the lock
through the add() callback. Access the guarded previous_values /
previous_profile_events directly inside the callback and suppress the
false positive on that access with TSA_SUPPRESS_WARNING_FOR_*, rather
than copying or moving the state out and back. This also removes the
exception-safety hazard of leaving the member moved-out if the callback
throws.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread src/Common/SystemLogBase.cpp
Field can hold a CustomType alternative backed by a shared_ptr, so
whitelisting Field/Array/Map/Tuple as self-contained leaves means the
gate cannot prove the invariant for elements carrying them. Record this
as a deliberate tripwire limitation: the elements that use these types
never store a CustomType, and the runtime sanitizer covers the rest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@SmitaRKulkarni SmitaRKulkarni self-assigned this Jul 20, 2026
…ntained-elements

# Conflicts:
#	src/Core/Settings.cpp
#	src/Core/Settings.h

@SmitaRKulkarni SmitaRKulkarni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Algunenano and others added 5 commits July 21, 2026 12:21
Storing the socket address by value made ClientInfo.h include
<Poco/Net/SocketAddress.h>, whose socket headers transitively pull in
<termios.h> on ppc64le, defining the CR1/CR2/CR3 macros. These collide
with parameter names in LLVM's ConstantRange.h in translation units that
include ClientInfo.h (via Context.h) before the LLVM headers, e.g.
FunctionsConversion.cpp. Undef the macros at the source of the inclusion,
matching the existing workaround in Native.cpp.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread src/Common/SelfContained.h
@clickhouse-gh

clickhouse-gh Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

📊 Cloud Performance Report

✅ AI verdict: no_change — no significant changes across 37 queries analysed

This PR reworks how system-log elements are built and enqueued (in-place fill under a memory-tracker blocker, a compile-time self-containment gate, and value-typed ErrorCounter/ClientInfo members) — logging plumbing, not the query-execution hot path. ClickBench Q4 showed a −15.6% median improvement but CPU time is essentially unchanged (~2690 ms both sides), so the delta is run-to-run variance and was correctly kept as no_change within master's current variance band. No real per-query regression or improvement is attributable to this change.

clickbench

🟢 No significant changes

tpch_adapted_1_official

🟢 No significant changes

Debug info
  • StressHouse run: 131c754c-ec32-42aa-8653-c06296a817d6
  • MIRAI run: c957e12d-20c8-40bb-98a6-0c1350e68e6f
  • PR check IDs:
    • clickbench_155623_1784895691
    • clickbench_155777_1784895776
    • clickbench_155791_1784895777
    • tpch_adapted_1_official_155979_1784895782
    • tpch_adapted_1_official_156081_1784895798
    • tpch_adapted_1_official_156143_1784895800

@clickhouse-gh

clickhouse-gh Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

LLVM Coverage Report

Metric Baseline Current Δ
Lines 86.20% 86.20% +0.00%
Functions 92.00% 92.00% +0.00%
Branches 78.30% 78.30% +0.00%

Changed lines: Changed C/C++ lines covered: 767/849 (90.34%) · Uncovered code

Full report · Diff report

@Algunenano

Copy link
Copy Markdown
Member Author

All three failures classified. Here's the summary.

PR #110708 — CI failure summary

PR: SystemLog per-user memory-tracker drift fix (makes system log elements self-contained; related to issue #81541, escalation 7909).
Report SHA: 899287e (3 distinct failures).

Test: test_replicated_database/test.py::test_replicated_table_structure_alter
Verdict: FLAKY
Master freq (7/14/30/90d): 61 / 136 / 140 / 140
Issue: tracked #110036 (open)
Root cause: Master-wide regression since 2026-07-10; DatabaseReplicated recovery race, column not replicated in time
Recommendation: Retry — unrelated to this PR
────────────────────────────────────────
Test: Error message in clickhouse-server.log (Upgrade check)
Verdict: FLAKY / generic
Master freq (7/14/30/90d): n/a (bucket)
Issue: generic bucket, untracked
Root cause: Noisy upgrade-check bucket: 184 fails on 2026-07-24 across ~150 unrelated PRs. Errors (part metadata CORRUPTED_DATA;
metric_log metadata exceeding max_query_size) are pre-existing upgrade noise, not PR-specific
Recommendation: Retry — not caused by this PR
────────────────────────────────────────
Test: Compile before-binary (ninja unit_tests_dbms, without the fix) (Bugfix validation)
Verdict: NEW-TEST / expected
Master freq (7/14/30/90d): n/a
Issue: none needed
Root cause: The PR adds gtest_client_info_read.cpp, which uses the new ClientInfo API. Bugfix validation reverts the fix
(ClientInfo.cpp/.h) but keeps the new test, so it fails: gtest_client_info_read.cpp:196/233: error: no viable overloaded '='
Recommendation: Expected artifact, not a defect

@Algunenano
Algunenano added this pull request to the merge queue Jul 24, 2026
Merged via the queue into ClickHouse:master with commit 64cf1c2 Jul 24, 2026
175 of 179 checks passed
@Algunenano
Algunenano deleted the system-log-self-contained-elements branch July 24, 2026 16:51
@clickgapai

Copy link
Copy Markdown
Contributor

Found via ClickGap automated review. Please close or comment if this is incorrect or needs adjustment.

TL;DR

Fix per-user memory tracker drift from system log elements

ClickGap verified: reproduced it with the script below on 26.6; tested 7 version(s) — 6 affected, 1 clean (see Affects); long-standing (not a recent regression); introducing PR not yet pinned.

Reproduction

Shell reproducer — run against a local ClickHouse build (bash <script> <clickhouse-binary>); requires the referenced test fixtures from the repo.

On a server with query_log + query_views_log enabled:
CREATE TABLE t (k UInt64, s String) ENGINE = MergeTree ORDER BY k;
CREATE MATERIALIZED VIEW mv1 ENGINE = SummingMergeTree ORDER BY k AS SELECT k, count() AS c FROM t GROUP BY k;
CREATE MATERIALIZED VIEW mv2 ENGINE = SummingMergeTree ORDER BY s AS SELECT s, count() AS c FROM t GROUP BY s;
CREATE USER u IDENTIFIED WITH no_password; GRANT INSERT ON *.* TO u;
-- keep the user alive so its per-user tracker is not reset to zero:
(background) clickhouse-client --user u --query "SELECT sleepEachRow(1) FROM numbers(600) SETTINGS max_block_size=1 FORMAT Null"
-- 50 inserts that each produce large query_log/query_views_log elements:
for i in $(seq 1 50); do clickhouse-client --user u --log_queries=1 --log_query_views=1 --log_comment="$(printf 'x%.0s' $(seq 1 100000))" --query "INSERT INTO t VALUES ($i,'v')"; done
SYSTEM FLUSH LOGS;
SELECT memory_usage FROM system.user_processes WHERE user = 'u';
Observed symptom: the user's memory_usage floor is inflated by ~tens of MB (roughly the total size of the logged elements) and never comes back down even after SYSTEM FLUSH LOGS destroys those elements; a user with a continuous workload eventually trips max_memory_usage_for_user with this phantom memory. The same workload with logging disabled leaves only sub-MB noise.

Bisect

  • Reproduces on every release line tested down to v22.3.20.29 (the oldest with a downloadable binary for this repro). The introducing change predates v22.3.20.29, so it can't be pinned from release binaries — this is a long-standing issue, not a recent regression. A maintainer with full git history can git blame the relevant file/line to find the exact change.

Affects

  • Fix is on master — this PR's change fixes the bug on master. Branches below still have the bug and need the backport.
  • Reproduces on: 26.6, 26.5, 26.4, 26.3, 26.2, 26.1
  • Does NOT reproduce on: master
  • Backport needed: 26.6, 26.5, 26.4, 26.3, 26.2, 26.1

Suggested next step

The fix has landed on master; the affected release branches above still need the backport.

Component: comp-logging
Severity: P2

cc fix PR reviewer: @SmitaRKulkarni


ClickGap · Finding: phase_d_pr110708

Triage metadata

  • Primary component: comp-logging
  • Secondary components: comp-memory
  • Component owners (comp-logging): @vitlibar @pamarcos

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

Labels

memory When memory usage is higher than expected pr-bugfix Pull request with bugfix, not backported by default pr-synced-to-cloud The PR is synced to the cloud repo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants