Skip to content

fix(glam): Compute power of 2 buckets the same way Glean does - #9749

Merged
edugfilho merged 2 commits into
mainfrom
glam-round-buckets-like-glean
Jul 31, 2026
Merged

fix(glam): Compute power of 2 buckets the same way Glean does#9749
edugfilho merged 2 commits into
mainfrom
glam-round-buckets-like-glean

Conversation

@edugfilho

Copy link
Copy Markdown
Contributor

Description

fixes mozilla/glam#3537

Fixes the BQ implementation of functional bucket calculation to match the Glean one for bucket labels that are power of 2.

Related Tickets & Documents

Reviewer, please follow this checklist

@github-actions github-actions Bot left a comment

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.

This changes glam.histogram_generate_functional_buckets to compute bucket minimums as POW(POW(log_base, 1 / buckets_per_magnitude), idx) instead of POW(log_base, idx / buckets_per_magnitude), so the floating-point rounding matches Glean's functional.rs, which precomputes the per-magnitude exponent once and raises it to the bucket index. A new assertion pins the (2, 16) power-of-two bucket minimums to their truncated values. The change is confined to one mozfun UDF, and the two existing assertions are left intact, which is good evidence that non-power-of-two buckets are unaffected.

Two comments inline, both about clarity rather than the math. One thing worth calling out for the merge plan rather than the diff: the only in-repo consumers are bigquery_etl/glam/templates/probe_counts_v1.sql and the two glam_etl__histogram_probe_counts_v1 queries, so partitions already built with the old bucket set will keep the old boundaries until those tables are rebuilt — worth deciding explicitly whether that needs a backfill or whether GLAM's full recompute covers it.

Comment on lines +20 to +25
FLOOR(POW(POW(log_base, 1 / buckets_per_magnitude), idx)) AS bucket
FROM
bucket_indexes,
UNNEST(indexes) AS idx
WHERE
FLOOR(POW(log_base, (idx) / buckets_per_magnitude)) <= range_max
FLOOR(POW(POW(log_base, 1 / buckets_per_magnitude), idx)) <= range_max

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.

suggestion: Hoist the base into bucket_indexes instead of repeating the nested POW in both the SELECT and the WHERE. The two copies now have to be kept byte-identical by hand — if a future edit changes one and not the other, the filter and the emitted value silently disagree. Naming it also makes the intent self-documenting, since it mirrors Glean's exponent = log_base.powf(1.0 / buckets_per_magnitude) that is computed once and then raised to the index:

    WITH bucket_indexes AS (
      -- Generate all bucket indexes
      -- https://github.com/mozilla/glean/blob/main/glean-core/src/histogram/functional.rs
      SELECT
        -- Glean precomputes this exponent once and raises it to the bucket index; the
        -- rounding of this intermediate value is what makes the bucket minimums match.
        POW(log_base, 1 / buckets_per_magnitude) AS exponent,
        GENERATE_ARRAY(0, CEIL(LOG(range_max + 1, log_base) * buckets_per_magnitude)) AS indexes
    ),
    buckets AS (
      SELECT
        FLOOR(POW(exponent, idx)) AS bucket
      FROM
        bucket_indexes,
        UNNEST(indexes) AS idx
      WHERE
        FLOOR(POW(exponent, idx)) <= range_max
    )

exponent is the same FLOAT64 value whether it is inlined or read from the CTE, so this is bit-for-bit equivalent.

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 +66 to +68
-- Bucket minimums at powers of two must match Glean, which truncates them to
-- 2^n - 1. Emitting 2^n instead makes downstream bucket fills drop every
-- sample that lands on a power-of-two bucket minimum.

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.

suggestion: This comment overstates the rule and contradicts the assertion 25 lines above it. The (2, 8, 305) case at line 41 still expects 16, 32, 64, 128, 256 — exact powers of two, not 2^n - 1 — and this PR does not change that expectation. Truncation is not a property of powers of two in general; it happens only for the specific (log_base, buckets_per_magnitude, index) combinations where POW(POW(log_base, 1 / buckets_per_magnitude), idx) lands just under the integer. Scoping the comment to the case actually being asserted would avoid sending a future reader looking for a bug in the (2, 8) expectations:

  -- For log_base=2, buckets_per_magnitude=16, several bucket minimums at powers of
  -- two evaluate to just under 2^n and Glean truncates them to 2^n - 1. Emitting 2^n
  -- instead makes downstream bucket fills drop every sample that lands on one of
  -- these bucket minimums.

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

@scholtzan

This comment has been minimized.

@scholtzan scholtzan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The Claude suggestions seem worth looking into

@edugfilho
edugfilho enabled auto-merge July 31, 2026 20:21
@scholtzan

Copy link
Copy Markdown
Collaborator

Integration report

@edugfilho
edugfilho added this pull request to the merge queue Jul 31, 2026
Merged via the queue into main with commit 53c8aac Jul 31, 2026
30 checks passed
@edugfilho
edugfilho deleted the glam-round-buckets-like-glean branch July 31, 2026 20:49
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.

GLAM vs Glean bucket calculation diverge on powers of 2 due to a rounding difference

2 participants