Skip to content

Commit 5ea22db

Browse files
Batching based on sharding target
1 parent fe33085 commit 5ea22db

3 files changed

Lines changed: 49 additions & 8 deletions

File tree

lib/src/main/java/graphql/nadel/NextgenEngine.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ internal class NextgenEngine(
198198
)
199199

200200
val result: ExecutionResult = try {
201-
val fields = fieldToService.getServicesForTopLevelFields(operation, executionHints)
201+
val fields = fieldToService.getServicesForTopLevelFields(executionContext)
202202
val results = coroutineScope {
203203
fields
204204
.map { (fields, service) ->

lib/src/main/java/graphql/nadel/engine/transform/query/NadelFieldToService.kt

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package graphql.nadel.engine.transform.query
33
import graphql.introspection.Introspection
44
import graphql.nadel.NadelExecutionHints
55
import graphql.nadel.Service
6+
import graphql.nadel.engine.NadelExecutionContext
67
import graphql.nadel.engine.blueprint.IntrospectionService
78
import graphql.nadel.engine.blueprint.NadelIntrospectionRunnerFactory
89
import graphql.nadel.engine.blueprint.NadelOverallExecutionBlueprint
@@ -24,9 +25,11 @@ internal class NadelFieldToService(
2425
private val introspectionService = IntrospectionService(querySchema, introspectionRunnerFactory)
2526

2627
fun getServicesForTopLevelFields(
27-
query: ExecutableNormalizedOperation,
28-
executionHints: NadelExecutionHints,
28+
executionContext: NadelExecutionContext,
2929
): List<NadelFieldAndService> {
30+
val query = executionContext.query
31+
val executionHints = executionContext.hints
32+
3033
// Feature flag: when root-field batching is globally disabled, keep the original behaviour
3134
// of one entry (i.e. one service call) per root field.
3235
if (!executionHints.batchRootFields()) {
@@ -39,9 +42,12 @@ internal class NadelFieldToService(
3942
}
4043
}
4144

42-
// Group batch-eligible root fields per service into a single entry (one service call).
45+
// Group batch-eligible root fields into a single entry (one service call) per (service, shard).
46+
// Two root fields can be owned by the same service but routed to different shards (e.g. different
47+
// cloud IDs), so they must not be batched together. Nadel stays generic here: the sharding target
48+
// comes from NadelExecutionHooks.getShardingTarget and is used purely as an opaque grouping key.
4349
val result = mutableListOf<NadelFieldAndService>()
44-
val batchedByService = LinkedHashMap<Service, MutableList<ExecutableNormalizedField>>()
50+
val batchedByGroup = LinkedHashMap<NadelBatchGroup, MutableList<ExecutableNormalizedField>>()
4551
for (topLevelField in query.topLevelFields) {
4652
if (isNamespacedField(topLevelField)) {
4753
result += getServicePairsForNamespacedFields(topLevelField, executionHints)
@@ -50,14 +56,15 @@ internal class NadelFieldToService(
5056

5157
val service = getService(topLevelField)
5258
if (canBatchRootField(topLevelField, service, executionHints)) {
53-
batchedByService.getOrPut(service) { mutableListOf() }.add(topLevelField)
59+
val shardingTarget = executionContext.hooks.getShardingTarget(executionContext, service, topLevelField)
60+
batchedByGroup.getOrPut(NadelBatchGroup(service, shardingTarget)) { mutableListOf() }.add(topLevelField)
5461
} else {
5562
result += NadelFieldAndService(fields = listOf(topLevelField), service = service)
5663
}
5764
}
5865

59-
batchedByService.forEach { (service, batchedFields) ->
60-
result += NadelFieldAndService(fields = batchedFields, service = service)
66+
batchedByGroup.forEach { (group, batchedFields) ->
67+
result += NadelFieldAndService(fields = batchedFields, service = group.service)
6168
}
6269

6370
return result
@@ -155,3 +162,14 @@ data class NadelFieldAndService(
155162
val service: Service,
156163
)
157164

165+
/**
166+
* Grouping key for root-field batching: root fields are only batched together when they share the
167+
* same [service] and the same [shardingTarget]. [shardingTarget] is an opaque value supplied by
168+
* [graphql.nadel.hooks.NadelExecutionHooks.getShardingTarget] (`null` when the field is not bound to
169+
* a specific shard).
170+
*/
171+
private data class NadelBatchGroup(
172+
val service: Service,
173+
val shardingTarget: Any?,
174+
)
175+

lib/src/main/java/graphql/nadel/hooks/NadelExecutionHooks.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,29 @@ interface NadelExecutionHooks {
4444
return null
4545
}
4646

47+
/**
48+
* Returns an opaque key identifying the target "shard" that a top level (root) [field] will be
49+
* routed to. It is used to decide which root fields may be batched into a single service call.
50+
*
51+
* Root fields are only batched together when they share the same [service] AND the same sharding
52+
* target. Two fields can belong to the same [service] but be routed to different shards (e.g.
53+
* different cloud IDs), in which case they cannot be sent in the same request.
54+
*
55+
* Nadel treats the returned value as opaque - it groups by it via [equals]/[hashCode] and never
56+
* interprets it - so Atlassian-specific concerns such as cloud ID / ARI parsing stay entirely in
57+
* the implementation.
58+
*
59+
* Return `null` when the field is not bound to a specific shard; such fields are grouped together
60+
* (per service) separately from shard-specific ones.
61+
*/
62+
fun getShardingTarget(
63+
executionContext: NadelExecutionContext,
64+
service: Service,
65+
field: ExecutableNormalizedField,
66+
): Any? {
67+
return null
68+
}
69+
4770
fun <T : NadelGenericHydrationInstruction> getHydrationInstruction(
4871
virtualField: ExecutableNormalizedField,
4972
instructions: List<T>,

0 commit comments

Comments
 (0)