Skip to content

P0.6 — elasticsql: cap-hit funnel instrumentation#130

Merged
fupelaqu merged 1 commit into
release-r1from
feature/P0.6
Jun 23, 2026
Merged

P0.6 — elasticsql: cap-hit funnel instrumentation#130
fupelaqu merged 1 commit into
release-r1from
feature/P0.6

Conversation

@fupelaqu

@fupelaqu fupelaqu commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Story P0.6 — Pricing-funnel instrumentation (cap-hits), elasticsql leg

Closes #129. Cross-repo P0.6 epic leg 1 of 4 (elasticsql → softclient4es-extensions → softclient4es-arrow → softclient4es-license-server). Phase 0 final story.

Cap-hits (count of license quota rejections by quota type) are the launch leading indicator — PRD §15.1 / pre-mortem Killer #2 — and the only missing funnel signal class (Epic 15 already ships conversion/activation/chasm). This leg adds the SEMANTIC cap-hit primitive to the shared TelemetryCollector and wires the maxQueryResults reject site.

Changes

  • licensing/.../TelemetryCollector.scala: new CapHitKind sealed trait {MaterializedViews, QueryResults, Joins, Clusters} (each with a snake-case wire key); four independent lock-free AtomicLong cap-hit DELTA buckets; incrementCapHit(kind); collectAndResetCapHits() (getAndSet, always all four keys); TelemetryData.capHitsByKind populated in collect (read, no reset — like joinQueryCount) and collectAndReset (getAndSet flush); Noop overrides. Mirrors the Story-15.3 JOIN-bucket pattern exactly.
  • core/.../extensions/CoreDqlExtension.scala: capture capHitCollector = licenseRefreshStrategy.telemetryCollector at initialize; incrementCapHit(QueryResults) on both capOrReject branches — the explicit-LIMIT 402 reject AND the P0.5 no-LIMIT truncation/cappedScroll bite (OQ-2). Suppressed JOIN legs (ResultCapContext.isSuppressed) do not count → no double-count. The reject itself is unchanged (AC8: increment is a side-effect before the existing return). The closed EnforcedDqlExtension inherits the increment via the shared capOrReject.
  • Tests: TelemetryCollectorSpec (+10 cap-hit cases: per-kind isolation, all-4-keys, collect-no-reset, collectAndReset delta, concurrency, Noop no-op), CoreDqlExtensionSpec (+5: 402 branch + truncation branch increment, no-increment for within-quota/suppressed-leg/Enterprise).

Design decisions

  • OQ-1 — instrument by SEMANTIC quota-rejection (incrementCapHit(kind) at each reject site), NOT by 'HTTP status == 402' (only MV + maxQueryResults raise a 402; joins return Left(String), clusters a LicenseError/sys.exit).
  • OQ-2 — the P0.5 no-LIMIT truncation IS counted as a QueryResults cap-hit (the meter biting non-fatally).
  • Cap-hits ride the existing InstancePing daily-ping delta (read via collect, like joinQueryCount); the license-server computes per-instance deltas from the stored previous snapshot.

Build & verify

  • + core/compile + + licensing/compile + + softclient4es-core-testkit/compile (cross 2.12 + 2.13) — green
  • licensing/testOnly *TelemetryCollectorSpec — 24 passed; core/testOnly *CoreDqlExtensionSpec — 14 passed; headerCheck — clean
  • publishLocal'd softclient4es-core + softclient4es-licensing 0.20-SNAPSHOT (2.12+2.13) — unblocks the extensions + arrow legs

Sibling PRs

  • softclient4es-extensions (MV cap-hit + thread cap-hits into InstancePing): to follow
  • softclient4es-arrow (Joins + Clusters cap-hits + shutdown flush): to follow
  • softclient4es-license-server (proto fields 14-17 + instance_ping migration + Grafana panel): to follow

🤖 Generated with Claude Code

…csql leg)

Add the SEMANTIC cap-hit primitive to the shared TelemetryCollector and wire
the maxQueryResults reject site (CoreDqlExtension). Cap-hits are the launch
leading indicator (PRD section 15.1, pre-mortem Killer #2) - the only missing
funnel signal class. They ride the existing InstancePing daily-ping delta.

- TelemetryCollector: CapHitKind {MaterializedViews, QueryResults, Joins,
  Clusters} + 4 lock-free AtomicLong DELTA buckets + incrementCapHit(kind) +
  collectAndResetCapHits() (getAndSet, all-4-keys) + TelemetryData.capHitsByKind
  (collect reads, collectAndReset getAndSet) + Noop overrides. Mirrors the
  Story-15.3 JOIN-bucket pattern.
- CoreDqlExtension: capture capHitCollector = strategy.telemetryCollector at
  initialize; incrementCapHit(QueryResults) on BOTH capOrReject branches - the
  explicit-LIMIT 402 reject AND the P0.5 no-LIMIT truncation bite (OQ-2).
  Suppressed JOIN legs do not count (no double-count). Reject behavior unchanged
  (AC8 - increment is a side-effect before the existing return).
- Tests: TelemetryCollectorSpec (+10), CoreDqlExtensionSpec (+5). Cross 2.12+2.13
  green incl. testkit; headerCheck clean.

OQ-1 = semantic incrementCapHit per reject site (NOT 'count 402s' - joins/clusters
never raise a 402). Cross-repo P0.6 epic leg 1 of 4 (Phase 0 final story).

Closed Issue #129

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@fupelaqu fupelaqu marked this pull request as ready for review June 23, 2026 18:57
@fupelaqu fupelaqu merged commit f186fdb into release-r1 Jun 23, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant