Repository navigation
optable-targeting new features: non-blocking early network call + hid + id5 - #4601
justadreamer wants to merge 92 commits into
Conversation
return early from the hook if (hasRawAuctionRequestHook && hasBidderRequestHook) - the new mode
random is 0-99, percentage is 0-100. enrich when random < (strictly less than) percentage. so if we specify percentage 0 we never enrich, if we specify percentage 100 we always enrich.
…ntage property is specified and it's value is 100%
The account config node is deserialized with the prebid mapper, which uses SNAKE_CASE, so the unannotated fields bound to enrich_web and enrich_app while the documented account keys are enrich-web and enrich-app. Per-account overrides of both were silently dropped and the global value kept.
…change Core changed ExecutionPlan.endpoints key type from Endpoint to HookHttpEndpoint. Map.get accepts Object, so the stale lookup kept compiling but always returned null against the EnumMap: the module read a bidder-request hook timeout of 0, so the targeting API call inherited only the raw-auction-request stage budget (50ms) instead of the configured 500ms and timed out on every auction.
Bidders from stored imps are never enriched when other imps name bidders inline. If one imp lists its bidders and another only references a stored imp, the raw hook doesn't defer. This affects mixed Prebid Mobile and web setups.
…execution-plan lookup
…taining Optable hook
…larify execution-time
| import java.util.List; | ||
|
|
||
| public class BidRequestCleaner implements PayloadUpdate<AuctionRequestPayload> { | ||
| public class AuctionRequestCleaner implements PayloadUpdate<AuctionRequestPayload> { |
There was a problem hiding this comment.
Please make the default constructor private.
| } | ||
|
|
||
| private static BidRequest clearExtUserOptable(BidRequest bidRequest) { | ||
| protected static BidRequest clearExtUserOptable(BidRequest bidRequest) { |
There was a problem hiding this comment.
This method should be private. There are no classes inheriting from this class.
| final ObjectNode cleanedOptable = cleanOptable((ObjectNode) optable); | ||
| final ExtUser extUserCopy = extUser.toBuilder().build(); | ||
| extUserCopy.addProperties(extUser.getProperties()); | ||
| if (cleanedOptable.isEmpty()) { |
There was a problem hiding this comment.
This if/else statement can be replaced with a ternary expression:
extUserCopy.addProperty(OPTABLE_FIELD, cleanedOptable.isEmpty() ? null : cleanedOptable);
| } | ||
|
|
||
| private static Set<String> extractUniqueBidders(BidRequest bidRequest) { | ||
| return Optional.ofNullable(bidRequest.getImp()) |
There was a problem hiding this comment.
Optional.ofNullable().stream() can be simplified to Stream.ofNullable().
| @AllArgsConstructor(staticName = "of") | ||
| public class BidderEnrichmentSampler { | ||
|
|
||
| private static final String PREBID_BIDDER_PATH = "/prebid/bidder"; |
There was a problem hiding this comment.
Replace PREBID_BIDDER_PATH with
private static final JsonPointer PREBID_BIDDER_JSON_POINTER = JsonPointer.valueOf("/prebid/bidder");
to avoid recompiling the JsonPointer at every usage.
| setEnrichRequestStatus(EnrichmentStatus.failure()); | ||
| } | ||
|
|
||
| public boolean hasOptableTargetingProperties() { |
There was a problem hiding this comment.
Unused method, please remove.
| } | ||
|
|
||
| @Bean | ||
| OptableTargetingFlowResolver earlyOptableCallResolver( |
There was a problem hiding this comment.
Align bean name with the class name.
| } | ||
| moduleContext.setBiddersToEnrich(biddersToEnrich); | ||
| final Account account = invocationContext.auctionContext().getAccount(); | ||
| final long crossHookFutureTimeout = |
There was a problem hiding this comment.
This is a cross hook timeout, so it shouldn't be taken from a single hook's timeout. Please use the whole request's timeout invocationContext.auctionContext().getTimeoutContext().getTimeout(). The idea is to keep hook timeouts as small as possible to not stall the processing pipeline.
There was a problem hiding this comment.
It's done this way by design.
We understand the intention to keep hook execution times as short as possible to avoid blocking the auction processing pipeline. However, the current proposal doesn't address our requirements.
We need to make a remote API call during hook steps execution and receive its response before proceeding with bidder invocation, as the returned targeting data is required to enrich the auction request. If the hook times out before the API call completes, we lose that data and cannot properly enrich the request.
At the same time, we use this value to control the API call timeout. It should be shorter than the whole auction execution timeout, allowing us to fail fast if the remote service is slow or unresponsive, rather than waiting for the entire auction execution window.
In other words, Hook execution time:
- lmits how long we wait for the remote service.
- limits the total time the hook is allowed to consume.
This gives us predictable latency, prevents unnecessary slow API calls, and ensures that the hook has enough time to get a response from API call.
The shorter HTTP client timeout also allows us to keep Connection Pool more robust.
| : defaultValue; | ||
| } | ||
|
|
||
| public boolean hasBidderRequestHook(Account account) { |
There was a problem hiding this comment.
This class exposes two public functions: hasBidderRequestHook and getOptableTargetingBidderRequestTimeout. After fixing other comments, these functions shouldn't be necessary and this class can be removed.
There was a problem hiding this comment.
hasBidderRequestHook was removed. getOptableTargetingBidderRequestTimeout is left due to comment above.
|
|
||
| final BidRequest bidRequest = applyActivityRestrictions(payload.bidRequest(), invocationContext); | ||
|
|
||
| final Timeout timeout = apiTimeout == null |
There was a problem hiding this comment.
Please remove this conditional from here. You can just change the apiTimeout parameter from type Long to type Timeout and perform these calculations in calling functions, instead of passing null in one of the cases and calculating timeout here.
| enabled: true | ||
| triplelift: | ||
| enabled: true | ||
| adf: |
There was a problem hiding this comment.
Please remove unnecessary bidders from sample config to keep it clean.
There was a problem hiding this comment.
I kept several bidders because they are referenced in the sample request and configuration.
|
Please update the PR description to match the latest changes.
|
Type of changes
What's the context?
Two related improvements to the
optable-targetingmodule: the Optable API call no longer blocks the auction pipeline, and the Targeting API request now carries the parameters needed for mobile in-app resolution (hid, bundle/ver, ID5).Latency: non-blocking early network call
The API call starts at
raw-auction-requestand runs in parallel with the rest of the auction. The result is consumed per bidder atbidder-request, where enrichment is actually injected, so the pipeline never waits on the network.Stored requests and stored imps are merged after
raw-auction-request, so for requests whose bidders orsite/applive there (f.e. Prebid Mobile SDK traffic) the call is started by the asyncprocessed-auction-requesthook (optable-targeting-async-processed-auction-request-hook) on the merged request instead. The same hook starts the call for AMP, which has noraw-auction-requeststage. Bidders are read from bothimp.ext.prebid.bidderand the old-styleimp.ext.<bidder>.Traffic controls
enrichment-percentage/bidder-enrichment-percentages: sampling rate (0-100) applied per bidder. A bidder with no entry falls back to the account default;0disables it entirely.enrich-web/enrich-app: select which traffic sources are enriched.Sampling applies to the split plan only. The legacy
optable-targeting-processed-auction-request-hookenriches the whole bid request before bidders split out, so it cannot sample per bidder.Targeting API request parameters
hid-prefixes(new config entry): comma-separated id prefixes to emit ashid=, in the given order (e.g."e,p,i6,a,g,c"). Independent ofid-prefix-order, and selective rather than merely ordering: only the listed prefixes are sent ashid.bundle/ver: emitted frombidRequest.appwhen present.id5_signaturein: read fromuser.ext.optable, forwarded as a Targeting API query attribute alongside email/phone/zip/vid.id5_signatureout: returned inext.prebid.passthrough.optable.id5_signature. PBS cannot persist the signature between requests, so it round-trips through the client, which caches it and replays it on the next auction.Backwards compatibility
No breaking changes. The existing
optable-targeting-processed-auction-request-hookkeeps its previous synchronous behavior and is now deprecated. The split plan uses the newoptable-targeting-async-processed-auction-request-hookatprocessed-auction-request, which never blocks and only starts the call for stored-request and AMP traffic.Configuration (recommended)
The
bidder-requestgroup timeout bounds the wait on the API call, so it must accommodate round-trip latency to the Optable edge. Theraw-auction-requestandprocessed-auction-requesttimeouts only cover dispatch.To migrate from the legacy plan, replace
optable-targeting-processed-auction-request-hookwithoptable-targeting-async-processed-auction-request-hookand add theraw-auction-requestandbidder-requesthooks.Test plan
Quality check
cc: @jrosendahl-opt @softcoder594