optable-targeting: Enhance Optable Targeting: Non-blocking network calls and granular traffic controls#2
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.
There was a problem hiding this comment.
I suggest you avoid keeping "legacy" files. Those 2 files in this PR seem to not be referenced anywhere. It feels like you are just duplicating effort relative to git history. This causes bloat and will drift. Also, what does legacy mean when you'll have a third version ?
The README already describes the relevant content that was there before (the migration section). I think this is sufficient and those legacy files could be removed to keep it more tidy.
| return bidRequest; | ||
| } | ||
|
|
||
| final com.iab.openrtb.request.User bidRequestUser = Optional.ofNullable(bidRequest.getUser()) |
There was a problem hiding this comment.
Prefer importing com.iab.openrtb.request.User
There was a problem hiding this comment.
This comment applies everywhere in this file, other modified files and for anything else that can be imported.
There was a problem hiding this comment.
done, there was another FQN that was colliding final org.prebid.server.hooks.modules.optable.targeting.model.openrtb.User optableUser - replaced with var instead of type
| public boolean hasRawAuctionRequestHook(Account account) { | ||
| final String accountId = account != null ? account.getId() : null; | ||
|
|
||
| return StringUtils.isNotEmpty(accountId) | ||
| ? rawAuctionRequestHookCache.computeIfAbsent(accountId, id -> { | ||
| final ExecutionPlan accountSpecificHooksExecutionPlan = resolveExecutionPlan(account); | ||
| return hasHook( | ||
| accountSpecificHooksExecutionPlan, STAGE_RAW_AUCTION_REQUEST, HOOK_CODE_OPTABLE_RAW_AUCTION) | ||
| || hasGlobalRawAuctionRequestHook; | ||
| }) | ||
| : false; | ||
| } | ||
|
|
||
| public boolean hasBidderRequestHook(Account account) { | ||
| final String accountId = account != null ? account.getId() : null; | ||
|
|
||
| return StringUtils.isNotEmpty(accountId) | ||
| ? bidderRequestHookCache.computeIfAbsent(accountId, id -> { | ||
| final ExecutionPlan accountSpecificHoksExecutionPlan = resolveExecutionPlan(account); | ||
| return hasHook( | ||
| accountSpecificHoksExecutionPlan, STAGE_BIDDER_REQUEST, HOOK_CODE_OPTABLE_BIDDER_REQUEST) | ||
| || hasGlobalBidderRequestHook; | ||
| }) | ||
| : false; | ||
| } | ||
|
|
||
| public long getOptableTargetingBidderRequestTimeout(Account account) { | ||
| final String accountId = account != null ? account.getId() : null; | ||
|
|
||
| return StringUtils.isNotEmpty(accountId) | ||
| ? bidderRequestHookTimeoutCache.computeIfAbsent(accountId, id -> { | ||
| final ExecutionPlan accountSpecificHoksExecutionPlan = resolveExecutionPlan(account); | ||
| final long hookTimeOut = getHookTimeout( | ||
| accountSpecificHoksExecutionPlan, | ||
| STAGE_BIDDER_REQUEST, | ||
| HOOK_CODE_OPTABLE_BIDDER_REQUEST); | ||
| return hookTimeOut != 0 ? hookTimeOut : globalBidderRequestHookTimeout; | ||
| }) | ||
| : globalBidderRequestHookTimeout; | ||
| } |
There was a problem hiding this comment.
There is quite some code duplication in these 3 methods. Consider a refactor. Here is a Claude code suggestion where the logic of getting the accountId, then resolving the execution plan, then use the plan to do whatever evaluation, is written only once. The three initial methods then become one-liners.
private static <T> T computeFromAccount(Account account,
ConcurrentHashMap<String, T> cache,
T defaultValue,
Function<ExecutionPlan, T> compute) {
final String accountId = account != null ? account.getId() : null;
return StringUtils.isNotEmpty(accountId)
? cache.computeIfAbsent(accountId, id -> compute.apply(resolveExecutionPlan(account)))
: defaultValue;
}
public boolean hasRawAuctionRequestHook(Account account) {
return computeFromAccount(account, rawAuctionRequestHookCache, false,
plan -> hasHook(plan, STAGE_RAW_AUCTION_REQUEST, HOOK_CODE_OPTABLE_RAW_AUCTION)
|| hasGlobalRawAuctionRequestHook);
}
public boolean hasBidderRequestHook(Account account) {
return computeFromAccount(account, bidderRequestHookCache, false,
plan -> hasHook(plan, STAGE_BIDDER_REQUEST, HOOK_CODE_OPTABLE_BIDDER_REQUEST)
|| hasGlobalBidderRequestHook);
}
public long getOptableTargetingBidderRequestTimeout(Account account) {
return computeFromAccount(account, bidderRequestHookTimeoutCache, globalBidderRequestHookTimeout,
plan -> {
final long timeout = getHookTimeout(plan, STAGE_BIDDER_REQUEST, HOOK_CODE_OPTABLE_BIDDER_REQUEST);
return timeout != 0 ? timeout : globalBidderRequestHookTimeout;
});
}
This change requires also resolveExecutionPlan to be static, which it can be.
There was a problem hiding this comment.
@softcoder594 please take a look, as it is beyond cosmetical refactor for me, or we can just accept the suggestion of claude code, just verify we don't break anything
|
|
||
| import java.util.Objects; | ||
|
|
||
| public class NetworkCall { |
There was a problem hiding this comment.
This name is vague, generic. Consider finding a more suitable name that properly describes the purpose of this class. Something like OptableTargetingHelper ? Or something related to applying privacy and other properties to the targeting request.
There was a problem hiding this comment.
renamed to TargetingRequestExecutor
There was a problem hiding this comment.
I suggest those validation functions are placed in the same package as OptableTargetingProperties. It will encourage reusability and reduce imports.
| } | ||
|
|
||
| @Test | ||
| public void shouldLimitGppSidToTwoValues() { |
There was a problem hiding this comment.
well because Targeting API processes only 2 gpp_sids... according to optable-sandbox code. maybe we were wrong
There was a problem hiding this comment.
Ok, I confirm, just found that in the sandbox code, didn't know about this limit 👍
|
|
||
| // then | ||
| assertThat(query).contains("&gpp=DBABzw~1YNY~BVQqAAAAAgA"); | ||
| assertThat(query).containsPattern("&gpp_sid=\\d+,\\d+"); |
There was a problem hiding this comment.
You should assert that the 2 values are indeed 7 and 22. I think order does not matter here ? But the actual values do.
| return update(BidRequestCleaner.instance(), moduleContext); | ||
| } | ||
|
|
||
| public static <AuctionRequestPayload> Future<InvocationResult<AuctionRequestPayload>> update( |
There was a problem hiding this comment.
I think you can remove the first <AuctionRequestPayload> type parameter
|
|
||
| import java.util.Objects; | ||
|
|
||
| public class OptableTargetingProcessedAuctionRequestHook implements ProcessedAuctionRequestHook { |
There was a problem hiding this comment.
Please add a comment here if this is the now deprecated hook
ericst-amand-optable
left a comment
There was a problem hiding this comment.
Looks good overall. Submitted some small comments/refactor. I did not try to run the server, nor the tests, so this is pure code review for what it is.
Type of changes
What's the context?
This update reduces auction latency by initiating the Optable API call at the earliest hook stage (
raw-auction-request) and deferring the wait to the per-bidder stage (bidder-request), where the enrichment data is actually injected. It also adds granular controls over which traffic and which bidders receive enrichment.New Features
raw-auction-requestand runs in parallel with other auction processing. The result is consumed per-bidder inbidder-request, avoiding pipeline blocking.enrich-web/enrich-appflags to select which traffic sources are enriched.Backwards Compatibility
The
processed-auction-requesthook is retained as a legacy fallback. If operators have not migrated to the new execution plan, the module behaves as before — enrichment happens synchronously in the processed hook.New Configuration (recommended)
Legacy Configuration (to be migrated)
The following execution plan fragment should be removed once migrated to the new configuration above:
The
processed-auction-requesthook detects whether the new hooks (raw-auction-requestandbidder-request) are present in the execution plan. If both are active, it passes through immediately without blocking the pipeline. If the new hooks are absent, it falls back to the legacy synchronous behavior. This means the legacy fragment can be kept during migration without negating the latency benefit of the new configuration.Test plan
Quality check