Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

experimental support for query batching #3837

Merged
merged 84 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from 79 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
842e34b
first working version
garypen Sep 15, 2023
94adb0c
improve comment to show that it is in fact working
garypen Sep 15, 2023
4768e27
remove commented out code
garypen Sep 15, 2023
ef91e72
Merge branch 'dev' into garypen/126-query-batching
garypen Sep 15, 2023
a235f28
process_value() can be private
garypen Sep 15, 2023
5847b32
make the code DRY(er)
garypen Sep 15, 2023
09ba72f
remove redundant clone()
garypen Sep 15, 2023
c9e5ffe
Add minimal configuration support
garypen Sep 15, 2023
4af04e3
lint
garypen Sep 15, 2023
839eec5
fix tests and update snapshots
garypen Sep 16, 2023
4096503
make the batching feature experimental
garypen Sep 18, 2023
72d188b
replace unwieldy tuple with TranslateError
garypen Sep 18, 2023
6202ab3
add some documentation for query batching
garypen Sep 18, 2023
9f77cb7
add tests for the new query batching functionality
garypen Sep 19, 2023
3d312b9
Merge branch 'dev' into garypen/126-query-batching
garypen Sep 19, 2023
911201f
add a changeset
garypen Sep 19, 2023
7482904
add json test fixtures
garypen Sep 19, 2023
0d934b0
Merge branch 'dev' into garypen/126-query-batching
garypen Sep 19, 2023
035b952
add metrics
garypen Sep 20, 2023
0880561
Merge branch 'dev' into garypen/126-query-batching
garypen Sep 20, 2023
4a3406e
modify apollo telemetry so that it understands batches.
garypen Sep 20, 2023
9a4f06a
refine the batching configuration metric
garypen Sep 20, 2023
bf33f09
add missing snapshot
garypen Sep 20, 2023
3ba599f
remove batching config documentation as requested in review
garypen Sep 20, 2023
643e267
Copyedit and organize query batching docs. Mark as experimental.
shorgi Sep 20, 2023
a1de70d
copyedit
shorgi Sep 20, 2023
c35daba
TLS client authentication (#3794)
Geal Sep 20, 2023
7f1a986
reintroduce rhai json functions (#3785)
Geal Sep 20, 2023
02fd749
Disable helm check for missing kubernetes versions until supported by…
garypen Sep 21, 2023
4c6722f
Require Batching mode to be explicitly specified in configuration
garypen Sep 21, 2023
33f5934
@defer not @delay
garypen Sep 21, 2023
e9c87cc
Merge branch 'dev' into garypen/126-query-batching
garypen Sep 21, 2023
206509a
add some batch tests to appol_reports.rs
garypen Sep 21, 2023
15079db
update docs to indicate there is no default for mode
garypen Sep 21, 2023
a835923
Address code-review comments on vector allocation
garypen Sep 22, 2023
6088b38
More code review comments
garypen Sep 22, 2023
d3438c0
More code review comments
garypen Sep 22, 2023
3cc67f8
More code review comments
garypen Sep 22, 2023
32489a2
Merge branch 'dev' into garypen/126-query-batching
garypen Sep 22, 2023
050eead
lint
garypen Sep 22, 2023
e0a1e18
enforce the constraint that defer/subscribe can't occur in a batch
garypen Sep 25, 2023
bed7ab0
Merge branch 'dev' into garypen/126-query-batching
garypen Sep 25, 2023
88c8326
modify batch processing to execute concurrently
garypen Sep 25, 2023
8bce700
add a note to the changeset about concurrent execution within a batch
garypen Sep 25, 2023
1b0a7dd
Merge branch 'dev' into garypen/126-query-batching
garypen Sep 25, 2023
db6e392
add a comment about concurrent execution in the docs
garypen Sep 25, 2023
efd11c9
Merge branch 'dev' into garypen/126-query-batching
garypen Sep 25, 2023
ac3486f
add cross refs for query batching
shorgi Sep 26, 2023
02ba8e4
disable batching tests
garypen Sep 26, 2023
ecf9821
Merge branch 'dev' into garypen/126-query-batching
garypen Sep 26, 2023
fca84f5
Merge branch 'garypen/126-query-batching' of github.com:apollographql…
garypen Sep 26, 2023
f0c44bb
re-write apollo_reports tests
garypen Sep 26, 2023
df1f6f3
Merge branch 'dev' into garypen/126-query-batching
garypen Sep 26, 2023
4193ce6
Update docs/source/executing-operations/query-batching.mdx
garypen Sep 26, 2023
26a89c3
Update docs/source/executing-operations/query-batching.mdx
garypen Sep 26, 2023
5a05d73
Update docs/source/executing-operations/query-batching.mdx
garypen Sep 26, 2023
b43459b
add a comment to make it clear how defer/subs work with batches
garypen Sep 26, 2023
1fddf65
Update docs/source/executing-operations/query-batching.mdx
garypen Sep 26, 2023
e1fc470
Update docs/source/executing-operations/query-batching.mdx
garypen Sep 26, 2023
dda9146
Update docs/source/executing-operations/query-batching.mdx
garypen Sep 26, 2023
d6690ca
Update docs/source/executing-operations/query-batching.mdx
garypen Sep 26, 2023
08d6b2e
Update docs/source/executing-operations/query-batching.mdx
garypen Sep 26, 2023
6ee2195
Update docs/source/executing-operations/query-batching.mdx
garypen Sep 26, 2023
abd076f
Update docs/source/executing-operations/query-batching.mdx
garypen Sep 26, 2023
946d8cc
Update docs/source/executing-operations/query-batching.mdx
garypen Sep 26, 2023
16b5406
Update docs/source/executing-operations/query-batching.mdx
garypen Sep 26, 2023
1f4697c
Update docs/source/executing-operations/query-batching.mdx
garypen Sep 26, 2023
711a421
Update docs/source/executing-operations/query-batching.mdx
garypen Sep 26, 2023
ef1478f
Merge branch 'dev' into garypen/126-query-batching
garypen Sep 26, 2023
2f84692
Merge branch 'garypen/126-query-batching' of github.com:apollographql…
garypen Sep 26, 2023
3522cd0
Update docs/source/executing-operations/query-batching.mdx
garypen Sep 26, 2023
354deef
clarify apollo client and remove now
garypen Sep 26, 2023
9205be3
Merge branch 'garypen/126-query-batching' of github.com:apollographql…
garypen Sep 26, 2023
c8904ae
Update docs/source/executing-operations/query-batching.mdx
garypen Sep 26, 2023
44f4a7f
disable metrics test until understood better
garypen Sep 26, 2023
50432b8
Merge branch 'dev' into garypen/126-query-batching
garypen Sep 26, 2023
a01d1a3
clean up the code a little
garypen Sep 26, 2023
d5e549f
Copyedit from review comment for consistent terminology
shorgi Sep 26, 2023
ceab536
try aborting the tokio task to see if that improves stability
garypen Sep 26, 2023
4fd50dd
use enum for mode rather than hard-coded string in metric
garypen Sep 27, 2023
6a91eb5
Document the batching metrics
garypen Sep 27, 2023
b10ad02
fix display message for batching enum
garypen Sep 27, 2023
bc290f5
rework apollo_reports testing further
garypen Sep 27, 2023
0cb755d
Merge branch 'dev' into garypen/126-query-batching
garypen Sep 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .changesets/exp_garypen_126_query_batching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
### query batching prototype ([Issue #126](https://github.com/apollographql/router/issues/126))

An experimental implementation of query batching which adds support for client request batching to the Apollo Router.

If you’re using Apollo Client, you can leverage the in-built support for batching to reduce the number of individual requests sent to the Apollo Router.

Once [configured](https://www.apollographql.com/docs/react/api/link/apollo-link-batch-http/), Apollo Client will automatically combine multiple operations into a single HTTP request. The number of operations within a batch is client configurable, including the maximum number of operations in a batch and the maximum duration to wait for operations to accumulate before sending the batch request.

The Apollo Router must be configured to receive batch requests, otherwise it rejects them. When processing a batch request, the router deserializes and processes each operation of a batch independently, and it responds to the client only after all operations of the batch have been completed.

```yaml
experimental_batching:
enabled: true
mode: batch_http_link
bnjjj marked this conversation as resolved.
Show resolved Hide resolved
```

All operations within a batch will execute concurrently with respect to each other.

Do not attempt to use subscriptions or `@defer` queries within a batch as they are not supported.

By [@garypen](https://github.com/garypen) in https://github.com/apollographql/router/pull/3837
3 changes: 2 additions & 1 deletion apollo-router/feature_discussions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"experimental_retry": "https://github.com/apollographql/router/discussions/2241",
"experimental_response_trace_id": "https://github.com/apollographql/router/discussions/2147",
"experimental_logging": "https://github.com/apollographql/router/discussions/1961",
"experimental_http_max_request_bytes": "https://github.com/apollographql/router/discussions/3220"
"experimental_http_max_request_bytes": "https://github.com/apollographql/router/discussions/3220",
"experimental_batching": "https://github.com/apollographql/router/discussions/3840"
},
"preview": {
"preview_directives": "https://github.com/apollographql/router/discussions/3754"
Expand Down
6 changes: 6 additions & 0 deletions apollo-router/src/configuration/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,12 @@ impl Metrics {
opt.tracing.zipkin,
"$.tracing.zipkin[?(@.endpoint)]"
);
log_usage_metrics!(
value.apollo.router.config.batching,
"$.experimental_batching[?(@.enabled == true)]",
opt.mode,
"$.mode"
);
}
}

Expand Down
30 changes: 30 additions & 0 deletions apollo-router/src/configuration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ pub struct Configuration {

#[serde(default, skip_serializing, skip_deserializing)]
pub(crate) notify: Notify<String, graphql::Response>,

/// Batching configuration.
#[serde(default)]
pub(crate) experimental_batching: Batching,
}

impl PartialEq for Configuration {
Expand Down Expand Up @@ -234,6 +238,7 @@ impl<'de> serde::Deserialize<'de> for Configuration {
limits: Limits,
experimental_chaos: Chaos,
experimental_graphql_validation_mode: GraphQLValidationMode,
experimental_batching: Batching,
}
let ad_hoc: AdHocConfiguration = serde::Deserialize::deserialize(deserializer)?;

Expand All @@ -252,6 +257,7 @@ impl<'de> serde::Deserialize<'de> for Configuration {
.chaos(ad_hoc.experimental_chaos)
.uplink(ad_hoc.uplink)
.graphql_validation_mode(ad_hoc.experimental_graphql_validation_mode)
.experimental_batching(ad_hoc.experimental_batching)
.build()
.map_err(|e| serde::de::Error::custom(e.to_string()))
}
Expand Down Expand Up @@ -288,6 +294,7 @@ impl Configuration {
chaos: Option<Chaos>,
uplink: Option<UplinkConfig>,
graphql_validation_mode: Option<GraphQLValidationMode>,
experimental_batching: Option<Batching>,
) -> Result<Self, ConfigurationError> {
#[cfg(not(test))]
let notify_queue_cap = match apollo_plugins.get(APOLLO_SUBSCRIPTION_PLUGIN_NAME) {
Expand Down Expand Up @@ -322,6 +329,7 @@ impl Configuration {
},
tls: tls.unwrap_or_default(),
uplink,
experimental_batching: experimental_batching.unwrap_or_default(),
#[cfg(test)]
notify: notify.unwrap_or_default(),
#[cfg(not(test))]
Expand Down Expand Up @@ -360,6 +368,7 @@ impl Configuration {
chaos: Option<Chaos>,
uplink: Option<UplinkConfig>,
graphql_validation_mode: Option<GraphQLValidationMode>,
experimental_batching: Option<Batching>,
) -> Result<Self, ConfigurationError> {
let configuration = Self {
validated_yaml: Default::default(),
Expand All @@ -382,6 +391,7 @@ impl Configuration {
apq: apq.unwrap_or_default(),
preview_persisted_queries: persisted_query.unwrap_or_default(),
uplink,
experimental_batching: experimental_batching.unwrap_or_default(),
};

configuration.validate()
Expand Down Expand Up @@ -1273,3 +1283,23 @@ fn default_graphql_path() -> String {
fn default_graphql_introspection() -> bool {
false
}

#[derive(Clone, Debug, Default, Error, Display, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields, rename_all = "snake_case")]
pub(crate) enum BatchingMode {
/// batch_http_link mode
#[default]
BatchHttpLink,
}

/// Configuration for Batching
#[derive(Debug, Clone, Default, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub(crate) struct Batching {
/// Activates Batching (disabled by default)
#[serde(default)]
pub(crate) enabled: bool,

/// Batching mode
pub(crate) mode: BatchingMode,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: apollo-router/src/configuration/metrics.rs
expression: "&metrics.metrics"
---
value.apollo.router.config.batching:
- 1
- opt__mode__: batch_http_link

Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,37 @@ expression: "&schema"
},
"additionalProperties": false
},
"experimental_batching": {
"description": "Batching configuration.",
"default": {
"enabled": false,
"mode": "batch_http_link"
},
"type": "object",
"required": [
"mode"
],
"properties": {
"enabled": {
"description": "Activates Batching (disabled by default)",
"default": false,
"type": "boolean"
},
"mode": {
"description": "Batching mode",
"oneOf": [
{
"description": "batch_http_link mode",
"type": "string",
"enum": [
"batch_http_link"
]
}
]
}
},
"additionalProperties": false
},
"experimental_chaos": {
"description": "Configuration for chaos testing, trying to reproduce bugs that require uncommon conditions. You probably don’t want this in production!",
"default": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
experimental_batching:
enabled: true
mode: batch_http_link
81 changes: 42 additions & 39 deletions apollo-router/src/plugins/telemetry/tracing/apollo_telemetry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,12 @@ impl Exporter {
})
}

fn extract_root_trace(
fn extract_root_traces(
&mut self,
span: &LightSpanData,
child_nodes: Vec<TreeData>,
) -> Result<Box<proto::reports::Trace>, Error> {
) -> Result<Vec<proto::reports::Trace>, Error> {
let mut results: Vec<proto::reports::Trace> = vec![];
let http = extract_http_data(span);
let mut root_trace = proto::reports::Trace {
start_time: Some(span.start_time.into()),
Expand All @@ -235,17 +236,19 @@ impl Exporter {
client_version,
duration_ns,
} => {
if http.method != Method::Unknown as i32 {
let root_http = root_trace
.http
.as_mut()
.expect("http was extracted earlier, qed");
root_http.request_headers = http.request_headers;
root_http.response_headers = http.response_headers;
for trace in results.iter_mut() {
if http.method != Method::Unknown as i32 {
let root_http = trace
.http
.as_mut()
.expect("http was extracted earlier, qed");
root_http.request_headers = http.request_headers.clone();
root_http.response_headers = http.response_headers.clone();
}
trace.client_name = client_name.clone().unwrap_or_default();
trace.client_version = client_version.clone().unwrap_or_default();
trace.duration_ns = duration_ns;
}
root_trace.client_name = client_name.unwrap_or_default();
root_trace.client_version = client_version.unwrap_or_default();
root_trace.duration_ns = duration_ns;
}
TreeData::Supergraph {
operation_signature,
Expand All @@ -258,6 +261,7 @@ impl Exporter {
variables_json,
operation_name,
});
results.push(root_trace.clone());
}
TreeData::Execution(operation_type) => {
if operation_type == OperationKind::Subscription.as_apollo_operation_type() {
Expand All @@ -281,21 +285,17 @@ impl Exporter {
}
}

Ok(Box::new(root_trace))
Ok(results)
}

fn extract_trace(&mut self, span: LightSpanData) -> Result<Box<proto::reports::Trace>, Error> {
self.extract_data_from_spans(&span)?
.pop()
.and_then(|node| {
match node {
TreeData::Request(trace) | TreeData::SubscriptionEvent(trace) => {
Some(trace)
}
_ => None
}
})
.expect("root trace must exist because it is constructed on the request or subscription_event span, qed")
fn extract_traces(&mut self, span: LightSpanData) -> Result<Vec<proto::reports::Trace>, Error> {
let mut results = vec![];
for node in self.extract_data_from_spans(&span)? {
if let TreeData::Request(trace) | TreeData::SubscriptionEvent(trace) = node {
results.push(*trace?)
}
}
Ok(results)
}

fn extract_data_from_spans(&mut self, span: &LightSpanData) -> Result<Vec<TreeData>, Error> {
Expand Down Expand Up @@ -416,11 +416,11 @@ impl Exporter {
});
child_nodes
}
_ if span.attributes.get(&APOLLO_PRIVATE_REQUEST).is_some() => {
vec![TreeData::Request(
self.extract_root_trace(span, child_nodes),
)]
}
_ if span.attributes.get(&APOLLO_PRIVATE_REQUEST).is_some() => self
.extract_root_traces(span, child_nodes)?
.into_iter()
.map(|node| TreeData::Request(Ok(Box::new(node))))
.collect(),
ROUTER_SPAN_NAME => {
child_nodes.push(TreeData::Router {
http: Box::new(extract_http_data(span)),
Expand Down Expand Up @@ -549,9 +549,10 @@ impl Exporter {
.to_string(),
));

vec![TreeData::SubscriptionEvent(
self.extract_root_trace(span, child_nodes),
)]
self.extract_root_traces(span, child_nodes)?
.into_iter()
.map(|node| TreeData::SubscriptionEvent(Ok(Box::new(node))))
.collect()
}
_ => child_nodes,
})
Expand Down Expand Up @@ -704,12 +705,14 @@ impl SpanExporter for Exporter {
if span.attributes.get(&APOLLO_PRIVATE_REQUEST).is_some()
|| span.name == SUBSCRIPTION_EVENT_SPAN_NAME
{
match self.extract_trace(span.into()) {
Ok(mut trace) => {
let mut operation_signature = Default::default();
std::mem::swap(&mut trace.signature, &mut operation_signature);
if !operation_signature.is_empty() {
traces.push((operation_signature, *trace));
match self.extract_traces(span.into()) {
Ok(extracted_traces) => {
for mut trace in extracted_traces {
let mut operation_signature = Default::default();
std::mem::swap(&mut trace.signature, &mut operation_signature);
if !operation_signature.is_empty() {
traces.push((operation_signature, trace));
}
}
}
Err(Error::MultipleErrors(errors)) => {
Expand Down
Loading