Skip to content

Add ClickHouse profiler - #2574

Open
dey-abhishek wants to merge 33 commits into
mainfrom
feat/profiler/clickhouse
Open

Add ClickHouse profiler#2574
dey-abhishek wants to merge 33 commits into
mainfrom
feat/profiler/clickhouse

Conversation

@dey-abhishek

@dey-abhishek dey-abhishek commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Changes

What does this PR do?

Adds ClickHouse as a first-class citizen profiler source on Lakebridge, covering both ClickHouse Cloud and self-managed / OSS. It conforms to the existing Lakebridge profiler contract (registration, credential configurator, connector, pipeline config, DuckDB output), so it behaves consistently with the other sources (mssql, bigquery, redshift, synapse, oracle, teradata, snowflake).

The analytics portion of the standalone tool is out of scope; only the read-only system.* metadata profiling is ported.

Architecture: SQL/DDL steps (not a Python extractor)

The profiler is built from paired ddl + sql profiler steps, matching the mssql/oracle/teradata profilers — there is no monolithic Python extractor or collector framework. Each of the ~64 result sets is a typed DuckDB CREATE TABLE (the ddl step) plus a read-only system.* query (the sql step). SQL is easier to maintain and debug than a custom Python script, and it keeps ClickHouse consistent with every other SQL-based source.

Relevant implementation details

  • Source registrationclickhouse added to PROFILER_SOURCE_SYSTEM with an AUTO variant. The oss vs cloud variant is auto-detected at run time (no user selection), mirroring the mssql pattern: a *.clickhouse.cloud host or the cloud_mode server setting decides.
  • Shared vs split SQL — replicated-metadata queries live once in the clickhouse/ root and are shared by both variants. The per-node append-only log tables (query_log, session_log, query_views_log, asynchronous_insert_log) are split into oss/ (read directly) and cloud/ (wrapped in clusterAllReplicas('default', ...) so all replicas are covered) — the same shared-root + variant-dir layout as mssql single_db/multi_db. Log-table steps are optional: true, so a build without a given table degrades to ABSENT instead of failing the run.
  • ConnectorClickHouseConnector over the clickhouse-connect HTTP client (native-client style, like RedshiftConnector, not SQLAlchemy). secure/port defaults are host-derived (TLS + 8443 for Cloud hosts, plaintext + 8123 otherwise) so it is never insecure-by-default for Cloud. Driver errors are wrapped in ConnectionError so the pipeline can classify a missing/disabled system.* table as ABSENT (mirrors the other connectors).
  • Credential configuratorConfigureClickHouseAssessment prompts host/port/user/password/secure and an optional Cloud API block (key_id/key_secret, optional org/service id, optional tier override). There are no days_back/redact knobs: the query-history window is a fixed 30 days and redaction is always on (see below).
  • Redaction is always-on, in SQL — sensitive fields (SQL text, auth params, host IPs/allow-lists, row-policy filters) are emitted as a '[REDACTED]' literal directly in the sql step, so the real value never leaves the source. Array/tuple columns are JSON-encoded with toJSONString(). Aggregate metrics, counts, and grant structure are always preserved.
  • DDL-first, so every table always exists — because the ddl step creates the typed table before the sql step populates it, the output schema is stable across runs and deployments even when a result set is empty (consistent with the mssql *_ddl.sql approach).
  • Costactual billed cost only, pulled from the ClickHouse Cloud usageCost API. This is the one remaining Python step (cloud/cost_enrich.py), run optionally in the Cloud pipeline only, since the billing figures live behind the Cloud REST API and SQL cannot reach them. It writes provider/region, real compute sizing, plan tier (recorded as metadata, no pricing math), and the billed cost into costs_pricing_config. OSS emits a static costs_pricing_config row via SQL (no dollar figures) and always reports a resource footprint.

Caveats/things to watch out for when reviewing:

  • Adds a runtime dependency: clickhouse-connect~=1.4.2 (+ transitive lz4, zstandard).
  • The Cloud-only cost_enrich.py is the sole Python step; if cloud_api credentials are absent or the API is unreachable it writes a "no enrichment" row and the step still succeeds (it is optional: true).
  • The query-history window is hardcoded to INTERVAL 30 DAY in the log-table SQL.
  • Verified end-to-end against a live ClickHouse Cloud service and a local OSS instance: variant auto-detection, all steps COMPLETE (with session_log correctly ABSENT on OSS), 77 tables per run, redaction, Cloud actual-cost + tier detection, OSS footprint-only.

Linked issues

Resolves #..

Functionality

  • added relevant user documentation (docs/.../assessment/profiler/clickhouse.mdx + profiler index entry)
  • added new CLI command
  • modified existing command: databricks labs lakebridge configure-database-profiler / execute-database-profiler (new clickhouse source)

Tests

  • manually tested (live ClickHouse Cloud + local OSS, both variants, output verified)
  • added unit tests (variants, connector, configurator, Cloud cost enrichment, CLI)
  • added integration tests

Add ClickHouse as a first-class profiler source, ported from the standalone
Field Engineering ClickHouse profiler and reshaped to the Lakebridge profiler
contract:

- Register 'clickhouse' with an AUTO variant; resolve oss vs cloud from the
  cloud_mode server setting / *.clickhouse.cloud host (mirrors mssql).
- Add ClickHouseConnector (clickhouse-connect HTTP client) and a credential
  configurator; host-derived secure/port defaults.
- Single Python extract step runs 7 read-only system.* collectors (workload,
  objects, features, dependencies, utilization, security, costs) and writes one
  DuckDB table per result set; empty result sets create typed stubs from a
  declared schema catalog so every table always exists.
- Cost is actual-billed-cost only via the ClickHouse Cloud usageCost API (no
  rate card); plan tier (Basic/Scale/Enterprise) recorded as metadata; OSS
  reports a resource footprint with no dollars.
- Sensitive-field redaction on by default (SQL text, auth params, IPs,
  row-policy filters).
- Unit tests for variants, connector, extract, configurator, and CLI; docs page
  and profiler index entry.
@dey-abhishek
dey-abhishek requested a review from a team as a code owner July 16, 2026 17:44
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.74648% with 49 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.52%. Comparing base (bd5cc18) to head (2ace58a).

Files with missing lines Patch % Lines
...idge/resources/assessments/clickhouse/cloud_api.py 73.52% 17 Missing and 10 partials ⚠️
...ources/assessments/clickhouse/cloud/cost_enrich.py 87.75% 7 Missing and 5 partials ⚠️
...ks/labs/lakebridge/connections/database_manager.py 85.00% 5 Missing and 1 partial ⚠️
...abs/lakebridge/assessments/configure_assessment.py 83.33% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2574      +/-   ##
==========================================
+ Coverage   71.19%   71.52%   +0.32%     
==========================================
  Files         111      114       +3     
  Lines       10071    10355     +284     
  Branches     1113     1153      +40     
==========================================
+ Hits         7170     7406     +236     
- Misses       2687     2717      +30     
- Partials      214      232      +18     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@dey-abhishek dey-abhishek self-assigned this Jul 16, 2026
@dey-abhishek dey-abhishek added the feat/profiler Issues related to profilers label Jul 16, 2026
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown

✅ 179/179 passed, 2 skipped, 2h35m9s total

Running from acceptance #5408

Addresses the Isaac Review finding: only CostsCollector wrapped
system.query_log in clusterAllReplicas() on ClickHouse Cloud, so
Workload/Features/Dependencies/Utilization/Security queried per-node log
tables directly and under-reported on multi-replica Cloud services (ClickHouse
docs: query_log is local to each node; a complete view needs clusterAllReplicas).

- Add BaseCollector.source(table): wraps per-node append-only log tables
  (query_log, session_log, query_views_log, asynchronous_insert_log) in
  clusterAllReplicas('default', ...) on Cloud; replicated metadata stays direct.
- Detect is_cloud once in the extract and share via config; enable
  skip_unavailable_shards once as a session setting (drops per-query SETTINGS).
- Use self.source(...) across all six collectors; also fixes the latent
  asynchronous_insert_log gap in costs. Remove the duplicate CostsCollector._detect_cloud.
- Reset the output DuckDB at the start of each run so re-running a different
  variant into the same folder replaces variant-shaped tables (e.g.
  costs_pricing_config) instead of TRUNCATE-into-stale-schema.
- Tests: source() wrapping, all collectors cluster-read on Cloud, same-folder
  variant re-run reset. Verified live on OSS + Cloud.
@dey-abhishek dey-abhishek changed the title Add ClickHouse profiler (Cloud & OSS) Add ClickHouse profiler Jul 16, 2026
dey-abhishek and others added 2 commits July 17, 2026 21:41
The costs collector reported only the profiled service's billed cost, which
understates total cost of ownership when an org has org-level charges (backups,
ClickPipes, shared) or multiple services not attributed to a single serviceId.

- summarize_usage_cost now surfaces org_total_usd from the usageCost
  grandTotalCHC (org-wide grand total across all services + org-level charges).
- actual_billed_cost now reports service_total_usd (profiled service),
  org_total_usd (TCO), and total_usd/monthly_total_usd as the TCO headline
  (org total when available, else the service total).
- Unit tests for the org-total TCO logic and the no-grand-total fallback.
@dey-abhishek
dey-abhishek enabled auto-merge July 19, 2026 19:57
dey-abhishek and others added 3 commits July 21, 2026 01:23
Blocking review fixes (PR #2574):
- Share host-derived TLS/port normalization (normalize_secure_and_port)
  across the extraction ClickHouseConnection and the probe
  ClickHouseConnector so a Cloud host is never plaintext-by-default and a
  stray secure:"false" can't downgrade it; parse_bool fixes bool("false").
- Redact previously-missed sensitive fields: dictionaries.source,
  mutations.command/latest_fail_reason, columns.default_expression.
- Rename the extrapolated monthly_total_usd to monthly_total_usd_projected
  so it can't be mistaken for an actual bill (actual-billed-only contract).
- Honor the cloud_api.tier override (was silently dropped; only top-level
  tier was read).

Non-blocking:
- safe_query classifies missing-object errors (WARN) vs real
  permission/SQL/connection errors (surfaced as ERROR).
- Coerce days_back in execute() instead of aborting on a bad value.
- _detect_region uses the suffix host-match helper, not a substring.
- Docs: soften the "every table always created" claim.

Fix configure-database-profiler tier prompt looping on Enter: an empty
question default re-prompts in blueprint, so gate the tier override behind
a yes/no confirm + choice.

Docs: add a Credentials File Format section (sanitized OSS/Cloud examples).

Tests: add coverage for extraction-connection secure defaults, redaction of
the new fields, no extrapolated dollar field, tier override winning over the
API, safe_query classification, days_back coercion, and the tier-override
confirm flow (incl. auto-detect default).

Co-authored-by: Isaac
@m-abulazm

Copy link
Copy Markdown
Contributor

@dey-abhishek why are we using a python script instead of SQL?

# Conflicts:
#	src/databricks/labs/lakebridge/connections/database_manager.py
#	tests/unit/assessment/test_assessment.py
#	tests/unit/connections/test_database_manager.py
#	uv.lock
@dey-abhishek

dey-abhishek commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

@m-abulazm

Good question. The short answer is that the ClickHouse collectors aren't a fixed set of queries — they make decisions while they run, so a plain .sql file wouldn't cover it.

A few concrete reasons:

  • The same query has to run differently on Cloud vs OSS. On Cloud the per-node log tables (query_log, session_log, etc.) only show the local replica, so we wrap them in clusterAllReplicas(...); on OSS we read them directly. We only know which one we're hitting after probing is_cloud at runtime, so a static file would be wrong for one of the two.
  • We want to degrade gracefully on OSS. Some system.* tables/columns just don't exist on older OSS builds. safe_query() looks at the ClickHouse error code and treats a missing object (16/47/60/81) as an empty result + warning, while still surfacing real failures like permissions or bad SQL. You can't branch on an error code from a SQL script.
  • Part of it isn't SQL at all. For cost we call the ClickHouse Cloud REST API to get the real service config (provider, region, replica memory/count) and merge that with the query output, so the numbers reflect the actual service instead of a guess from the hostname.
  • There's runtime work around the results too — redacting sensitive fields, coercing days_back to an int before it goes into INTERVAL ... DAY, and JSON-encoding ClickHouse types like Decimal/UUID/bytes.

It also matches what we already do — Synapse and BigQuery are Python extract scripts for the same reasons. The .sql-file pattern (mssql, oracle, teradata, snowflake) is used where a static query set is enough; anything that needs branching or an API call is Python.

- Redact sensitive fields nested inside struct/map columns (redact_structure),
  not just top-level keys, on the query-result row path.
- Harden ClickHouseCloudAPI._get: raise CloudAPIError on non-JSON / non-dict responses.
- Normalize host case in discover_service so a case-only mismatch still resolves the service.
- Strengthen the schema-coverage test to drive collect() under both variants
  (catches dynamic result-set keys a regex scan misses).
- _detect_cloud re-raises real connection/permission errors instead of silently
  treating them as OSS; only genuine missing-object errors degrade to OSS.

Verified end-to-end against live OSS and Cloud ClickHouse instances.

Co-authored-by: Isaac
Docstring-only edit; no behavior change.

Co-authored-by: Isaac
The merge in 17e2d03 regenerated uv.lock with local dev-proxy
(pypi-proxy.dev.databricks.com) registry and package URLs, which CI cannot
reach. Rewrite them back to public pypi.org / files.pythonhosted.org, matching
the scrub in `make lock-dependencies`. No dependency versions change.

Co-authored-by: Isaac
Replaces the cross-module protected-access call (which needed a
`# pylint: disable=protected-access` the CI no-cheat check forbids) by moving
_is_missing_object_error to a module-level `is_missing_object_error` in base.py.
No behavior change.

Co-authored-by: Isaac
- Rename _detect_cloud -> detect_cloud (public) so the unit tests calling it no
  longer trigger protected-access (W0212).
- Absorb urlopen's timeout kwarg via **_kwargs in the cloud_api test fake
  (unused-argument, W0613).
- Correct a stale CostsCollector._detect_cloud reference in a test docstring.

No behavior change.

Co-authored-by: Isaac
Replace the monolithic Python extractor (ch_metadata_extract.py + 7 collector
classes + connection/redaction helpers, ~2,150 lines) with paired ddl+sql
profiler steps, matching the mssql/oracle/teradata profilers. Each of the ~64
result sets is now a typed DuckDB DDL plus a system.* SQL query.

- Shared replicated-metadata SQL lives in the clickhouse/ root; per-node log-table
  reads (query_log, session_log, query_views_log, asynchronous_insert_log) are split
  into oss/ (direct) and cloud/ (clusterAllReplicas) variants, like mssql single_db/
  multi_db. Log-table steps are optional: true so a build without them degrades to
  ABSENT instead of failing the run.
- Redaction is always-on via '[REDACTED]' SQL literals (sensitive columns never leave
  the source); array columns are JSON-encoded with toJSONString(). The query-history
  window is a fixed 30 days. The now-dead days_back/redact configurator knobs are removed.
- The Cloud billing enrichment is the one remaining Python step (cost_enrich.py), run
  optionally in the cloud/ pipeline only, since the usageCost figures live behind the
  ClickHouse Cloud REST API. OSS emits a static pricing_config row via SQL.
- Wrap ClickHouseConnector.fetch driver errors in ConnectionError so the pipeline can
  classify a missing/disabled system table as ABSENT (mirrors the other connectors).

Verified end-to-end against a local OSS container and a live ClickHouse Cloud service
(both variants: all steps COMPLETE, session_log ABSENT on OSS, real billed cost pulled
on Cloud). Updates configurator, tests, and docs accordingly.

Co-authored-by: Isaac
@dey-abhishek

Copy link
Copy Markdown
Contributor Author

Hi @m-abulazm - The profiler's collector feature has been now rewritten into SQL/DDL steps, as requested

Comment on lines +19 to +21
### 1. Download
- No ODBC driver is required. The ClickHouse profiler connects over the HTTP interface using the
[`clickhouse-connect`](https://clickhouse.com/docs/integrations/python) client, which is installed with Lakebridge.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

please remove this part. why even mention something if it is not needed nor in-scope

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines +509 to +515
Connection keys (``host``/``port``/``user``/``password``/``secure``) are stored flat so the
``ClickHouseConnector`` and the OSS-vs-Cloud variant probe can read them directly. The profiler
itself is SQL/DDL: the query-history window is a fixed 30 days and sensitive fields (SQL text, auth
params, host IPs, row-policy filters) are always redacted to ``[REDACTED]`` in the SQL, so there
are no ``days_back`` / ``redact`` knobs. An optional ``cloud_api`` block enables pulling the actual
billed cost + real sizing + plan tier from the ClickHouse Cloud API (Cloud only).
"""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
Connection keys (``host``/``port``/``user``/``password``/``secure``) are stored flat so the
``ClickHouseConnector`` and the OSS-vs-Cloud variant probe can read them directly. The profiler
itself is SQL/DDL: the query-history window is a fixed 30 days and sensitive fields (SQL text, auth
params, host IPs, row-policy filters) are always redacted to ``[REDACTED]`` in the SQL, so there
are no ``days_back`` / ``redact`` knobs. An optional ``cloud_api`` block enables pulling the actual
billed cost + real sizing + plan tier from the ClickHouse Cloud API (Cloud only).
"""
"""

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed

}
_save_to_disk(credential, cred_file)

logger.info(f"Credential template created for {source}.")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
logger.info(f"Credential template created for {source}.")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed

Comment on lines +299 to +301
Not SQLAlchemy-based (like Redshift), so it subclasses the ABC directly. Works against both
ClickHouse Cloud (``secure=True``, port 8443) and self-managed / OSS (port 8123). Only read-only
``system.*`` metadata queries are ever run through it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
Not SQLAlchemy-based (like Redshift), so it subclasses the ABC directly. Works against both
ClickHouse Cloud (``secure=True``, port 8443) and self-managed / OSS (port 8123). Only read-only
``system.*`` metadata queries are ever run through it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed

Comment on lines +20 to +36
def parse_bool(value: object, default: bool = False) -> bool:
"""Parse a credential value that may be a real bool or a string like ``"false"``.

``bool("false")`` is ``True`` in Python, so a hand-written credentials file that stores
``secure: "false"`` must be parsed on the string content, not coerced with ``bool()``. A real
bool passes through; a string is matched case-insensitively against the truthy token set;
anything else falls back to ``default``.
"""
if isinstance(value, bool):
return value
if value is None:
return default
if isinstance(value, str):
return value.strip().lower() in _TRUTHY
if isinstance(value, (int, float)):
return bool(value)
return default

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

our confirm prompts always return True or False literals. this extra parsing is unnecessary

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

return default


def normalize_secure_and_port(config: dict) -> tuple[bool, int]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we embed this inside the connector

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines +310 to +314
# The configurator always writes `secure` explicitly; this fallback is for a minimal
# hand-written credentials file. normalize_secure_and_port forces TLS/8443 for managed
# ClickHouse Cloud hosts (which only accept TLS) and defaults to plaintext/8123 for
# self-managed / OSS — never insecure-by-default for Cloud. Shared with the extraction
# ClickHouseConnection so both paths behave identically.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
# The configurator always writes `secure` explicitly; this fallback is for a minimal
# hand-written credentials file. normalize_secure_and_port forces TLS/8443 for managed
# ClickHouse Cloud hosts (which only accept TLS) and defaults to plaintext/8123 for
# self-managed / OSS — never insecure-by-default for Cloud. Shared with the extraction
# ClickHouseConnection so both paths behave identically.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed

Comment on lines +325 to +327
# Wrap driver errors as ConnectionError so the pipeline can classify them: an optional step
# that hits a missing/disabled system table (e.g. system.session_log on an OSS build) degrades
# to ABSENT instead of aborting the whole run. Mirrors the other connectors (e.g. Redshift).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
# Wrap driver errors as ConnectionError so the pipeline can classify them: an optional step
# that hits a missing/disabled system table (e.g. system.session_log on an OSS build) degrades
# to ABSENT instead of aborting the whole run. Mirrors the other connectors (e.g. Redshift).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed

}


def execute(credential_manager: CredentialManager, db_path: str) -> dict[str, Any]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
def execute(credential_manager: CredentialManager, db_path: str) -> dict[str, Any]:
def execute(credential_manager: CredentialManager, db_path: str, client: ClickHouseCloudAPI) -> dict[str, Any]:

please pass client to substitute the dependency in the tests

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

# Conflicts:
#	tests/unit/assessment/test_assessment.py
#	tests/unit/test_cli_other.py
dey-abhishek and others added 5 commits August 11, 2026 00:01
- docs: drop out-of-scope Download/ODBC note, renumber prerequisites
- configure_assessment: trim ClickHouse configurator docstring, drop the
  redundant trailing log line
- database_manager: inline secure/port normalization into ClickHouseConnector
  and trim verbose comments; drop the shared normalize_secure_and_port helper
- clickhouse/__init__: remove now-unused parse_bool (confirm prompts return
  real bools) and normalize_secure_and_port
- cost_enrich: inject ClickHouseCloudAPI client into execute() so tests can
  substitute the dependency; add execute() coverage
- _constants: register clickhouse as an AUTO variant source
New costs_read_locality metric attributes query reads to the local
filesystem cache vs the object-storage source (S3) using query_log
ProfileEvents (CachedReadBufferReadFromCacheBytes vs
CachedReadBufferReadFromSourceBytes, plus ReadBufferFromS3Bytes and
S3ReadRequestsCount), reporting a cache_hit_pct. Wired into the oss and
cloud pipeline configs (ddl + sql). Docs updated with the metric, the
Read Locality section, and the bare-minimum Cloud API-key roles
(Basic Service API Reader + Billing).
… steps

- cloud_api: fold unnamed CHC buckets (ClickPipes, dictionary, etc.) into
  `other` so the per-service billed total covers the whole metrics map
- database_manager: correct a cloud host's plaintext default port to the
  secure port so the forced-TLS override connects to the right place
- cost_enrich: report region alone for legacy hosts instead of guessing an
  AWS provider; cap the usage window at 30 inclusive dates (today - 29d)
- pipeline_config (cloud + oss): mark security_roles and
  security_settings_profiles steps optional
@dey-abhishek
dey-abhishek requested a review from m-abulazm August 16, 2026 15:58
@dey-abhishek

dey-abhishek commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

Added feature as requested by @alanchoi 0e82ff4

# Conflicts:
#	src/databricks/labs/lakebridge/connections/database_manager.py
#	tests/unit/connections/test_database_manager.py
ClickHouse returns count/byte/row/memory counters (and its unlimited
sentinel) as UInt64, whose upper half exceeds signed BIGINT range. DuckDB
raises a conversion error on insert that the profiler's optional:true net
does not catch, crashing the whole run instead of skipping one table.

Match the DuckDB column type to the ClickHouse source type: UInt64-sourced
metrics -> UBIGINT (as normalized_query_hash already does); leave UInt8
flags and signed Int32/Int64 sources (system.metrics.value, exception_code,
mutations.parts_to_do, metric_log CurrentMetric_* peaks) as BIGINT.
@dey-abhishek

Copy link
Copy Markdown
Contributor Author

Added UBIGINT changes as per code review comment by @Rubjit

dey-abhishek and others added 9 commits August 18, 2026 20:12
- Report org-wide TCO (grandTotalCHC) even when the profiled service has no
  billed records yet: gate the billed-cost block on record_count OR an org
  grand total, via _has_billed_cost(). Previously record_count==0 silently
  dropped the org total.
- Use a distinct note when metadata is captured but no billed cost is
  available, instead of _ENRICHED_NOTE claiming dollar figures that are absent.
- Degrade gracefully on a reachable-but-malformed API response: broaden
  execute()'s except around _fetch_cloud_metadata to structural errors
  (KeyError/IndexError/TypeError) that discover_service can raise, so the
  optional step writes a no-enrichment row instead of aborting the run.

Add regression tests for all three.
Cloud profiler SQL reads replicated system tables via
clusterAllReplicas('default', ...). ClickHouse Cloud always names its
cluster 'default', but a self-managed deployment profiled as cloud may use
a different name, making those (optional) steps fail with
CLUSTER_DOESNT_EXIST and come back empty.

ClickHouseConnector now honors an optional 'cluster' config value: when set
to something other than 'default', it rewrites the clusterAllReplicas cluster
in outgoing queries. Default behavior is unchanged (Cloud's 'default' stays
authoritative), and the name is validated against the identifier charset
before being spliced into SQL. Scoped entirely to ClickHouseConnector.

Verified e2e on an OSS box with a 'main_cluster' (no 'default') forced to the
cloud variant: without the override 33 cluster steps failed to ABSENT; with
cluster: main_cluster all resolved (153 COMPLETE, 0 CLUSTER_DOESNT_EXIST).
Explain that the Cloud variant reads via clusterAllReplicas('default', ...),
that ClickHouse Cloud always uses 'default' (no config needed), and that a
self-managed deployment profiled as Cloud with a differently-named cluster
sets 'cluster' in the credentials file to avoid CLUSTER_DOESNT_EXIST.
summarize_usage_cost bucketed metrics with case-sensitive exact keys and a
'DataTransferCHC' substring, so a re-cased/renamed metric key would fall into
'other' instead of its proper bucket. Classify each metric in a single
case-insensitive pass; 'other' stays the catch-all so the total always covers
the whole metrics map. Breakdown-only hardening: the total was already correct.
Avoids pylint W0212 (protected-access) by matching a distinctive phrase in the
note text, mirroring the existing no-creds test's style.
Tighten the explanatory comments added for the review fixes (cluster override,
billed-cost gating, malformed-API handling, case-insensitive bucketing) while
keeping the essential rationale.
# Conflicts:
#	src/databricks/labs/lakebridge/assessments/_constants.py
#	uv.lock
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat/profiler Issues related to profilers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants