@@ -3,6 +3,7 @@ package graphql.nadel.engine.transform.query
33import graphql.introspection.Introspection
44import graphql.nadel.NadelExecutionHints
55import graphql.nadel.Service
6+ import graphql.nadel.engine.NadelExecutionContext
67import graphql.nadel.engine.blueprint.IntrospectionService
78import graphql.nadel.engine.blueprint.NadelIntrospectionRunnerFactory
89import 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+
0 commit comments