Skip to content

Select blob source by priority and weighted random#3458

Merged
tonidero merged 1 commit into
mainfrom
toniricodiez/select-blob-source-by-priority-and-weight
May 13, 2026
Merged

Select blob source by priority and weighted random#3458
tonidero merged 1 commit into
mainfrom
toniricodiez/select-blob-source-by-priority-and-weight

Conversation

@tonidero
Copy link
Copy Markdown
Contributor

@tonidero tonidero commented May 8, 2026

Motivation

RemoteConfigManager.refresh was hardcoded to pick blobSources.firstOrNull() with a WIP: comment. The backend already returns priority and weight on each source, so this PR wires up the real selection logic.

Description

  • Highest priority value wins.
  • Among sources tied at the top priority, pick one with weighted random using weight:
    total = sum(weights)
    r = random(0, total)
    cumulative = 0
    for source in candidates:
        cumulative += source.weight
        if r < cumulative: return source
    
  • Fallback to uniform random when all top-priority weights are zero (or any of them are negative, defensively).
  • Selection extracted into a WeightedSource interface + List<T>.selectWeighted(Random) extension so the same algorithm can later be reused for ApiSource (which has the same priority/weight shape).
  • RemoteConfigManager now takes an injectable Random (defaulted to Random.Default) for deterministic unit tests.

Test plan

  • New WeightedSourceSelectionTest covers: empty list, single source with zero weight, priority dominance, weighted-pick boundary cases (r=0, r=29, r=30, r=99 for weights [30, 70]), zero-weight uniform fallback, negative-weight uniform fallback.
  • RemoteConfigManagerTest updated: existing multi-source test relaxed to assert delegation; new test asserts the highest-priority source is selected through the manager.
  • ./gradlew :purchases:testDefaultsBc8DebugUnitTest --tests "com.revenuecat.purchases.common.remoteconfig.*" passes.
  • ./gradlew detektAll passes.

Note

Medium Risk
Changes remote-config blob source selection from deterministic first-item to priority/weight-based randomness, which can affect which CDN/source is used in production. Risk is moderate but bounded to remote config fetching and covered by new unit tests and injectable RNG for determinism.

Overview
Remote config blob fetching now selects a BlobSource by highest priority, and breaks ties with weighted random using weight (with a uniform-random fallback for zero/negative weights) rather than always using blobSources.firstOrNull().

This extracts the algorithm into a reusable WeightedSource + selectWeighted(...) helper, updates ApiSource/BlobSource to implement it, injects Random into RemoteConfigManager for deterministic tests, and expands unit coverage for priority/weight edge cases.

Reviewed by Cursor Bugbot for commit 98ba254. Bugbot is set up for automated code reviews on this repo. Configure here.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 8, 2026

Codecov Report

❌ Patch coverage is 83.33333% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.53%. Comparing base (03c6f6f) to head (c125314).

Files with missing lines Patch % Lines
...at/purchases/common/remoteconfig/WeightedSource.kt 75.00% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@                                   Coverage Diff                                    @@
##           toniricodiez/use-topic-file-for-offline-entitlements    #3458      +/-   ##
========================================================================================
- Coverage                                                 79.53%   79.53%   -0.01%     
========================================================================================
  Files                                                       369      370       +1     
  Lines                                                     14885    14898      +13     
  Branches                                                   2042     2048       +6     
========================================================================================
+ Hits                                                      11839    11849      +10     
- Misses                                                     2224     2226       +2     
- Partials                                                    822      823       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tonidero tonidero force-pushed the toniricodiez/select-blob-source-by-priority-and-weight branch from 4f3da1a to a820471 Compare May 11, 2026 08:40
@tonidero tonidero force-pushed the toniricodiez/use-topic-file-for-offline-entitlements branch from 475500e to 5d17b34 Compare May 11, 2026 08:40
@tonidero tonidero force-pushed the toniricodiez/select-blob-source-by-priority-and-weight branch from a820471 to c125314 Compare May 11, 2026 11:37
@tonidero tonidero force-pushed the toniricodiez/use-topic-file-for-offline-entitlements branch 2 times, most recently from 03c6f6f to d128c04 Compare May 11, 2026 13:16
@tonidero tonidero force-pushed the toniricodiez/select-blob-source-by-priority-and-weight branch from c125314 to 5b5596f Compare May 11, 2026 13:16
@tonidero tonidero changed the base branch from toniricodiez/use-topic-file-for-offline-entitlements to graphite-base/3458 May 11, 2026 13:22
@tonidero tonidero force-pushed the toniricodiez/select-blob-source-by-priority-and-weight branch from 5b5596f to 78ea29f Compare May 11, 2026 13:23
@tonidero tonidero force-pushed the graphite-base/3458 branch from d128c04 to 6d89f70 Compare May 11, 2026 13:23
@tonidero tonidero changed the base branch from graphite-base/3458 to toniricodiez/add-remote-config-topic-cleanup May 11, 2026 13:23
@tonidero tonidero force-pushed the toniricodiez/select-blob-source-by-priority-and-weight branch from 78ea29f to 48367fa Compare May 11, 2026 13:30
@tonidero tonidero force-pushed the toniricodiez/add-remote-config-topic-cleanup branch from 6d89f70 to 32ba5ab Compare May 11, 2026 13:30
@tonidero tonidero marked this pull request as ready for review May 11, 2026 13:36
@tonidero tonidero requested a review from a team as a code owner May 11, 2026 13:36
@tonidero tonidero force-pushed the toniricodiez/add-remote-config-topic-cleanup branch from 32ba5ab to 85b8714 Compare May 11, 2026 13:52
@tonidero tonidero force-pushed the toniricodiez/select-blob-source-by-priority-and-weight branch 2 times, most recently from 4e64b90 to ae9921c Compare May 11, 2026 14:05
@tonidero tonidero force-pushed the toniricodiez/add-remote-config-topic-cleanup branch 2 times, most recently from 3985e09 to 74eedab Compare May 12, 2026 09:47
@tonidero tonidero force-pushed the toniricodiez/select-blob-source-by-priority-and-weight branch from ae9921c to 6eda732 Compare May 12, 2026 09:47
@tonidero tonidero force-pushed the toniricodiez/add-remote-config-topic-cleanup branch from 74eedab to e1dac4b Compare May 12, 2026 11:56
@tonidero tonidero force-pushed the toniricodiez/select-blob-source-by-priority-and-weight branch 2 times, most recently from 6eda732 to 4cd0810 Compare May 12, 2026 11:56
@tonidero tonidero force-pushed the toniricodiez/add-remote-config-topic-cleanup branch from 74eedab to e1dac4b Compare May 12, 2026 11:56
@tonidero tonidero force-pushed the toniricodiez/select-blob-source-by-priority-and-weight branch from 4cd0810 to a29029f Compare May 12, 2026 12:52
@tonidero tonidero changed the base branch from toniricodiez/add-remote-config-topic-cleanup to graphite-base/3458 May 12, 2026 14:06
@tonidero tonidero force-pushed the graphite-base/3458 branch from 9503c36 to e9b0f74 Compare May 13, 2026 08:49
@tonidero tonidero force-pushed the toniricodiez/select-blob-source-by-priority-and-weight branch from a29029f to 2172324 Compare May 13, 2026 08:49
@tonidero tonidero changed the base branch from graphite-base/3458 to main May 13, 2026 08:49
Copy link
Copy Markdown
Member

@ajpallares ajpallares left a comment

Choose a reason for hiding this comment

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

Some initial comments on the weight-based randomization logic. Will keep looking 👀

@tonidero tonidero force-pushed the toniricodiez/select-blob-source-by-priority-and-weight branch from 337c191 to ff1124a Compare May 13, 2026 11:09
@tonidero tonidero requested a review from ajpallares May 13, 2026 11:11
Copy link
Copy Markdown
Member

@ajpallares ajpallares 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 it looks good! I have some doubts though, mostly about whether the lower-priority blob sources not being picked at all for now.
Great job btw!

Comment on lines +23 to +25
override val priority: Int,
override val weight: Int,
) : WeightedSource
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.

NABD, but I believe in this PR we're not actually using the conformance of ApiSource to WeightedSource

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.

Correct! The idea is that we can reuse the same logic to select the ApiSource, but that's not implemented yet (will be in future PRs)

}
// WIP: We should have some logic to pick the correct source for this. Right now, hardcoded to the first source.
val source = response.blobSources.firstOrNull()
val source = response.blobSources.selectWeighted(random)
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'm guessing this will be tackled in follow-up PRs, but, with the current implementation of this PR, we only ever use the highest priority blobSources, right? lower-priority sources aren't fallback candidates.

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.

That's indeed correct! That's tackled in this follow-up PR: #3466

val totalWeight = candidates.sumOf { it.weight }
val anyNegativeWeights = candidates.any { it.weight < 0 }
return if (totalWeight <= 0 || anyNegativeWeights) {
candidates[random.nextInt(candidates.size)]
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.

Cursor highlighted some concerns about thread safety in random. Seems like the default Random.Default is thread safe, so this would only really affect tests I guess. Just wanted to highlight here as well

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.

Hmm can you elaborate? I think for tests as well, we create new instances on each test. It's true we don't test with the "actual" Random.Default, but this is to make the tests deterministic.

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.

You're right, yes. With the uses we have for it now, it's not a problem. Please ignore me

@tonidero tonidero force-pushed the toniricodiez/select-blob-source-by-priority-and-weight branch from ff1124a to 9d28ec1 Compare May 13, 2026 13:23
Copy link
Copy Markdown
Member

@ajpallares ajpallares 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 looks great!

@tonidero tonidero enabled auto-merge May 13, 2026 14:11
@tonidero tonidero disabled auto-merge May 13, 2026 14:11
@tonidero tonidero force-pushed the toniricodiez/select-blob-source-by-priority-and-weight branch from 9d28ec1 to 98ba254 Compare May 13, 2026 15:19
@tonidero tonidero enabled auto-merge May 13, 2026 15:20
@tonidero tonidero added this pull request to the merge queue May 13, 2026
Merged via the queue into main with commit 1631b1f May 13, 2026
35 checks passed
@tonidero tonidero deleted the toniricodiez/select-blob-source-by-priority-and-weight branch May 13, 2026 16:03
github-merge-queue Bot pushed a commit that referenced this pull request May 13, 2026
**This is an automatic release.**

## RevenueCat SDK
### 📦 Dependency Updates
* [RENOVATE] Update dependency gradle to v8.14.5 (#3459) via RevenueCat
Git Bot (@RCGitBot)

## RevenueCatUI SDK
### ✨ New Features
* Pre-warm image cache for workflow step states (#3447) via Cesar de la
Vega (@vegaro)
### Paywallv2
#### ✨ New Features
* Add `close_workflow` button action (#3453) via Cesar de la Vega
(@vegaro)
#### 🐞 Bugfixes
* Fix preload VideoComponent fallback override images (#3449) via Cesar
de la Vega (@vegaro)

### 🔄 Other Changes
* Select blob source by priority and weighted random (#3458) via Toni
Rico (@tonidero)
* [AUTOMATIC] Update golden test files for backend integration tests
(#3473) via RevenueCat Git Bot (@RCGitBot)
* Clean up unreferenced topic files after successful remote-config
refresh (#3439) via Toni Rico (@tonidero)
* Cache remote config response in memory with TTL and persist to disk
(#3457) via Toni Rico (@tonidero)
* build(deps): bump fastlane from 2.233.1 to 2.234.0 (#3463) via
dependabot[bot] (@dependabot[bot])
* Update codelabs links (#3460) via Jaewoong Eum (@skydoves)
* Add RemoteConfigManager and TopicFetcher (#3437) via Toni Rico
(@tonidero)
* Add exit offers support to workflows (#3452) via Cesar de la Vega
(@vegaro)
* Update baseline profiles (#3461) via RevenueCat Git Bot (@RCGitBot)
* Add network scaffolding for remote config endpoint (#3435) via Toni
Rico (@tonidero)
* test: cover singleStepFallbackId == initialStepId edge case (#3445)
via Facundo Menzella (@facumenzella)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: this is a release/versioning update (SNAPSHOT -> final) plus
docs deployment path changes, with no functional code changes beyond
version constants.
> 
> **Overview**
> Finalizes the `10.6.0` release by switching all version references
from `10.6.0-SNAPSHOT` to `10.6.0` (root `.version`,
`gradle.properties`, `Config.frameworkVersion`, and sample/test app
`libs.versions.toml` files).
> 
> Updates documentation publishing to point at the `10.6.0` docs path
(CircleCI S3 sync target and `docs/index.html` redirect), and prepends
the `10.6.0` section to `CHANGELOG.md`/`CHANGELOG.latest.md`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
4da1697. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants