Skip to content

feat: time-series download analytics - #2134

Open
netomi wants to merge 1 commit into
split/timescale-datasourcefrom
split/download-analytics
Open

feat: time-series download analytics#2134
netomi wants to merge 1 commit into
split/timescale-datasourcefrom
split/download-analytics

Conversation

@netomi

@netomi netomi commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Stack: 5 of 6. Split out of #2027. Base: #2133 (split/timescale-datasource).

Makes the analytics live: the Timescale repository behind the seam the ingestion rework left inert, a query service, and a public endpoint.

  • TimescaleDownloadAnalyticsRepository implements DownloadAnalyticsRepository against the time-series DSLContext, batching writes and reading the download_stats_daily aggregate
  • the interface regains findSeries, held back from refactor: rework the download ingestion pipeline #2132 because it drags in the query model (DownloadSeriesRequest, DownloadSeriesRow and their enums)
  • DownloadAnalyticsService applies a settling margin (ovsx.analytics.settling-margin, 2h) so a series never ends on a bucket the continuous aggregate has not caught up to
  • DownloadAnalyticsAPI exposes the series, publicly cacheable
  • /api/version reports analyticsEnabled, so a client can tell whether the endpoints exist before calling them

All gated on ovsx.analytics.enabled. With it off no bean is created and captureDownload's ObjectProvider stays unsatisfied, exactly as in the base.

The thing to decide in this PR

This is where the request-path capture stops being inert, and it deserves a decision rather than a nod.

StorageUtilService.increaseDownloadCount is @Transactional and called synchronously on the download path (LocalRegistryService:303, before the file response is returned). captureDownload sits inside it.

The transactional half is already handled, and deliberately: b84a70cd3 documents and enforces that writes run on the time-series pool and cannot join a caller's registry transaction — one save() is one transaction of its own. So an analytics write is never rolled back with the registry's work, and never extends its atomicity.

What remains is latency:

  • it is a synchronous JDBC round-trip on the download request thread, so every download not covered by a log source pays a Timescale insert before its file response
  • the call sits inside @Transactional, so it holds the registry's own JPA connection for that duration
  • the 2-second timeout from feat: give download analytics its own time-series database #2133 bounds pool acquisition, not statement execution — a time-series database that is slow rather than unreachable adds its latency directly, with no ceiling

This is the registry's hottest endpoint. The failure is handled (exceptions are swallowed so a download never fails for analytics), but slowness is not the same as failure. Whether that is acceptable, or whether the capture should be handed to an executor or dropped in favour of log-based ingestion alone, is a judgement for the maintainers — I have deliberately not changed the design.

It is also the one part of #2027 that is genuinely hard to see from the outside, which is most of why the stack put it here rather than in the ingestion PR.

Verification

Full server suite: 1216 tests passing (1179 from the base plus 37 here).

Refs #2027, #2025

Makes the analytics live: the Timescale repository behind the seam the ingestion
rework left inert, a query service, and a public endpoint for a download series.

TimescaleDownloadAnalyticsRepository implements DownloadAnalyticsRepository
against the time-series DSLContext, batching writes and reading the
download_stats_daily aggregate. The interface regains findSeries, which was held
back from the ingestion rework because it drags in the query model.

DownloadAnalyticsService applies a settling margin (ovsx.analytics.settling-margin,
2 hours) so a series never ends on a bucket the continuous aggregate has not
caught up to yet. DownloadAnalyticsAPI exposes the series, publicly cacheable.

/api/version reports analyticsEnabled, so a client can tell whether the endpoints
are there before calling them.

Everything is gated on ovsx.analytics.enabled. With it off, no bean is created
and captureDownload's ObjectProvider stays unsatisfied, exactly as before.

Co-Authored-By: gnugomez <gomezbanaco@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@netomi
netomi force-pushed the split/download-analytics branch from 4762381 to 7629c23 Compare September 3, 2026 14:46
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