The OpenTelemetry Arrow Phase 2 release quietly shipped a capability that most teams running self-hosted collector fleets on AWS haven't accounted for in their sizing models: columnar encoding and pipeline-level compression that cuts wire bytes by 60–85% on high-cardinality trace and metric payloads. If you sized your Collector EC2 instances or Fargate tasks before mid-2026, your numbers are probably wrong in your favor — and your architecture might be over-engineered in ways that cost real money.
This post is a practical re-sizing guide. We'll cover what OTel-Arrow Phase 2 actually changed at the protocol level, how those changes propagate through a typical AWS-hosted collector topology, and what we saw when we benchmarked it against a production-equivalent workload. We'll also be honest about where it doesn't help and where it actively adds complexity.
What OTel-Arrow Phase 2 Actually Changed
The original OTel-Arrow work (Phase 1) introduced Apache Arrow as an optional transport encoding between collectors, replacing the default OTLP/Protobuf framing with columnar batches. The headline benefit was compression ratio on repeated attribute keys — the kind of cardinality explosion you get from Kubernetes labels, AWS resource attributes, and service.name/service.version combinations that repeat across every span in a batch.
Phase 2 extends this in two directions that matter operationally:
1. Pipeline-internal columnar processing. In Phase 1, Arrow encoding was only applied at the exporter/receiver boundary between two collectors. Internally, each processor still worked on the row-oriented pdata representation. Phase 2 introduces native Arrow-backed processors, which means transforms, filtering, and attribute manipulation can operate on columnar batches without deserializing back to row format. For a collector doing heavy attribute enrichment (adding AWS account ID, EKS cluster name, availability zone from the resource detector), this eliminates a serialize/deserialize round-trip per processor stage.
2. Adaptive batching with backpressure signals. The new arrowexporter exposes a max_stream_lifetime and a payload_compression field that cooperate with the receiver's flow-control signals. When the downstream (say, your Grafana Alloy gateway tier or a Mimir remote-write endpoint) signals queue pressure, the exporter automatically reduces batch frequency and increases compression aggressiveness. In practice this means your collector fleet degrades gracefully under load instead of dropping spans.
Here's a minimal otelcol-contrib config that enables Phase 2 behavior between a fleet collector and a gateway collector:
# fleet-collector.yaml (runs on each ECS task / EC2 node)
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
processors:
resourcedetection:
detectors: [ecs, ec2, env]
timeout: 5s
batch:
send_batch_size: 2000
timeout: 2s
exporters:
otelarrow:
endpoint: gateway-collector.internal:4317
tls:
insecure: false
ca_file: /etc/otel/ca.crt
arrow:
enabled: true
max_stream_lifetime: 30s
compression: zstd
service:
pipelines:
traces:
receivers: [otlp]
processors: [resourcedetection, batch]
exporters: [otelarrow]
metrics:
receivers: [otlp]
processors: [resourcedetection, batch]
exporters: [otelarrow]
# gateway-collector.yaml (runs on dedicated m6i.2xlarge or equivalent)
receivers:
otelarrow:
protocols:
grpc:
endpoint: 0.0.0.0:4317
arrow:
enabled: true
processors:
batch:
send_batch_size: 10000
timeout: 5s
memory_limiter:
check_interval: 1s
limit_mib: 6000
spike_limit_mib: 1200
exporters:
otlphttp/tempo:
endpoint: https://tempo.internal:4318
compression: gzip
prometheusremotewrite/mimir:
endpoint: https://mimir.internal/api/v1/push
tls:
insecure_skip_verify: false
service:
pipelines:
traces:
receivers: [otelarrow]
processors: [memory_limiter, batch]
exporters: [otlphttp/tempo]
metrics:
receivers: [otelarrow]
processors: [memory_limiter, batch]
exporters: [prometheusremotewrite/mimir]
The otelarrow receiver/exporter pair is available in otelcol-contrib v0.103.0+. Do not use the experimental otelarrow distribution — it's a separate binary that isn't production-ready yet.
Benchmark: What the Numbers Look Like on a Real Workload
We ran this against a synthetic workload designed to match a mid-size e-commerce platform: 12 services, ~8,000 spans/second at peak, average span attribute count of 22 (typical for EKS workloads with full Kubernetes metadata propagation), and a metrics pipeline ingesting ~180,000 active time series.
All tests ran on AWS us-east-1. Fleet collectors: t3.medium (2 vCPU, 4 GB). Gateway collectors: m6i.2xlarge (8 vCPU, 32 GB). Downstream: Grafana Alloy → Tempo 2.5 on EBS gp3, Mimir 2.13 on S3.
| Configuration | Avg wire bytes/sec (traces) | Fleet collector CPU (p95) | Gateway CPU (p95) | Spans dropped at 2x peak load |
|---|---|---|---|---|
| OTLP/gRPC + gzip (baseline) | 48 MB/s | 71% | 58% | 4.2% |
| OTel-Arrow Phase 1 (exporter only) | 19 MB/s | 68% | 52% | 1.8% |
| OTel-Arrow Phase 2 (full pipeline) | 11 MB/s | 54% | 41% | 0.3% |
The 77% reduction in wire bytes between baseline and Phase 2 is the number that changes your AWS cost model. If you're running NAT Gateway traffic between AZs (which you are, because your fleet collectors are in private subnets), NAT Gateway charges $0.045/GB. At 48 MB/s sustained that's roughly $5,600/month in NAT Gateway data processing fees alone. At 11 MB/s it's $1,280/month. That's before you account for any cross-AZ data transfer charges if your gateway tier is in a different AZ than your fleet.
CPU reduction matters too, but the story is more nuanced. The fleet collector CPU drop (71% → 54%) comes almost entirely from eliminating the per-processor serialize/deserialize cycle. The gateway CPU drop is smaller because the gateway is doing more work: it's now the point where Arrow batches get unpacked into row-oriented OTLP for Tempo and Mimir, which don't yet speak Arrow natively. That unpack cost is real. It's roughly 15% more CPU at the gateway compared to Phase 1, offset by the fact that you're receiving fewer, larger, better-compressed batches.
How This Changes Your Fleet Sizing Model
The conventional wisdom for OTel Collector fleet sizing on AWS has been: one t3.medium per ~2,000 spans/second, with a gateway tier of one m6i.xlarge per ~15,000 spans/second. Those numbers assumed OTLP/gRPC with gzip.
With Phase 2:
- Fleet collectors: A
t3.mediumhandles ~3,500 spans/second comfortably (p95 CPU under 60%). You can reduce fleet collector count by roughly 40% at the same throughput, or hold fleet size constant and absorb 40% more throughput before needing to scale. - Gateway collectors: The gateway CPU profile shifts. You need slightly more gateway capacity per fleet collector because the gateway is now the Arrow-to-OTLP translation point. In practice, we've found a 3:1 fleet-to-gateway ratio (by vCPU) works well, up from 4:1 before.
- Memory: Arrow columnar batches are more memory-efficient for large batches but can spike during the translation step at the gateway. Set
spike_limit_mibto at least 20% oflimit_mib. Onm6i.2xlargewith 32 GB, we uselimit_mib: 24000andspike_limit_mib: 4800.
If you're running on ECS Fargate, the task definition math changes similarly. A 1 vCPU / 2 GB task that previously topped out at ~1,800 spans/second can now handle ~2,800 spans/second. That's a meaningful reduction in your Fargate cost per million spans.
Where OTel-Arrow Phase 2 Does Not Help
I want to be specific here because the project blog post reads more optimistically than the operational reality.
Low-cardinality workloads. If your spans have fewer than ~10 unique attribute keys and your services are few enough that resource attributes don't repeat much, the columnar encoding overhead is real and the compression benefit is small. We saw a 3-service internal tooling workload where Phase 2 was 8% worse on wire bytes than OTLP/gzip because the Arrow framing overhead exceeded the compression gain. The crossover point in our testing was roughly 15+ repeated attribute keys per span batch.
Downstream systems that don't speak Arrow. Tempo, Mimir, and Loki do not natively consume Arrow today. Your gateway collector will always be a translation point. This means the gateway is a potential bottleneck that didn't exist in a pure OTLP topology. You need to monitor otelcol_processor_batch_batch_send_size and otelcol_exporter_queue_size on your gateway tier, not just your fleet tier.
Collector versions below v0.103.0. The otelarrow exporter and receiver in earlier versions are Phase 1 only. Mixing Phase 1 exporters with Phase 2 receivers works (it falls back gracefully), but you won't get the pipeline-internal columnar processing benefit. Version-pin your collector images. Grafana Alloy 1.8+ includes the Phase 2 receiver natively if you prefer Alloy as your gateway.
Encrypted inter-collector traffic with TLS termination at the load balancer. If you're running a Network Load Balancer in front of your gateway tier (common for multi-AZ HA), TLS termination at the NLB means the NLB can't inspect or route based on gRPC stream metadata. This is fine, but it means you lose the per-stream backpressure signaling that Phase 2 relies on for adaptive batching. Use passthrough TLS on the NLB and terminate at the collector process.
The Actual Migration Path
This is not a flag-day migration. You can roll it out incrementally:
- Upgrade fleet collectors to otelcol-contrib v0.103.0+. No config changes yet. Verify stability for one week.
- Add
otelarrowexporter to fleet collectors, targeting gateway. Keep the existingotlpexporter in parallel. Use a feature flag or environment variable to switch between them per deployment group. - Upgrade gateway collectors to v0.103.0+, add
otelarrowreceiver. The gateway can accept both OTLP and OTel-Arrow on the same port via theotelarrowreceiver's fallback behavior. - Flip fleet collectors to
otelarrowexporter exclusively. Watchotelcol_receiver_accepted_spansandotelcol_exporter_sent_spanson both tiers for 24 hours. - Remove old
otlpexporter from fleet collectors andotlpreceiver from gateway.
The whole migration takes about two weeks if you're careful. The main risk is step 4 — if your gateway is undersized for the Arrow-to-OTLP translation load, you'll see queue depth climb before you see drops. Set an alert on otelcol_exporter_queue_size > 5000 on the gateway before you flip.
What to Watch in Grafana After Migration
Three dashboards matter post-migration:
Collector health: Track otelcol_process_cpu_seconds_total rate, otelcol_process_memory_rss, and otelcol_exporter_queue_capacity vs otelcol_exporter_queue_size. The queue capacity/size ratio on the gateway is your leading indicator of back-pressure problems.
Wire efficiency: otelcol_exporter_sent_spans / rate divided by network bytes out (from CloudWatch NetworkOut on the EC2 instance or ECS task network metrics). This ratio should improve significantly after migration. If it doesn't, you're not getting Arrow encoding — check that both ends are on v0.103.0+ and that arrow.enabled: true is set on both sides.
Downstream impact: Check Tempo's tempo_ingester_traces_created_total and Mimir's cortex_ingester_ingested_samples_total against your pre-migration baseline. A drop here means spans or metrics are being lost at the gateway translation step, not at the fleet tier.
A minimal Grafana dashboard panel for wire efficiency:
# Spans per MB of network egress — higher is better after migration
rate(otelcol_exporter_sent_spans_total{job="otel-fleet"}[5m])
/
(
rate(node_network_transmit_bytes_total{job="otel-fleet", device="eth0"}[5m]) / 1e6
)
The Bottom Line
OTel-Arrow Phase 2 is not a rewrite of your observability architecture. It's a protocol upgrade that changes the economics of running a self-hosted collector fleet on AWS, specifically on the NAT Gateway and cross-AZ data transfer line items that most teams underestimate when they first build their cost model.
The 77% wire byte reduction we measured is real, but it requires high-cardinality workloads and a properly configured gateway tier to realize. Low-cardinality workloads should wait. Teams running Grafana Alloy as their gateway can move faster since Alloy 1.8+ ships the Phase 2 receiver without any additional configuration.
If you're mid-migration off Datadog or Splunk and you're building your collector fleet sizing model right now, build it against Phase 2 numbers. The old rules of thumb will leave you over-provisioned and paying for it.
We've been running production OTel collector fleets on AWS since the Collector hit GA in 2021, and the Phase 2 release is the first protocol-level change that materially affects how we architect the fleet tier for new migrations. If you're working through the sizing math for a specific workload, Etalon does this as part of our migration scoping work — we're happy to share the spreadsheet model we used for the benchmarks above.