Skip to content

Introduce minimal dependency analytics interface#72262

Merged
metamben merged 19 commits into
masterfrom
que2-507-analytics-interface
Apr 24, 2026
Merged

Introduce minimal dependency analytics interface#72262
metamben merged 19 commits into
masterfrom
que2-507-analytics-interface

Conversation

@metamben
Copy link
Copy Markdown
Contributor

@metamben metamben commented Apr 9, 2026

Closes QUE2-507

Description

This PR introduces an interface namespace for emitting internal analytics events, aiming for

  • providing a common interface for FE and BE event emission, and
  • allowing metabase lib to use the analytics interface without introducing cyclic dependencies.

The event emission parts that are provided by the new interface are migrated from analytics.core and prometheus to the new interface namespace.

How to verify

The tests should pass.

Checklist

  • Tests have been added/updated to cover changes in this PR
  • If adding new Loki tests: they pass stress testing

@metamben metamben requested a review from a team as a code owner April 9, 2026 16:00
@metamben metamben added the no-backport Do not backport this PR to any branch label Apr 9, 2026
@metabase-bot metabase-bot Bot added the .Team/QueryingPlatform Platform subteam for .Team/Querying. Please use in conjunction with .Team/Querying label Apr 9, 2026
@metamben metamben force-pushed the que2-507-analytics-interface branch from e4b95dd to 1b52135 Compare April 9, 2026 16:07
@metamben metamben force-pushed the que2-507-analytics-interface branch from 1b52135 to a75027d Compare April 9, 2026 16:25
@metamben metamben force-pushed the que2-507-analytics-interface branch from e11e13c to b63aa21 Compare April 9, 2026 18:47
:refer [gsheets gsheets!]]
[metabase-enterprise.harbormaster.client :as hm.client]
[metabase.analytics.core :as analytics]
[metabase.analytics.interface :as analytics]
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.

is this too unorthodox?

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.

Not to bikeshed, but yeah my expectation would be that the "public interface" namespace would be the top-level metabase.analytics.

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.

the issue here is that public API is usually metabase.<modul-name>.core and is required as <modul-name>. there is no convention how to deal with a separate interface namespace different from the core. I hijacked the standard analytics alias for the interface namespace, because in most cases that's what users should import.

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.

I think in this case we'd probably normally actually want to have a separate interface module, e.g. analytics-interface with an API namespace of metabase.analytics-interface.core

(let [table-size (row-count pgvector gate-table-name)]
(log/debugf "Setting `semantic-gate-size` metric to %d" table-size)
(analytics/set! :metabase-search/semantic-gate-size table-size)
(analytics/set-gauge! :metabase-search/semantic-gate-size table-size)
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.

I didn't feel like fighting the set! special form, especially with CLJS added to the picture.

Comment on lines +7 to +8
[metabase.analytics.core :as analytics.core]
[metabase.analytics.interface :as analytics]
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.

here we have both namespaces.

@@ -0,0 +1,19 @@
(ns metabase.analytics.impl
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.

too generic?

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.

I think impl is fine, but you could also call it metabase.analytics.reporter.

Comment thread src/metabase/analytics/impl.cljs Outdated
Comment thread src/metabase/analytics/init.clj Outdated
@@ -1,4 +1,5 @@
(ns metabase.analytics.init
(:require
[metabase.analytics.impl]
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.

to register the reporter.

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.

It would be better to add a comment in the code to this effect 🙃

@metamben metamben requested a review from a team as a code owner April 9, 2026 23:36
Comment thread src/metabase/analytics/api.clj
Copy link
Copy Markdown
Contributor

@technomancy technomancy left a comment

Choose a reason for hiding this comment

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

Can't really comment on the frontend stuff, but the backend side makes sense.

Copy link
Copy Markdown
Member

@camsaul camsaul left a comment

Choose a reason for hiding this comment

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

I think this all looks good but it would be my preference to split out analytics-interface into its own zero-dependency module and then have a separate analytics or analytics-impl module with the Prometheus/Snowplow/whatever implementations in it

@metamben metamben enabled auto-merge (squash) April 24, 2026 15:36
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 24, 2026

e2e tests failed on c780483e85de6aa5504d99b6fb2f28a5c448ac98-1

e2e test run

File Test Name
model-actions.cy.spec.js Write actions on model detail page (postgres) > should respect impersonated permission
source-replacement.cy.spec.ts scenarios > data-studio > source replacement > Native queries > replaces a table referenced in a native SQL question
dependency-unreferenced-list.cy.spec.ts scenarios > dependencies > unreferenced list > analysis > should show unreferenced entities
dependency-unreferenced-list.cy.spec.ts scenarios > dependencies > unreferenced list > filters > should filter entities by type
dependency-unreferenced-list.cy.spec.ts scenarios > dependencies > unreferenced list > filters > should persist filter changes after page reload
dependency-unreferenced-list.cy.spec.ts scenarios > dependencies > unreferenced list > filters > should filter by location
dependency-unreferenced-list.cy.spec.ts scenarios > dependencies > unreferenced list > selecting entities > should show the sidebar for supported entities and trigger snowplow event

Copy link
Copy Markdown
Contributor

@Onlinehead Onlinehead left a comment

Choose a reason for hiding this comment

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

Metrics LGTM

@metamben metamben merged commit c0927e9 into master Apr 24, 2026
229 checks passed
@metamben metamben deleted the que2-507-analytics-interface branch April 24, 2026 18:45
@github-actions github-actions Bot added this to the 0.61 milestone Apr 30, 2026
dpsutton added a commit that referenced this pull request May 1, 2026
* Lighter prometheus analytics

There's a couple things about prometheus that have gotten a bit
unwieldy. This pain has recently been seen in Tamas's change that broke
startup in a very "action at a distance" way.

#72262 is _excellent_ work. But
prometheus starts up so late that it is hazardous. Principally it
requires app db support (for the setting system) and it loads
essentially last after app db is initialized, token checks, etc. And
this is the bad part. Many systems can log analytic events before the
system is initialized. There are calls that when incrementing a a
metric, if it's not already initialized, to initialize it. But in the
course of initializing, it would want to increment a metric, causing an
infinite loop.

This was exacerbated in that the safety valves of the token checker
kicked in and suppressed errors for 30 seconds, ending up with threads
locked up trying to initialize the collector.

Solution:
just initialize it. It's just a few atomic longs and maybe a small
webserver. One complication is from chris's work in
#52834 where they try to seed
initial values so that when something registers a number it is a jump
from 0 to 8 and therefore alarming, rather than just 8 with no
differential.

So i added an edge that these can be set later on in the process. One
thing to call out is that the initial value is mostly 0, but the search
ones use a value of 1 as a bit field to indicate it's true:

```
prometheus=> (initial-labelled-metric-values)
[
,,,
 {:metric :metabase-search/engine-default,
  :labels {:engine "appdb"},
  :value 1} ;; -> indicates appdb is the default engine
 {:metric :metabase-search/engine-default,
  :labels {:engine "semantic"},
  :value 0}
  ,,,
 {:metric :metabase-search/engine-active,
  :labels {:engine "in-place"},
  :value 1}
 {:metric :metabase-search/engine-active,
  :labels {:engine "appdb"},
  :value 1}
 {:metric :metabase-search/engine-active,
  :labels {:engine "semantic"},
  :value 0}
```

So when we call the initial observed values it's just setting an
affirmative 0 on the value, or an affirmative 1. we won't clobber the
number of emails sent or anything

* initialize prometheus in test runner

* remove tests that care about reentrant and auto start

* default no-op reporter

* remove setting up dynamic var. we don't setup for you any more
github-automation-metabase added a commit that referenced this pull request May 1, 2026
Lighter prometheus analytics (#73456)

* Lighter prometheus analytics

There's a couple things about prometheus that have gotten a bit
unwieldy. This pain has recently been seen in Tamas's change that broke
startup in a very "action at a distance" way.

#72262 is _excellent_ work. But
prometheus starts up so late that it is hazardous. Principally it
requires app db support (for the setting system) and it loads
essentially last after app db is initialized, token checks, etc. And
this is the bad part. Many systems can log analytic events before the
system is initialized. There are calls that when incrementing a a
metric, if it's not already initialized, to initialize it. But in the
course of initializing, it would want to increment a metric, causing an
infinite loop.

This was exacerbated in that the safety valves of the token checker
kicked in and suppressed errors for 30 seconds, ending up with threads
locked up trying to initialize the collector.

Solution:
just initialize it. It's just a few atomic longs and maybe a small
webserver. One complication is from chris's work in
#52834 where they try to seed
initial values so that when something registers a number it is a jump
from 0 to 8 and therefore alarming, rather than just 8 with no
differential.

So i added an edge that these can be set later on in the process. One
thing to call out is that the initial value is mostly 0, but the search
ones use a value of 1 as a bit field to indicate it's true:

```
prometheus=> (initial-labelled-metric-values)
[
,,,
 {:metric :metabase-search/engine-default,
  :labels {:engine "appdb"},
  :value 1} ;; -> indicates appdb is the default engine
 {:metric :metabase-search/engine-default,
  :labels {:engine "semantic"},
  :value 0}
  ,,,
 {:metric :metabase-search/engine-active,
  :labels {:engine "in-place"},
  :value 1}
 {:metric :metabase-search/engine-active,
  :labels {:engine "appdb"},
  :value 1}
 {:metric :metabase-search/engine-active,
  :labels {:engine "semantic"},
  :value 0}
```

So when we call the initial observed values it's just setting an
affirmative 0 on the value, or an affirmative 1. we won't clobber the
number of emails sent or anything

* initialize prometheus in test runner

* remove tests that care about reentrant and auto start

* default no-op reporter

* remove setting up dynamic var. we don't setup for you any more

Co-authored-by: dpsutton <dan@dpsutton.com>
nvoxland added a commit that referenced this pull request May 12, 2026
Master moved Prometheus helper fns (inc!, set!/set-gauge!, observe!)
out of metabase.analytics.core and into metabase.analytics-interface.core
(see #72262), and the rename broke the namespace-decl check on this
branch. Point all metabase.mq.* files (and their tests) at the new
namespace, rename set! → set-gauge!, and add analytics-interface to
the mq module's :uses set.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-backport Do not backport this PR to any branch .Team/QueryingPlatform Platform subteam for .Team/Querying. Please use in conjunction with .Team/Querying

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants