Skip to content

Restore anonymous usage telemetry - #6927

Open
dayaffe wants to merge 4 commits into
mainfrom
david.yaffe/restore-telemetry
Open

dayaffe wants to merge 4 commits into
mainfrom
david.yaffe/restore-telemetry

Conversation

@dayaffe

@dayaffe dayaffe commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Description

Restores the anonymous usage telemetry removed in #6431. Nodes send it to telemetry.quickwit.io, a relay operated by Datadog that forwards it to Datadog Logs.

  • Reverts Remove telemetry #6431, resolving conflicts with changes made since: the SIGHUP TLS reload and the new CLI argument parsing.
  • Makes QW_DISABLE_TELEMETRY fail closed: any non-empty value other than false, no or 0 disables telemetry. Values such as on used to leave it enabled.
  • Reports more: machine size, build info, a persistent install ID and an install event, a hashed cluster ID, the ingest v1/v2, S3 flavor and docs clustering features, bucketed API request counts, and, from the control plane, the index count and the source types.
  • The relay is in a separate, internal repository, deployed on AWS Lambda behind CloudFront. It rejects payloads that don't match the format, never stores IP addresses, replaces the host and cluster hashes with keyed hashes, and records the country and city that CloudFront derives from the IP address.
  • Rewrites docs/telemetry.md to list everything that is sent and where it goes.

Once telemetry.quickwit.io points at the relay, releases 0.6.0 to 0.8.x, which still send to that address, start reporting again.

How was this PR tested?

Unit tests for the telemetry crate, the CLI, the API counters and the control plane summary. Deployed the relay to a throwaway AWS account and ran a Quickwit node through it twice, with indexes, sources and requests on each counted API. All events and fields reached a test Datadog org, and the install event was only sent on the first run.

This reverts commit a2dd31c.

Conflict resolutions against current main:
- Cargo.toml files: keep quickwit-transport, quickwit-telemetry-exporters
  and procfs; add quickwit-telemetry back. The workspace no longer has a
  default-members list, so only members and workspace.dependencies change.
- quickwit-cli/src/main.rs: keep parse_cli_command() and have it call the
  restored about_text().
- quickwit-cli/src/service.rs: keep the SIGHUP TLS reload. The service
  override now happens in load_node_config(), so only the telemetry loop
  and RunCommand/EndCommand events are restored.
- Drop the datafusion-ci workflow, datafusion sandbox and
  metrics_distributed_tests changes: those files were removed in #6809.
- Set QW_DISABLE_TELEMETRY in full-tests.yml (added in #6718), since
  make test-all runs RunCliCommand::execute from quickwit-cli tests.
- Cargo.lock: only adds the telemetry crate and its dependencies, with no
  other re-resolution.
- Add a telemetry relay in distribution/telemetry-relay (Cloudflare
  Worker). It validates the payload and keeps only documented fields,
  never reads or forwards client IP addresses or headers, replaces the
  unsalted MD5 host hash with an HMAC keyed with a relay-only secret,
  and forwards one Datadog log per event.
- Rewrite docs/telemetry.md: Datadog receives the data through the
  relay, list the events, services and features actually sent, and fix
  the --help example.
- Make QW_DISABLE_TELEMETRY fail closed: any non-empty value other than
  false, no or 0 disables telemetry. Values such as "on" used to leave
  telemetry enabled, contrary to the docs.
- Report more features from the node config: the storage backend of
  the default index root URI, Lambda leaf search, split cache and TLS.
Add the machine size, build info, a persistent install ID, a hashed
cluster ID, more features, API request counts and a cluster summary.
The relay runs behind CloudFront, which provides the country and city.
@dayaffe
dayaffe requested a review from a team as a code owner October 22, 2026 17:27
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-23T19:16:37.719925Z e139dec New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9985cfdea6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread quickwit/quickwit-cli/src/telemetry.rs Outdated
Comment on lines +34 to +35
let mut redacted_metastore_uri = config.metastore_uri.clone();
redacted_metastore_uri.redact();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Strip the database user before hashing cluster identity

For PostgreSQL metastore URLs, Uri::redact() masks only the password while preserving the username, so including this redacted URI in the hash makes nodes from the same cluster report different cluster identities when they use per-node database users or rotate the user. It also contradicts the documented credential-independent identity; remove the complete userinfo component before hashing.

Useful? React with 👍 / 👎.

Comment on lines +102 to +105
pub fn running() -> Self {
TelemetryEvent::Running {
api_usage: API_REQUEST_COUNTS.take(),
cluster_summary: cluster_summary(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Defer resetting counters until the summary is queued

When the ten-event queue is full—for example, after repeated UI page loads during the one-minute cooldown—running() resets all API counters here before EventsState::push_event rejects the summary event. The next report therefore permanently undercounts that interval; end_command() has the same issue at shutdown. Preserve the counts unless the summary event is successfully enqueued.

Useful? React with 👍 / 👎.

Comment thread .github/workflows/ci.yml
Comment on lines 19 to +21
env:
CARGO_INCREMENTAL: 0
QW_DISABLE_TELEMETRY: 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Disable telemetry in the UI end-to-end workflow

The opt-out is added to the Rust CI workflows, but .github/workflows/ui-ci.yml still starts a real server with cargo run --features=postgres -- run ... in its e2e job without QW_DISABLE_TELEMETRY. Every UI CI run will therefore send install, lifecycle, UI, and API-usage events to the production relay and contaminate the usage data; set the same environment variable on that job.

Useful? React with 👍 / 👎.

Comment thread quickwit/quickwit-cli/src/telemetry.rs Outdated
Comment on lines +73 to +77
if config.indexer_config.enable_otlp_endpoint {
features.insert(QuickwitFeature::Otlp);
}
if config.jaeger_config.enable_endpoint {
features.insert(QuickwitFeature::Jaeger);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Gate endpoint features on the services that expose them

With production defaults, these flags are true even on a metastore-only or control-plane-only node, so such nodes report both otlp and jaeger despite exposing neither endpoint. serve_quickwit only constructs OTLP services when Indexer is enabled and the Jaeger service when Searcher is enabled; apply those same service checks here to avoid substantially overstating feature adoption.

Useful? React with 👍 / 👎.

Comment on lines +225 to +227
const city = headers.get(CITY_HEADER);
if (city !== null && CITY_RE.test(city)) {
location.city = city;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Decode CloudFront city names before storing them

CloudFront percent-encodes non-ASCII characters in viewer-location headers, as documented by AWS, but this code stores the header verbatim. Cities such as São Paulo will consequently be recorded with encoded text such as S%C3%A3o Paulo, fragmenting location aggregation; safely percent-decode the header before validating and forwarding it.

Useful? React with 👍 / 👎.

Comment on lines +72 to +78
function parseNames(value, field) {
check(Array.isArray(value), `${field} must be a list`);
check(value.length <= MAX_NUM_NAMES, `${field} has more than ${MAX_NUM_NAMES} items`);
for (const name of value) {
check(isMatch(name, NAME_RE), `${field} contains an invalid name`);
}
return [...new Set(value)].sort();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Whitelist documented telemetry names

parseNames validates only the shape of each string, so arbitrary identifiers such as customer_logs or private-index are accepted and forwarded when placed in services, features, or cluster_summary.source_types. Because this relay is the privacy boundary intended to reject fields outside the documented schema, a malformed client can persist user-specific names that the Rust enums would never emit; use schema-versioned allowlists for each of these fields rather than a generic identifier regex.

Useful? React with 👍 / 👎.

@dayaffe dayaffe added area:core Server layer, CLI, config, actors, shared crates type:feature A new capability, such as an endpoint, query type, source, setting or command labels Oct 22, 2026
The relay now lives with its infrastructure in a separate repository.
Read the memory size with sysinfo, replace an invalid install ID instead
of failing, and trim the tests.

Leave the database user and query parameters out of the cluster hash,
take the API request counts only once their event is queued, report the
OTLP and Jaeger features only on the nodes that serve them, and disable
telemetry in the UI CI.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e139dec72e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +27 to +31
Ok(install_id_str) => {
if let Ok(install_id) = Uuid::parse_str(install_id_str.trim()) {
return Ok((install_id, false));
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject an invalid persisted install ID

If telemetry_install_id is truncated or corrupted—for example, by a crash during the non-atomic write below—this branch silently replaces it and returns created = true. The next startup therefore reports a second installation under a new UUID, breaking the promised persistent identity and contaminating install counts; treat an existing invalid file as an error and write newly created IDs atomically instead.

AGENTS.md reference: AGENTS.md:L19-L22

Useful? React with 👍 / 👎.

Comment on lines +192 to +194
if let Some((before_query, _)) = location.split_once('?') {
location = before_query;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Strip metastore URI fragments from the cluster hash

For file-backed and S3 metastores, Quickwit encodes the polling setting as a URI fragment such as #polling_interval=30s, but this normalization strips only ? parameters. Nodes using the same cluster ID and storage location but different polling intervals therefore produce different cluster hashes, fragmenting the node and cluster counts; remove the fragment before computing the digest as well.

Useful? React with 👍 / 👎.

This branch has not been deployed

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

Labels

area:core Server layer, CLI, config, actors, shared crates type:feature A new capability, such as an endpoint, query type, source, setting or command

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant