Skip to content

Commit 1b0b9d0

Browse files
vahidlazioclaude
andauthored
feat(cloudflare): add opt-in apply-event deduplication (#542)
Co-authored-by: vahidlazio <vahidlazio@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e66e265 commit 1b0b9d0

3 files changed

Lines changed: 52 additions & 9 deletions

File tree

confidence-cloudflare-resolver/deployer/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ The deployer automatically:
6868
| `ENABLE_STICKY_ASSIGNMENTS` | Set to create a KV namespace and enable sticky assignments for experiments. Requires a [KV store](https://developers.cloudflare.com/kv/platform/pricing/) |
6969
| `MATERIALIZATION_TTL_SECONDS` | TTL in seconds for sticky assignment KV entries. Omit for no expiration |
7070
| `FORCE_APPLY` | Defaults to `true`: every resolve is treated as `apply=true` and assignments are logged at resolve time. Set to `false` to respect the `apply` value sent by SDKs (deferred-apply flow via `flags:apply`) |
71+
| `ENABLE_APPLY_DEDUP` | Defaults to `false`: set to `true` to enable apply-event deduplication — repeated identical assignments within a 120s window are logged once |
7172

7273
### Extending Wrangler Configuration
7374

confidence-cloudflare-resolver/deployer/script.sh

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ WRANGLER_DEPLOY_TAG=${WRANGLER_DEPLOY_TAG:=}
2222
WRANGLER_DEPLOY_MESSAGE=${WRANGLER_DEPLOY_MESSAGE:=}
2323
ENABLE_STICKY_ASSIGNMENTS=${ENABLE_STICKY_ASSIGNMENTS:=}
2424
FORCE_APPLY=${FORCE_APPLY:=}
25+
ENABLE_APPLY_DEDUP=${ENABLE_APPLY_DEDUP:=}
2526
INITIAL_WORKDIR="$(pwd)"
2627

2728
# CDN base URL for fetching resolver state
@@ -550,16 +551,26 @@ if [ -n "$FORCE_APPLY" ]; then
550551
fi
551552
fi
552553

554+
# Validate ENABLE_APPLY_DEDUP if provided (worker defaults to false when unset)
555+
if [ -n "$ENABLE_APPLY_DEDUP" ]; then
556+
ENABLE_APPLY_DEDUP=$(printf '%s' "$ENABLE_APPLY_DEDUP" | tr '[:upper:]' '[:lower:]')
557+
if [ "$ENABLE_APPLY_DEDUP" != "true" ] && [ "$ENABLE_APPLY_DEDUP" != "false" ]; then
558+
echo "❌ ENABLE_APPLY_DEDUP must be \"true\" or \"false\", got: $ENABLE_APPLY_DEDUP" >&2
559+
exit 1
560+
fi
561+
fi
562+
553563
# Update [vars] table with ALLOWED_ORIGIN, RESOLVER_STATE_ETAG and RESOLVER_VERSION, without duplicating the table
554-
if [ -n "$ALLOWED_ORIGIN_TOML" ] || [ -n "$ETAG_TOML" ] || [ -n "$DEPLOYER_VERSION" ] || [ -n "$CLIENT_SECRET_TOML" ] || [ -n "$FORCE_APPLY" ]; then
564+
if [ -n "$ALLOWED_ORIGIN_TOML" ] || [ -n "$ETAG_TOML" ] || [ -n "$DEPLOYER_VERSION" ] || [ -n "$CLIENT_SECRET_TOML" ] || [ -n "$FORCE_APPLY" ] || [ -n "$ENABLE_APPLY_DEDUP" ]; then
555565
# Remove any existing definitions to avoid duplicates
556566
sed -i.tmp '/^ALLOWED_ORIGIN *= *.*$/d' wrangler.toml || true
557567
sed -i.tmp '/^RESOLVER_STATE_ETAG *= *.*$/d' wrangler.toml || true
558568
sed -i.tmp '/^RESOLVER_VERSION *= *.*$/d' wrangler.toml || true
559569
sed -i.tmp '/^DEPLOYER_VERSION *= *.*$/d' wrangler.toml || true
560570
sed -i.tmp '/^CONFIDENCE_CLIENT_SECRET *= *.*$/d' wrangler.toml || true
561571
sed -i.tmp '/^FORCE_APPLY *= *.*$/d' wrangler.toml || true
562-
awk -v allowed="${ALLOWED_ORIGIN_TOML}" -v etag="${ETAG_TOML}" -v version="${DEPLOYER_VERSION}" -v client_secret="${CLIENT_SECRET_TOML}" -v force_apply="${FORCE_APPLY}" '
572+
sed -i.tmp '/^ENABLE_APPLY_DEDUP *= *.*$/d' wrangler.toml || true
573+
awk -v allowed="${ALLOWED_ORIGIN_TOML}" -v etag="${ETAG_TOML}" -v version="${DEPLOYER_VERSION}" -v client_secret="${CLIENT_SECRET_TOML}" -v force_apply="${FORCE_APPLY}" -v enable_apply_dedup="${ENABLE_APPLY_DEDUP}" '
563574
BEGIN{inserted=0}
564575
{
565576
print $0
@@ -569,6 +580,7 @@ if [ -n "$ALLOWED_ORIGIN_TOML" ] || [ -n "$ETAG_TOML" ] || [ -n "$DEPLOYER_VERSI
569580
if (version != "") print "DEPLOYER_VERSION = \"" version "\""
570581
if (client_secret != "") print "CONFIDENCE_CLIENT_SECRET = \"" client_secret "\""
571582
if (force_apply != "") print "FORCE_APPLY = \"" force_apply "\""
583+
if (enable_apply_dedup != "") print "ENABLE_APPLY_DEDUP = \"" enable_apply_dedup "\""
572584
inserted=1
573585
}
574586
}

confidence-cloudflare-resolver/src/lib.rs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod materialization;
22

33
use confidence_resolver::{
4+
apply_dedup::ApplyDedup,
45
assign_logger, flag_logger,
56
proto::{confidence, google::Struct},
67
resolve_logger,
@@ -15,7 +16,7 @@ use bytes::Bytes;
1516
use prost::Message;
1617
use serde_json::from_slice;
1718
use serde_json::json;
18-
use std::cell::RefCell;
19+
use std::cell::{Cell, RefCell};
1920
use wasm_bindgen::JsCast;
2021

2122
use confidence::flags::resolver::v1::{ApplyFlagsRequest, ApplyFlagsResponse, ResolveFlagsRequest};
@@ -48,11 +49,16 @@ thread_local! {
4849
// Side channel for the `Host` logging callbacks, which are static methods
4950
// with no way to reach their caller. Only ever `Some` inside `with_log`.
5051
static FLAG_LOG: RefCell<Option<WriteFlagLogsRequest>> = const { RefCell::new(None) };
52+
static APPLY_DEDUP: RefCell<ApplyDedup> = RefCell::new(ApplyDedup::new(120, 100_000));
53+
static APPLY_DEDUP_ENABLED: Cell<bool> = const { Cell::new(false) };
5154
}
5255

53-
/// Queues one request's flag log. Called via `Context::wait_until`, so it runs
54-
/// after the response has been returned.
56+
/// Queues one request's flag log and sweeps the apply-dedup map. Called via
57+
/// `Context::wait_until`, so both run after the response has been returned.
5558
async fn queue_flag_log(log: WriteFlagLogsRequest) {
59+
if APPLY_DEDUP_ENABLED.with(|c| c.get()) {
60+
APPLY_DEDUP.with(|d| d.borrow_mut().sweep((js_sys::Date::now() / 1000.0) as i64));
61+
}
5662
match serde_json::to_string(&log) {
5763
Ok(json) => {
5864
if let Some(queue) = FLAGS_LOGS_QUEUE.get() {
@@ -164,14 +170,32 @@ impl Host for H {
164170
client: &Client,
165171
sdk: &Option<Sdk>,
166172
) {
173+
if !assigned_flags.is_empty() && APPLY_DEDUP_ENABLED.with(|c| c.get()) {
174+
let now_seconds = (js_sys::Date::now() / 1000.0) as i64;
175+
let result = APPLY_DEDUP.with(|dedup| {
176+
dedup.borrow_mut().filter_duplicates(assigned_flags, now_seconds)
177+
});
178+
if result.is_empty() {
179+
return;
180+
}
181+
if result.kept_count() < assigned_flags.len() {
182+
let filtered = result.collect(assigned_flags);
183+
FLAG_LOG.with(|f| {
184+
if let Some(req) = f.borrow_mut().as_mut() {
185+
req.flag_assigned
186+
.push(assign_logger::build_flag_assigned(
187+
resolve_id, &filtered, client, sdk,
188+
));
189+
}
190+
});
191+
return;
192+
}
193+
}
167194
FLAG_LOG.with(|f| {
168195
if let Some(req) = f.borrow_mut().as_mut() {
169196
req.flag_assigned
170197
.push(assign_logger::build_flag_assigned(
171-
resolve_id,
172-
assigned_flags,
173-
client,
174-
sdk,
198+
resolve_id, assigned_flags, client, sdk,
175199
));
176200
}
177201
});
@@ -290,6 +314,12 @@ pub async fn main(req: Request, env: Env, ctx: Context) -> Result<Response> {
290314
.map(|var| var.to_string())
291315
.unwrap_or_default();
292316

317+
let enable_apply_dedup = env
318+
.var("ENABLE_APPLY_DEDUP")
319+
.map(|var| var.to_string().trim().eq_ignore_ascii_case("true"))
320+
.unwrap_or(false);
321+
APPLY_DEDUP_ENABLED.with(|c| c.set(enable_apply_dedup));
322+
293323
if req.method() == Method::Options {
294324
return Response::ok("")?.with_cors_headers(&allowed_origin_env);
295325
}

0 commit comments

Comments
 (0)