Apollo Router
OverviewGet StartedRequest Lifecycle
Configuration
Features

Security

Observability

Performance and Scaling

Client Features

Query Planning

Customization

Deployment
Releases
GraphOS Integration
Reference

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:

Bash
1router config upgrade --diff router.yaml

Then apply the changes using:

Bash
1router config upgrade router.yaml > router.next.yaml
2mv router.next.yaml router.yaml

Removals 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_context propagation instead:

YAML
router.yaml
1telemetry:
2  exporters:
3    tracing:
4      propagation:
5        # Before (no longer supported)
6        # jaeger: true
7        trace_context: true

This 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 otlp exporter instead:

YAML
router.yaml
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: http

Removed 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_sampler from your router config. The router config upgrade command does this for you.

  • To control how many traces are sent to GraphOS, use telemetry.apollo.sampler or 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 assumes ring is the installed provider) before or after calling into apollo-router, the two installs will race, and whichever runs first wins. Update your own crypto-provider install to use aws-lc-rs, or remove it and rely on the router's install.

  • If your binary depends directly on ring-backed features of reqwest, tonic, fred, or aws-smithy-http-client, switch to their aws-lc-rs-backed equivalents to avoid pulling in a second copy of ring alongside the router's aws-lc-rs build.

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 validate checks 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_awareness and progressive_override used to ignore keys they didn't define, so a misspelled authorization.require_authentcation was silently dropped. These sections now reject unknown keys like every other plugin section. An empty section such as fleet_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 $config values, the value stays a string: with FIVE=5, $config.timeout: ${env.FIVE} is now the string "5", where router v2.x produced the number 5.

Upgrade steps:

  • Run router config validate against 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 upgrade to 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-configuration as a dependency, using the same version as the router.

  • Define your plugin configuration with the #[configuration] attribute, in place of the serde and schemars derives. The attribute derives Clone, Deserialize, and JsonSchema, and implements Configuration. It makes every field optional with its type's default unless you mark it #[config(required)] or #[config(default = ...)], and it rejects unknown keys:

Rust
1#[apollo_configuration::configuration]
2struct Conf {
3    #[config(required)]
4    name: String,
5}
  • Move configuration-only checks from your plugin's new into a validation function, so they run at parse time and report the offending lines: #[apollo_configuration::configuration(validate = validate_conf)], with fn validate_conf(conf: &Conf, errors: apollo_configuration::ErrorCollector<'_>).

  • Where the attribute can't express your type, such as a tuple struct, derive Clone, Deserialize, and JsonSchema yourself and implement Validate and Configuration manually. The router re-exports the crate as apollo_router::plugin::apollo_configuration, so these impls don't need the extra dependency. For a section that is a bare true or false, use apollo_router::plugin::Enabled instead.

  • 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 nameNew Prometheus name
apollo_router_cache_hit_timeapollo_router_cache_hit_time_seconds
apollo_router_cache_miss_timeapollo_router_cache_miss_time_seconds
apollo_router_operations_coprocessor_durationapollo_router_operations_coprocessor_duration_seconds
apollo_router_query_planning_plan_durationapollo_router_query_planning_plan_duration_seconds
apollo_router_query_planning_total_durationapollo_router_query_planning_total_duration_seconds
apollo_router_query_planning_warmup_durationapollo_router_query_planning_warmup_duration_seconds
apollo_router_schema_load_durationapollo_router_schema_load_duration_seconds
apollo_router_uplink_fetch_duration_secondsapollo_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_sizeapollo_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.