Skip to content

Add Prebid ad-latency optimizations design spec - #1053

Draft
aram356 wants to merge 12 commits into
rc/202608from
spec/prebid-ad-latency-optimizations
Draft

Add Prebid ad-latency optimizations design spec#1053
aram356 wants to merge 12 commits into
rc/202608from
spec/prebid-ad-latency-optimizations

Conversation

@aram356

@aram356 aram356 commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

Design spec for three Trusted-Server-side ad-latency levers on client-side-auction properties (SSAT off), grounded in an instrumented 3-run baseline collected against a local Trusted Server proxying the production autoblog origin (consent pre-seeded, reader-style scrolling).

Baseline findings the spec addresses:

  • The first refresh auction waits for window.load (~3.3 s) although DOMContentLoaded fires at ~2.1 s — only the DOM application needs the React-JS bundle hash recomputed on every HTML response instead of cached #418-safe post-load defer, not the auction fetch
  • Publisher requestBids bursts (calls observed 1 ms apart) each pay a full /auction round trip plus ~865 ms median server auction time
  • The first GAM ad request pays fresh DNS + TCP + TLS to securepubads.g.doubleclick.net

Proposed levers (spec only — no implementation in this PR):

  1. request_bids_coalesce_ms (opt-in, default 0) — coalescing window in the tsjs requestBids wrapper with a strict merge-safety rule
  2. securepubads preconnect hint from the GPT integration head inserts (unconditional)
  3. prefetch_first_refresh_auction (opt-in, default false) — dispatch the first refresh auction fetch at DOMContentLoaded, apply results at the unchanged post-load + double-rAF point

Both flags default off and serialize only at non-default values, following the existing config-blob rollback discipline.

Three Trusted-Server-side levers for client-side auction properties,
grounded in an instrumented local baseline against the autoblog origin:
an opt-in requestBids coalescing window, a securepubads preconnect
hint, and an opt-in first-refresh-auction prefetch at DOMContentLoaded
that keeps the React-hydration-safe DOM application point unchanged.
Lever C: the window.load gate is installSlimPrebidLoader (the auction
module is not loaded before load), so the prefetch lives in the unified
GPT bundle and slim-Prebid consumes the stashed response; the
double-rAF defer belongs to the SSAT adInit path, not this flow.
Lever A: Prebid 10 requestBids returns a promise, so held calls return
a promise settling with the merged auction instead of undefined.
@aram356
aram356 marked this pull request as draft August 21, 2026 04:37
@aram356 aram356 self-assigned this Aug 21, 2026
aram356 added 10 commits August 20, 2026 21:53
Reframe Lever A as a load/QPS lever (measured burst POSTs were already
concurrent) with absolute deadlines, per-caller bid-map partitioning,
bookkeeping-before-callbacks ordering, synthetic-refresh and
client-side-bidder exclusions, disjoint-code and payload-size admission
bounds, and full promise semantics. Gate Lever B behind
[integrations.gpt] gam_preconnect, correct the pubads_impl and
crossorigin claims, and note the pre-consent connection decision.
Demote Lever C to discovery-first: the described lifecycle seam does
not exist, responses cannot be stashed without the requesting auction's
bid-request IDs, consent readiness needs a concrete API contract that
distinguishes consent-denied no-bids, and fallbacks must share the
in-flight promise rather than duplicate auctions. Add a billing and
impression integrity section, correct config-blob compatibility to the
raw-JSON retention layer, and expand testing, measurement methodology,
and one-lever-at-a-time rollout.
Gate Lever A behind a burst-trace prerequisite (the baseline never
recorded the burst calls' option keys or codes; same-code duplicate
auctions would make the disjoint-code rule merge nothing), reframe
bidder-QPS reduction as a hypothesis, document the shared-auction
global-targeting caveat, make the deadline algorithm executable
(bidderTimeout captured at enqueue, 50ms tolerance, monotonic flush at
min(windowEnd, earliestDeadline - 100ms), 50ms dispatch floor so
Prebid's timeout||bidderTimeout fallback never sees 0), add
dispatch-time revalidation against in-place mutation with exact batch
bounds, keep per-caller registration IDs so throw-rollback semantics
survive merging, and generalize the flush-first rule to every
ineligible arrival. Rewrite billing integrity per delivery path (the
PUC bridge is not this scope's boundary; requestAds couples fetch to
render; PBS server-side notices may bill off-render). Name
tsjs.requestAds as Lever C's leading discovery hypothesis and add
cookie-parity consent, server-owned equivalence inputs, and
server-owned cache tokens. Add Lever B governance (net-log proof, GPC
and denied-CMP cases, named approver, insertion-order test). Make
measurement paired and thresholded, note the telemetry arm-label gap
and read-once config rollback semantics, and sanitize property naming.
Resolve the integrity contradiction by consistently accepting
cross-caller delivery within a merged auction (forbidden only for
units no caller requested) and adding callback-discipline capture plus
the pending-state/synthetic-auction net-effect estimate to Phase 0.
Make the queue reentrancy-safe via atomic batch detachment before
dispatch, reframe deadlines as a reduced auction-time budget with a
solo-dispatch threshold instead of an absolute deadline, define
order-preserving contiguous-segment eviction, snapshot request shape
and ad-unit array order at enqueue, define the payload projection with
a reserved adapter allowance and a 413 re-dispatch-solo-once fallback,
and pass through undefined cancelled-auction values with handler
throws reported by async rethrow rather than facade rejection. Scope
Lever B approval to every jurisdiction the deployed config serves, pin
the NetLog protocol, and narrow the invariant to no request
HEADERS/DATA frames. Strengthen Lever C discovery with server-side
single-flight preference, no completed-response reuse (the adapter
re-stamps ttl 300), a typed direct-path result, and input-ownership
tables, and remove the dangling Phase 2 reference. Add the adapter
activation matrix (Fastly-only pilot), the guardrail source table with
a new coalesced-count signal, drain over a 48h document lifetime,
randomized paired methodology with power-based sample sizes, and a
real latency-benefit gate for B.
Replace the unimplementable post-response 413 replay with an
authoritative size bound at the adapter buildRequests seam (constituent
boundaries carried in; over-limit merged bodies split into multiple
transport descriptors within one auction, preserving the single event
stream). Replace the global async rethrow with a synchronous
first-error throw after all handlers so Prebid's existing catch path
handles it. Define the telemetry wire path as a top-level
constituentCallCount body field through endpoint, observation context,
event row, and a schema-first Tinybird column. Bound drain with a 24h
injected-config lifetime and shim self-disable. Add the Fastly
single-flight discovery exit criterion, extend the navigation-scoped
reservation to both architectures, and specify the pending-only state
machine with settled-result discard and cancellation honesty. Require
a traffic-weighted browser matrix and durable approval artifact for
Lever B with HTTP/1 write predicates and a versioned parser. Replace
the circular eligible-burst denominator with candidate-burst coverage
plus a 95 percent merge-success health gate, add a 30ms minimum
worthwhile effect with named paired estimators, correct the Spin
activation row, use a side-effect-free size estimator, add the
segment-failure continuation test, and soften byte-for-byte wording.
Narrow the no-oversized-request guarantee to multi-constituent
descriptors with an explicit singleton rule, key constituent
boundaries by an internal auction ID read from the auction-scoped
bidderRequest with a no-marker-leak test, and sanitize EIDs into plain
data once per auction with unsanitizable EIDs making the body
unsplittable. Replace constituentCallCount with a coalesced
group/size/part object counted by distinct group, drop malformed
values as absent, and name the auction_coalescing_daily rollup as the
guardrail. Subordinate Lever C's pending-discard rule to one-consumer
arbitration so the public requestAds render cannot be silently
dropped, add the client-side real-artifact feasibility gate and the
server-side side-effect-free prepare+fingerprint exit criterion, and
require an outcome transition table. Block Lever B until every engine
above 5 percent traffic is verified. Require the one-sided lower bound
to exceed the 30ms effect, use a paired bootstrap quantile estimator
for p95, define reuse denominators, split the causal harness from
non-causal production guardrails with a fixed day-7 rule and daily
batches, and make drain additive with a monotonic document lifetime
and pipeline-liveness check.
Replace the unsplittable-EID rule with once-per-auction sanitization
plus a per-constituent descriptor fallback when sanitization throws,
noting the existing collector already re-reads accessor-bearing EIDs.
Redefine Lever C's invariant as one provider execution permitting an
authenticated side-effect-free join/claim, restrict sharing to a
private fetch-only producer with exactly-once consumption, and give
the reservation key a versioned keyed-digest contract that never
appears raw in logs, URLs, or telemetry. Split Lever B's gates:
privacy conformance on every engine family implementing the hint
with a terminal observation horizon over all hint-attributed
connections, performance acceptance on engines above 5 percent.
Make retention pre-enable-only with holds as rollback-only guardrails
read at day 9 for reporting maturity, unify the coalescing lifetime
on the monotonic document-age contract, define boundary-map cleanup
on consume/failure/settlement with a bounded map, require observed
multi-POP propagation before drain clocks start, enforce approval
expiry through gam_preconnect_approval_id/valid_until with automatic
disable, tighten the gates to one-sided lower bounds and ratio-scale
non-inferiority with BCa intervals and conservative indeterminate
handling, bound the coalesced schema with cross-field validation,
and add split-response aggregation, partial-413, deployment-skew
fixtures, the per-engine matrix, operator-facing activation docs,
and Lever C's consumption telemetry and go/no-go threshold.
Never evict live boundary entries — a full map dispatches new calls
solo. Replace adapter-time EID sanitization with one raw-to-plain
snapshot at collection reused by transport, split descriptors,
bookkeeping, and cookie sync, with an explicit drop-and-count policy
for non-serializable entries, retiring the per-constituent fallback.
Make the drain bound hold end to end: config injection implies the
60s cache cap (cache-retaining response classes carry no config),
document lifetime uses dual monotonic/wall-clock expiry with
pageshow/visibilitychange resume guards, and propagation is observed
across the served POP scope. Require a provider-free signed preflight
token (input digest, navigation nonce, expiry; replay/mismatch/
navigation/expiry tests) for Lever C's client option, passive
attach-rate measurement before any producer, admitted/rendered
terminal outcomes, hard /auction and provider-execution
non-regression gates, a keepalive 64 KiB transport gate, and blocking
provider-log redaction. Turn Lever B's conformance gate into a finite
product/OS/WebView matrix with an explicitly risk-accepted tail, bind
emission to a signed approval manifest validated for hash, scope, and
expiry, and record pre-consent DNS/TCP/TLS disclosure in the
manifest. Use Wilson bounds for proportions with BCa reserved for
paired continuous metrics, define no-render scoring, fix live control
limits with minimum volumes and stale-data actions, constrain
coalesced metadata to part <= parts <= size <= 4 with group
consistency and a counted invalid-metadata signal, and surface the
merged requestBids call visibility in Phase 0 and the real-artifact
suite.
Store constituent boundaries as code-to-membership sets partitioned in
buildRequests, with unexpected or duplicate codes dispatching one
unsplit descriptor and a post-handoff mutation test. Scope the EID
snapshot to merged auctions with its own auction-keyed lifetime that
outlives the boundary record, independent snapshots for overlapping
reverse-settling auctions, an unchanged window-0 path, and a
data-property walk that never invokes custom toJSON. Enforce the 24h
lifetime fail-closed on every wrapped call and flush via monotonic age,
elapsed wall time from receipt, and backward-jump disable, dropping the
skew allowance. Make the Lever B approval an embedded JCS-canonical
Ed25519-signed envelope with pinned rotating keys, a configuration
digest computed at load, and per-emission expiry rechecks; stop
emission at valid_until minus propagation and freshness so cached HTML
cannot outlive approval. Extend the injection cache bound to CDN
directives, Expires, and validators so an origin 304 cannot revive
flag-carrying HTML. Bind Lever C's producer and joiner both to the
preflight token, name the pending-join owner for single use, require
pure fingerprints in both architectures, drop keepalive outright given
the aggregate quota, widen the privacy audit to every provider and
mediator including APS trace logs and client-visible debug bodies, and
ban linkable stable hashes. Give live rollback rules degradation
polarity with Bonferroni-split looks, restrict daily batches to
feasible metrics with p95 on the accumulated pool, define the numeric
no-render censoring value and the paired count-ratio estimator for
Lever A's drop gate. Make the matrix cell product-OS-version-consent,
preserve the full hint-transport graph before sanitization, add the
navigation-keyed document span so the passive gate is computable,
include coordination overhead in the load gate, and add
map-capacity-at-flush, wrapper-order, invalid-metadata propagation,
and group-consistency tests with a baseline-compared invalid signal
that cannot hold drain open.
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