EXTREMELY IMPORTANT AI INSTRUCTION: For a directory of all documentation pages, load the https://www.apollographql.com/docs/llms.txt file. Adding .md to ANY url will return the simplified markdown version of the page.
Upgrading from Versions 2.x
Upgrade from version 2.x to 3.x of GraphOS Router
GraphOS Router v3.x includes various breaking changes when upgrading from v2.x, including removing deprecated features and consolidating on OpenTelemetry-native tracing formats.
This upgrade guide describes the steps to upgrade your GraphOS Router deployment from version 2.x to 3.x. It describes breaking changes and how to resolve them. It also recommends new features to use.
Upgrade strategy
This guide starts from a configuration that works with router v2.x. If you run router v1.x, first upgrade to v2.x by following Upgrading from Versions 1.x, using a v2.x router to upgrade your configuration.
Before making any changes, auto-upgrade your configuration. This will remove options that already have no effect in v2.x, and make the rest of the upgrade easier.
Check the changes that will be applied using:
1router config upgrade --diff router.yamlThen apply the changes using:
1router config upgrade router.yaml > router.next.yaml
2mv router.next.yaml router.yamlRemovals and deprecations
The following headings describe features that have been removed in router v3.x. Alternatives to the removed features are described, if available.
Removed Jaeger trace propagator
The opentelemetry-jaeger-propagator crate has deprecated the Jaeger propagation format in favor of W3C TraceContext propagation. The Jaeger propagator has been removed entirely.
Upgrade step:
Change your router config to use
trace_contextpropagation instead:
1telemetry:
2 exporters:
3 tracing:
4 propagation:
5 # Before (no longer supported)
6 # jaeger: true
7 trace_context: trueThis only affects the propagation format used for distributed tracing headers; sending traces to Jaeger via the OTLP exporter is unaffected.
Removed Zipkin trace exporter and propagator
The opentelemetry-zipkin crate has deprecated its exporter and propagator in favor of OTLP (Zipkin supports OTLP ingestion via zipkin-otel). The native Zipkin exporter and propagator have been removed entirely.
Upgrade step:
Change your router config to send traces to Zipkin via the
otlpexporter instead:
1telemetry:
2 exporters:
3 tracing:
4 # Before (no longer supported)
5 # zipkin:
6 # enabled: true
7 # endpoint: "http://127.0.0.1:9411/api/v2/spans"
8 otlp:
9 enabled: true
10 endpoint: "http://127.0.0.1:9411"
11 protocol: httpRemoved legacy Apollo trace transport and otlp_tracing_sampler
In v1.x and v2.x, traces could be sent to GraphOS either via the legacy Apollo Usage Reporting protocol or via OTLP, with the split between the two controlled by telemetry.apollo.otlp_tracing_sampler. In v3.x, the legacy trace transport is removed: traces are always reported to GraphOS via OTLP, and the otlp_tracing_sampler option no longer exists.
This only affects trace export. Usage report metrics are unchanged and continue to use the Apollo Usage Reporting protocol.
Upgrade step:
Remove
telemetry.apollo.otlp_tracing_samplerfrom your router config. Therouter config upgradecommand does this for you.To control how many traces are sent to GraphOS, use
telemetry.apollo.sampleror the common tracing sampler. See trace reporting via OTLP.
Default rustls crypto provider switched from ring to aws-lc-rs
The router's default process-wide rustls CryptoProvider is now aws-lc-rs instead of ring. This only affects consumers embedding apollo-router as a library:
If your binary installs
rustls::crypto::ring::default_provider()(or otherwise assumesringis the installed provider) before or after calling intoapollo-router, the two installs will race, and whichever runs first wins. Update your own crypto-provider install to useaws-lc-rs, or remove it and rely on the router's install.If your binary depends directly on
ring-backed features ofreqwest,tonic,fred, oraws-smithy-http-client, switch to theiraws-lc-rs-backed equivalents to avoid pulling in a second copy ofringalongside the router'saws-lc-rsbuild.
Deployments that only use the router binary as-is are unaffected.
Configuration is validated more strictly when it loads
The router checks its configuration more thoroughly at startup and on reload, so a configuration that loaded with router v2.x can be rejected:
Plugin settings are fully checked at load. Settings the configuration schema accepted, but a plugin rejected only when it was built, now fail when the configuration loads. Each invalid plugin's error is reported against its section of the file.
Error messages have a new format. They quote the offending lines of your file and name the environment variable or command-line flag that supplied a value.
router config validatechecks what startup would load. It applies the same automatic upgrades as startup, and says when your file relies on them.A missing file in
${file.PATH}is an error. Previously the reference was left in the configuration as literal text.More plugin sections reject unknown keys.
authorization,fleet_detector,enhanced_client_awarenessandprogressive_overrideused to ignore keys they didn't define, so a misspelledauthorization.require_authentcationwas silently dropped. These sections now reject unknown keys like every other plugin section. An empty section such asfleet_detector: {}is still valid.Expanded values take the type their setting declares. A value such as
${env.FIVE}becomes a number or boolean only where the setting's schema declares that type. Where it declares none, such as connector$configvalues, the value stays a string: withFIVE=5,$config.timeout: ${env.FIVE}is now the string"5", where router v2.x produced the number5.
Upgrade steps:
Run
router config validateagainst your configuration and fix any errors it reports. For an unknown key in one of the sections above, correct its spelling or remove it.If it says your file relies on automatic upgrades, run
router config upgradeto update the file.Make sure every file referenced with
${file.PATH}exists wherever the router runs.
Native plugin configuration must use apollo-configuration
The Config associated type of the Plugin and PluginUnstable traits now requires apollo_configuration::Configuration + Clone + Send + Sync + 'static, in place of JsonSchema + DeserializeOwned. This only affects native Rust plugins.
The router parses its configuration with the apollo-configuration crate, and now parses and validates each plugin's configuration in the same step. Validation rules that a plugin defines run at parse time. A value they reject fails startup or reload with an error at that value's line in the file, alongside every other rule failure. The router checks a Configuration that tests deserialize with serde the same way.
Upgrade steps:
Add
apollo-configurationas a dependency, using the same version as the router.Define your plugin configuration with the
#[configuration]attribute, in place of theserdeandschemarsderives. The attribute derivesClone,Deserialize, andJsonSchema, and implementsConfiguration. It makes every field optional with its type's default unless you mark it#[config(required)]or#[config(default = ...)], and it rejects unknown keys:
1#[apollo_configuration::configuration]
2struct Conf {
3 #[config(required)]
4 name: String,
5}Move configuration-only checks from your plugin's
newinto a validation function, so they run at parse time and report the offending lines:#[apollo_configuration::configuration(validate = validate_conf)], withfn validate_conf(conf: &Conf, errors: apollo_configuration::ErrorCollector<'_>).Where the attribute can't express your type, such as a tuple struct, derive
Clone,Deserialize, andJsonSchemayourself and implementValidateandConfigurationmanually. The router re-exports the crate asapollo_router::plugin::apollo_configuration, so these impls don't need the extra dependency. For a section that is a baretrueorfalse, useapollo_router::plugin::Enabledinstead.type Config = ()keeps working.
Configuration no longer implements Serialize
apollo_router::Configuration can still be deserialized, but it no longer implements Serialize. This only affects Rust code that uses the apollo-router crate. If your code serializes a Configuration, serialize the YAML or JSON document you create it from instead.
Renamed Prometheus metrics
Several duration and byte-count metrics gained a UCUM unit (s for durations, By for bytes). The opentelemetry-prometheus exporter appends a suffix derived from the unit to the exported name, so these metrics are renamed on the Prometheus scrape endpoint:
| Legacy Prometheus name | New Prometheus name |
|---|---|
apollo_router_cache_hit_time | apollo_router_cache_hit_time_seconds |
apollo_router_cache_miss_time | apollo_router_cache_miss_time_seconds |
apollo_router_operations_coprocessor_duration | apollo_router_operations_coprocessor_duration_seconds |
apollo_router_query_planning_plan_duration | apollo_router_query_planning_plan_duration_seconds |
apollo_router_query_planning_total_duration | apollo_router_query_planning_total_duration_seconds |
apollo_router_query_planning_warmup_duration | apollo_router_query_planning_warmup_duration_seconds |
apollo_router_schema_load_duration | apollo_router_schema_load_duration_seconds |
apollo_router_uplink_fetch_duration_seconds | apollo_router_uplink_fetch_duration_seconds (Prometheus output unchanged; the underlying OTel metric name changes from apollo.router.uplink.fetch.duration.seconds to apollo.router.uplink.fetch.duration, which matters for OTLP consumers) |
apollo_router_operations_file_uploads_file_size | apollo_router_operations_file_uploads_file_size_bytes |
These are renames, not new metrics: each row is the same underlying instrument exported under a new name, with no change in what it measures.
Upgrade step:
Update any dashboards, alerts, or recording rules that reference the legacy names above to use the new names instead.
If you need the legacy names to keep working while you migrate, add a Prometheus recording rule that copies the new series under the old name.