The Anatomy of an OpenRTB Bid Request: Compression, Connection Keep-Alives, and JSON Parsing Performance

An OpenRTB 2.6 bid request operating inside a 120-millisecond client-side header bidding timeout window or a 200-millisecond Connected TV (CTV) Server-Side

High-speed 3D network illustration of raw data cubes entering a polished chrome gzip compression nozzle and exiting as a razor-thin cobalt-blue HTTP keep-alive laser stream.

An OpenRTB 2.6 bid request operating inside a 120-millisecond client-side header bidding timeout window or a 200-millisecond Connected TV (CTV) Server-Side Ad Insertion (SSAI) pod auction loses up to 35% of its total execution budget before a Demand-Side Platform (DSP) evaluates a single targeting parameter. In high-throughput programmatic auctions, the milliseconds lost between an exchange edge router and a bidder's listening endpoint rarely stem from core bidding logic. Instead, latency accumulates inside the mechanics of network transport and payload serialization: ephemeral TCP handshakes, unresumed TLS negotiations, suboptimal gzip compression levels, heap-allocating JSON reflection parsers, and bloated extension (ext) objects.

When p99 transport and serialization latency breaches the auction timeout threshold (tmax), valid DSP bid responses arrive at the Supply-Side Platform (SSP) after the auction has already closed. In Google Ad Manager (GAM 360), Prebid Server, or proprietary mobile and CTV mediation layers, late bids are functionally identical to no-bids. This late-bid attrition directly degrades bid density, softens floor clearing prices, and erodes yield across Mobile App, CTV, and Web inventory.

At scale, programmatic yield optimization is fundamentally a systems engineering problem. When an SSP processes hundreds of thousands of queries per second (QPS) across global points of presence (PoPs), shaving 8 milliseconds off wire serialization and socket negotiation directly increases the volume of clearing bids that make the auction cutoff. Examining the lifecycle of an OpenRTB 2.6 HTTP POST request from memory allocation to wire transmission reveals how transport tuning, compression physics, and schema hygiene govern real-time monetization performance.

Network Transport Optimization Between SSPs and DSPs

Every inbound ad request arriving at an exchange triggers a fan-out amplification event. A single CTV ad pod request or Prebid Mobile SDK call routinely fans out into 15 to 40 concurrent outbound OpenRTB HTTP POST requests distributed across participating tier-1 DSP endpoints. If the underlying network transport layer treats these outbound calls as isolated, stateless transactions, handshake overhead immediately saturates both the auction clock and the host operating system's ephemeral port table.

To prevent transport bottlenecks, the IAB Tech Lab OpenRTB specifications explicitly recommend HTTP persistent connections (Connection: keep-alive) and gzip payload compression (Content-Encoding: gzip paired with Accept-Encoding: gzip) to minimize bandwidth consumption and eliminate repetitive TCP handshake latency between exchanges and DSPs. Within our low-latency OpenRTB infrastructure, treating these transport recommendations as strict architectural invariants rather than optional headers is the baseline requirement for sub-100ms auction execution.

HTTP Persistent Connections and TLS Session Resumption

Opening a new network socket for an outbound OpenRTB bid request imposes a severe latency penalty dictated by round-trip time (RTT). A cold HTTPS connection requires one full RTT for the TCP three-way handshake (SYN, SYN-ACK, ACK), followed by one to two additional RTTs to complete the TLS handshake and verify cryptographic certificates. Even when an SSP edge cluster and a DSP listening endpoint are co-located within the same regional cloud availability zone or metropolitan peering exchange (such as US-East Northern Virginia, Frankfurt, or Tokyo) with a 4ms base RTT, a cold TCP plus TLS handshake burns 8ms to 12ms before the first byte of the OpenRTB JSON payload ever leaves the network interface card (NIC). Cross-regional calls with a 25ms RTT lose 50ms to 75ms on connection setup alone, rendering the auction unviable.

Cold TCP connections also suffer from TCP slow-start. Newly initialized sockets begin with a restricted initial congestion window (initcwnd, typically 10 maximum segment sizes, or roughly 14.6 KB on standard 1500-byte MTU Ethernet links). If an uncompressed multi-impression CTV bid request exceeds 14.6 KB, the kernel TCP stack must pause transmission after the initial burst and wait an entire RTT for an ACK packet from the DSP before expanding the congestion window (cwnd) to transmit the remaining bytes.

Maintaining warm, persistent HTTP/1.1 keep-alive connection pools or multiplexed HTTP/2 streams eliminates both handshake RTTs and slow-start stalls. By keeping sockets warm and continuously active, the congestion window remains expanded, and TLS encryption keys remain negotiated in memory. In Prebid Server (Go) and high-concurrency exchange routers, connection pool tuning requires aligning application-level idle settings with kernel socket parameters and DSP load balancer timeouts:

package transport

import (
	"crypto/tls"
	"net"
	"net/http"
	"time"
)

func NewOpenRTBTransport() *http.Transport {
	return &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout:   40 * time.Millisecond,
			KeepAlive: 30 * time.Second,
			DualStack: true,
		}).DialContext,
		MaxIdleConns:          20000,
		MaxIdleConnsPerHost:   1024,
		MaxConnsPerHost:       2048,
		IdleConnTimeout:       60 * time.Second,
		TLSHandshakeTimeout:   35 * time.Millisecond,
		ExpectContinueTimeout: 0,
		DisableCompression:    false,
		TLSClientConfig: &tls.Config{
			MinVersion:         tls.VersionTLS13,
			ClientSessionCache: tls.NewLRUClientSessionCache(4096),
		},
	}
}

A common production failure mode in standard Go and Java HTTP clients occurs when MaxIdleConnsPerHost is left near its default value (which is only 2 in Go's standard http.DefaultTransport). Under a 5,000 QPS burst to a single DSP endpoint, a tiny idle pool forces the runtime to close and re-open thousands of sockets per second, exhausting ephemeral ports (EADDRNOTAVAIL), filling the kernel connection tracking table (nf_conntrack) with sockets stuck in TIME_WAIT, and spiking TLS CPU utilization. Setting MaxIdleConnsPerHost to match peak concurrency per upstream worker node, enforcing TLS 1.3 with an LRU ClientSessionCache for 0-RTT or 1-RTT session ticket resumption, and setting IdleConnTimeout slightly below the upstream DSP load balancer's idle timeout (typically 60 seconds on cloud application load balancers) prevents mid-flight RST packets when reusing pooled connections after diurnal traffic dips.

Gzip Compression Ratios on Payload Sizes vs. CPU Decompression Cost

While persistent connections solve handshake latency, payload compression governs wire transmission time and network egress costs. An uncompressed OpenRTB 2.6 JSON payload ranges from 3.5 KB for a standard mobile web banner to over 22 KB for a CTV video pod request populated with multiple imp objects, extended identity graphs (user.ext.eids), IAB Content Taxonomy arrays, and multi-hop source.ext.schain declarations.

Because OpenRTB JSON consists of highly repetitive ASCII keys, structural braces, quotation marks, and recurring taxonomy strings, Deflate-based gzip compression (Content-Encoding: gzip) achieves exceptional byte reduction. Across production traffic, gzip compresses OpenRTB 2.6 payloads by 72% to 82%, shrinking an 18 KB CTV bid request down to approximately 3.4 KB on the wire. This reduction guarantees that even complex multi-slot requests fit comfortably inside a single TCP congestion window, preventing packet fragmentation and reducing cross-datacenter serialization delay on 25GbE and 100GbE NICs.

However, enabling gzip compression introduces a non-trivial CPU vs. bandwidth engineering tradeoff. Standard library compression packages (such as compress/gzip in Go or java.util.zip.GZIPOutputStream in Java) default to compression level 6 (Z_DEFAULT_COMPRESSION). At level 6, the LZ77 sliding-window matcher spends significant CPU cycles scanning backward through the byte buffer to find the longest possible duplicate byte sequences before building dynamic Huffman trees. Under heavy fan-out load, level 6 gzip compression can consume 15% to 22% of total CPU cycles on an SSP edge node.

Benchmarking OpenRTB 2.6 payloads across compression levels reveals a clear diminishing return curve:

  • Uncompressed (Level 0): 16,400 bytes wire size; 0 microseconds CPU compression time; high risk of exceeding initial TCP cwnd and triggering multi-RTT packet splits.
  • Gzip Level 1 (BestSpeed): 3,620 bytes wire size (77.9% reduction); ~18 microseconds CPU time per payload; fits inside 3 TCP segments.
  • Gzip Level 6 (DefaultCompression): 3,180 bytes wire size (80.6% reduction); ~54 microseconds CPU time per payload; requires 3x the CPU cycles of Level 1 to save just 440 bytes (less than one-third of a single Ethernet MTU frame).
  • Gzip Level 9 (BestCompression): 3,110 bytes wire size (81.0% reduction); ~115 microseconds CPU time per payload; actively harms p99 auction latency by starving request worker threads of CPU time.

Configuring SSP egress pipelines to use Gzip Level 1 (BestSpeed) alongside SIMD-accelerated compression libraries (which leverage AVX2 and AVX-512 instructions for parallel hash table lookups and CRC32 calculation) captures nearly 97% of the maximum possible byte reduction at a fraction of the CPU cost. Furthermore, instantiating a new gzip writer per request allocates roughly 256 KB of internal hash tables and sliding-window buffers on the heap. Pooling gzip writers via thread-local storage or sync.Pool and invoking writer.Reset(targetBuffer) per outbound request eliminates heap allocations during the compression phase.

Minimizing JSON Serialization Overhead at 500,000 QPS

Once network sockets and compression pipelines are tuned, the primary compute bottleneck inside an exchange shifts to JSON serialization and deserialization. Consider an SSP cluster processing 25,000 inbound publisher requests per second with an average fan-out factor of 20 DSPs per auction, resulting in 500,000 outbound OpenRTB QPS. At an average uncompressed request size of 10 KB, the cluster must serialize 5 gigabytes of structured JSON text every single second while concurrently parsing hundreds of thousands of inbound JSON bid responses.

Standard runtime reflection serializers (such as Go's encoding/json or unoptimized reflection modes in Java's Jackson library) cannot sustain 500,000 QPS without severe latency degradation. Runtime reflection inspects struct metadata dynamically, boxes primitive values into heap-allocated interface pointers, checks string characters byte-by-byte for HTML escaping, and repeatedly reallocates underlying byte slices as the JSON output grows. At 5 gigabytes per second, this allocation churn overwhelms the memory allocator, degrades CPU L1/L2 instruction and data cache locality, and forces the garbage collector (GC) into continuous concurrent mark-and-sweep cycles. Even a 12-millisecond GC stop-the-world pause or scheduler assist delay across an SSP node shifts the entire p99 latency distribution outward, causing thousands of in-flight auctions to miss their Prebid or GAM Open Bidding deadlines.

High-throughput programmatic exchanges bypass runtime reflection through three architectural patterns:

  1. Compile-Time AST Code Generation: Instead of inspecting struct tags at runtime, static code generators (such as easyjson in Go or compiled bytecode writers in Java) parse the OpenRTB 2.6 struct definitions at build time and emit deterministic, type-specific serialization methods. These generated methods append raw byte literals directly into a contiguous buffer without interface boxing or reflection lookups.
  2. Pre-Serialized Immutable Payload Segments: During a single auction fan-out across 25 DSPs, approximately 80% to 85% of the OpenRTB bid request is invariant. The app or site object, device telemetry, user object (for DSPs sharing the same identity graph configuration), regs privacy signals (GPP, GDPR TCF v2.2, US Privacy), and source.ext.schain remain identical across every outbound request in that auction. Only bidder-specific fields such as imp[].bidfloor, seat-specific deal IDs (pmp.deals), tmax, and partner ext mappings change per DSP. By serializing the invariant objects once into a []byte slice at the start of the auction and splicing those raw bytes into each DSP's outbound buffer using json.RawMessage or direct byte-slice assembly, an exchange reduces per-auction serialization CPU work by over 65%.
  3. Zero-Allocation Buffer Pooling: Allocating outbound HTTP request bodies from a pre-warmed memory pool sized to the p95 payload length (for example, 16,384 bytes) ensures that JSON marshaling and gzip compression occur entirely inside reused memory slabs.
package openrtb

import (
	"bytes"
	"compress/gzip"
	"sync"
)

var bufferPool = sync.Pool{
	New: func() any { return bytes.NewBuffer(make([]byte, 0, 16384)) },
}

var gzipWriterPool = sync.Pool{
	New: func() any {
		w, _ := gzip.NewWriterLevel(nil, gzip.BestSpeed)
		return w
	},
}

func SerializeAndCompress(req PrecompiledBidRequest) (*bytes.Buffer, error) {
	buf := bufferPool.Get().(*bytes.Buffer)
	buf.Reset()

	gz := gzipWriterPool.Get().(*gzip.Writer)
	gz.Reset(buf)

	if err := req.WriteDeterministicJSON(gz); err != nil {
		gzipWriterPool.Put(gz)
		bufferPool.Put(buf)
		return nil, err
	}

	if err := gz.Close(); err != nil {
		gzipWriterPool.Put(gz)
		bufferPool.Put(buf)
		return nil, err
	}

	gzipWriterPool.Put(gz)
	return buf, nil
}

By pairing deterministic code-generated serialization (WriteDeterministicJSON) with pooled gzip.BestSpeed writers writing directly into pre-allocated 16 KB bytes.Buffer instances, heap allocations per outbound DSP request drop to near zero. The garbage collector remains dormant, L3 cache misses plummet, and p99 serialization latency stays flat under peak QPS bursts.

Trimming Dead Weight: Removing Redundant Extensions (ext) That Trigger DSP Timeouts

Even when an SSP perfects its TCP connection pools, gzip compression levels, and zero-allocation JSON serializers, payload bloat inside the OpenRTB request body can still sabotage auction performance on the buy side. Every byte an SSP compresses and transmits must be decompressed and parsed by the receiving DSP. Tier-1 DSPs operate under strict ingress compute budgets; when a bidder's edge parser encounters an unnecessarily massive or deeply nested JSON payload, it consumes extra CPU cycles during decompression and DOM tree construction, increasing the probability that the DSP's internal QPS throttler drops the request or fails to respond within tmax.

In modern programmatic supply paths across Web, Mobile App, and CTV, payload bloat concentrates heavily inside four areas of the OpenRTB specification:

  • Cross-Contaminated Wrapper Extensions (imp[].ext): When an ad request originates in a client-side Prebid.js 11.x wrapper or Prebid Mobile SDK and routes through Prebid Server, the imp[].ext.prebid.bidder node often contains placement IDs and custom parameters for all 15 configured adapters. If the exchange fails to strip the parameters of the other 14 adapters before serializing the outbound OpenRTB request to a specific DSP, that DSP receives kilobytes of irrelevant configuration metadata. Worse, deep custom key-value maps in imp[].ext.data often duplicate signals already present in standard OpenRTB 2.6 fields.
  • Unfiltered Extended Identity Arrays (user.ext.eids): Publishers frequently resolve 15 to 25 alternative identity modules in the browser or app environment. Transmitting all 25 identity objects in user.ext.eids to a DSP that only ingests three specific ID spaces forces the DSP's JSON parser to allocate, validate, and discard dozens of unused strings on every single QPS tick.
  • Static Blocklist Bloat (bcat, badv, bapp): Publishers with complex brand-safety policies often attach 300+ blocked advertiser domains (badv), 100+ blocked mobile app bundle IDs (bapp), and dozens of IAB category codes (bcat) to every single bid request. When these blocklists are static across a publisher's account, transmitting 6 KB of identical domain strings 500,000 times per second wastes massive network and parser capacity. Because Sky Boosts strictly monetizes 100% Google-friendly and HUMAN / Pixalate IVT-clean inventory (mainstream brand-safe supply with zero alcohol, gambling, adult, tobacco, or restricted categories), baseline category filtering and creative compliance are enforced structurally at the SSP seat configuration and response validation layer rather than bloating every outbound JSON packet.
  • Unpruned Nulls and Empty Objects: Serializers that fail to enforce omitempty semantics output keys like "wseat": [], "battr": [], or "ext": {}, multiplying lexical tokens that the DSP's JSON state machine must tokenize and skip.

When provisioning custom OpenRTB seats for DSP partners, our platform applies strict per-seat schema sanitation and field filtering before serialization. Each DSP endpoint receives a surgically tailored OpenRTB 2.6 payload containing only the identity graphs (eids), supply chain nodes (schain), and impression extensions (imp[].ext) mapped to that buyer's ingestion engine:

{
  "id": "80ce30c53c16e",
  "tmax": 140,
  "cur": ["USD"],
  "imp": [
    {
      "id": "1",
      "video": {
        "mimes": ["video/mp4"],
        "minduration": 15,
        "maxduration": 30,
        "protocols": [7, 8],
        "w": 1920,
        "h": 1080,
        "startdelay": 0,
        "plcmt": 1,
        "linearity": 1,
        "maxseq": 4,
        "podid": "pod_992",
        "minbitrate": 1500,
        "maxbitrate": 8000
      },
      "bidfloor": 14.50,
      "bidfloorcur": "USD",
      "secure": 1
    }
  ],
  "app": {
    "id": "ctv_app_4410",
    "bundle": "com.MainstreamStream.tv",
    "name": "Mainstream Stream CTV",
    "publisher": { "id": "pub_8821" },
    "content": {
      "cat": ["IAB1-5"],
      "cattax": 6,
      "livestream": 0
    }
  },
  "device": {
    "ua": "Mozilla/5.0 (Linux; Tizen 6.5) AppleWebKit/537.36",
    "ip": "198.51.100.42",
    "devicetype": 3,
    "ifa": "7b2a9d1e-4c3f-4a8b-9e1d-0f2c4a6b8d0e",
    "lmt": 0
  },
  "user": {
    "ext": {
      "eids": [
        {
          "source": "uidapi.com",
          "uids": [{ "id": "A4G9K2L8M1N0P3Q", "atype": 3 }]
        }
      ]
    }
  },
  "source": {
    "fd": 1,
    "tid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "ext": {
      "schain": {
        "complete": 1,
        "ver": "1.0",
        "nodes": [
          {
            "asi": "skyboosts.com",
            "sid": "pub_8821",
            "hp": 1
          }
        ]
      }
    }
  },
  "regs": {
    "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
    "gpp_sid": [7]
  }
}

Notice what is absent from this CTV OpenRTB 2.6 payload: there are no leaked Prebid wrapper configs from competing adapters, no empty arrays, no redundant OpenRTB 2.5 legacy privacy flags when IAB Global Privacy Platform (regs.gpp) strings are present, and no unmapped eids. The video object utilizes native OpenRTB 2.6 pod bidding parameters (maxseq, podid, plcmt) rather than proprietary nested extensions, and the source.ext.schain reflects a direct, single-hop (hp: 1) supply path.

For AdOps architects and platform engineers, the operational takeaway is straightforward: treat wire payload size and socket reuse as primary monetization KPIs alongside floor pricing and timeout thresholds. Audit outbound OpenRTB traffic regularly via packet capture and telemetry histograms, enforce HTTP persistent connections with tuned per-host idle pools and TLS 1.3 session caching, standardize on pooled Gzip Level 1 (BestSpeed) compression, replace runtime reflection with deterministic byte-buffer serialization, and strip unmapped ext dead weight before it hits the NIC. Every millisecond reclaimed from network transport and JSON parsing converts directly into higher DSP response completion rates, stronger auction clearing prices, and predictable, high-margin yield across reliable Net-30 and Net-60 publisher payment cycles.

Next step

Put it to work.

Every pattern in this article runs on our unified SSP and monetization platform: GAM and MCM management, Prebid client and server-side, and CTV SSAI.

Net-30 / Net-60 terms · 100% brand-safe demand