Geode documentation tagged with QUIC Protocol. QUIC (Quick UDP Internet Connections) is a modern transport protocol that provides the reliability of TCP with the performance of UDP, featuring built-in encryption, multiplexing, and connection migration.
Introduction to QUIC
QUIC is a transport layer protocol initially developed by Google and subsequently standardized by the IETF in RFC 9000 (2021). Unlike TCP, which operates over raw IP and requires a separate TLS handshake for encryption, QUIC runs over UDP and integrates encryption directly into the protocol using TLS 1.3. This design eliminates head-of-line blocking, reduces connection establishment latency, and provides robust connection migration for mobile and multi-homed devices.
For database connections, QUIC offers significant advantages over traditional TCP/TLS stacks. The protocol’s stream multiplexing allows multiple concurrent queries over a single connection without one slow query blocking others. Connection migration means mobile applications maintain database connections when switching between WiFi and cellular networks. The 0-RTT resumption feature enables reconnecting clients to send application data immediately without waiting for handshake completion, critical for latency-sensitive applications.
Geode exclusively uses QUIC for client-server communication, running on the standard port 3141. There is no TCP fallback—all connections require QUIC support. This bold design choice reflects confidence in QUIC’s maturity and recognition that modern networks and client libraries have excellent QUIC support. By eliminating TCP compatibility layers, Geode achieves a simpler, more efficient wire protocol with guaranteed encryption and superior performance characteristics.
Key Concepts
UDP-Based Transport
QUIC runs over UDP rather than TCP:
- User Datagram Protocol: Connectionless, unreliable packet delivery
- QUIC Reliability Layer: Implements reliable, ordered delivery on top of UDP
- Congestion Control: Advanced congestion algorithms (Cubic, BBR)
- Packet Loss Recovery: Fast retransmission and forward error correction
Stream Multiplexing
QUIC supports multiple independent streams per connection:
- Bidirectional Streams: Both endpoints can send and receive
- Unidirectional Streams: Data flows in only one direction
- Stream IDs: Unique identifier for each stream
- No Head-of-Line Blocking: Packet loss on one stream doesn’t affect others
Integrated Encryption
TLS 1.3 is built into QUIC from the ground up:
- Mandatory Encryption: All QUIC packets are encrypted
- TLS 1.3 Handshake: Modern, efficient cryptographic handshake
- 0-RTT Resumption: Resume previous connection without handshake latency
- Forward Secrecy: Perfect forward secrecy for all connections
Connection Migration
QUIC connections can migrate across network paths:
- Connection IDs: Persistent identifier independent of IP address/port
- Path Validation: Verify new paths before migration
- Mobile Scenarios: Seamless WiFi to cellular transitions
- Multi-homed Hosts: Failover between network interfaces
1-RTT Connection Establishment
QUIC combines transport and encryption handshakes:
Client Server
| |
| Initial (ClientHello) |
|------------------------------------>|
| |
| <- Initial (ServerHello, Certificate, Finished)
| <- Handshake (CRYPTO) |
|<------------------------------------|
| |
| Handshake (Finished) |
| 1-RTT (Application Data) |
|------------------------------------>|
Compared to TCP + TLS (2-RTT minimum), QUIC achieves 1-RTT connection establishment, and 0-RTT for resumed connections.
How QUIC Works in Geode
Wire Protocol Architecture
Geode’s wire protocol leverages QUIC streams:
- Connection Establishment: Client initiates QUIC connection to port 3141
- Stream Allocation: Each query executes on a dedicated bidirectional stream
- Request/Response: Protobuf wire protocol messages flow over streams
- Concurrent Queries: Multiple queries execute in parallel using different streams
- Connection Pooling: Clients maintain persistent QUIC connections
Protobuf Messages Over QUIC
Each QUIC stream carries a request-response conversation using Protobuf messages:
ExecuteRequest→ExecutionResponse(schema + first page)PullRequest→ExecutionResponse(subsequent pages)
Stream Management
Geode uses QUIC streams efficiently:
- Query Streams: One bidirectional stream per query
- Control Stream: Stream 0 reserved for connection control messages
- CDC Streams: Unidirectional server-to-client streams for change events
- Multiplexing: Up to 100 concurrent streams per connection by default
Connection Migration Support
Geode fully supports QUIC connection migration:
import geode_client
# Client maintains connection during network changes
client = geode_client.open_database('quic://db.example.com:3141')
async with client.connection() as conn:
# Execute query on WiFi
result, _ = await conn.query("MATCH (n:Person) RETURN count(n)")
# User moves from WiFi to cellular - connection migrates automatically
# Network stack detects new path, validates it, and migrates connection
# Subsequent query uses migrated connection without interruption
result, _ = await conn.query("MATCH (n:Product) RETURN count(n)")
0-RTT Resumption
Geode supports 0-RTT for repeat connections:
# First connection: 1-RTT handshake
client = geode_client.open_database('quic://db.example.com:3141')
async with client.connection() as conn:
result, _ = await conn.query("MATCH (n) RETURN count(n)")
# Server sends session ticket for resumption
# Second connection within ticket lifetime: 0-RTT
client = geode_client.open_database('quic://db.example.com:3141')
async with client.connection() as conn:
# Query sent in first packet, no handshake wait
result, _ = await conn.query("MATCH (n) RETURN count(n)")
Use Cases
Low-Latency Query Execution
import geode_client
import asyncio
async def measure_query_latency():
"""Measure end-to-end query latency with QUIC."""
client = geode_client.open_database('quic://localhost:3141')
async with client.connection() as conn:
for i in range(100):
start = asyncio.get_event_loop().time()
result, _ = await conn.query(
"MATCH (n:Person {id: $id}) RETURN n.name",
{"id": i}
)
row = result.rows[0] if result.rows else None
latency = (asyncio.get_event_loop().time() - start) * 1000
print(f"Query {i}: {latency:.2f}ms")
# Typical latencies with QUIC:
# Local: 0.5-2ms
# Same datacenter: 2-5ms
# Cross-region: 50-150ms
Concurrent Query Execution
async def concurrent_queries():
"""Execute multiple queries in parallel over QUIC streams."""
client = geode_client.open_database('quic://localhost:3141')
async with client.connection() as conn:
# All queries use the same QUIC connection but different streams
results = await asyncio.gather(
conn.execute("MATCH (p:Person) RETURN count(p)"),
conn.execute("MATCH (c:Company) RETURN count(c)"),
conn.execute("MATCH (p:Product) RETURN count(p)"),
conn.execute("MATCH ()-[r]->() RETURN count(r)")
)
for i, result in enumerate(results):
row = result.rows[0] if result.rows else None
print(f"Query {i}: {row[0]} entities")
# QUIC multiplexing allows all queries to execute concurrently
# without head-of-line blocking
Mobile Application Resilience
import asyncio
from geode_client import GeodeConnectionError, open_database
class MobileClient:
"""Database client that retries on transient network loss."""
def __init__(self, url: str):
self.client = open_database(url)
async def query(self, gql: str):
"""Execute query with a simple reconnect loop."""
while True:
try:
async with self.client.connection() as conn:
result, _ = await conn.query(gql)
return result.rows
except GeodeConnectionError:
await asyncio.sleep(0.5)
# Usage in mobile app
client = MobileClient('quic://db.example.com:3141')
# Works seamlessly across WiFi/cellular transitions
results = await client.query("MATCH (n:Person) RETURN n.name LIMIT 10")
WAN Optimization
from geode_client import ConnectionPool
async def wan_optimized_queries():
"""Optimize queries over high-latency WAN links."""
pool = ConnectionPool(host="remote-db.example.com", port=3141, max_size=20)
async with pool:
async def run_query(i: int):
async with pool.acquire() as conn:
result, _ = await conn.query(
"MATCH (n:Person {id: $id}) RETURN n",
{"id": i},
)
return result.rows
results = await asyncio.gather(*(run_query(i) for i in range(50)))
Best Practices
Connection Management
- Connection Pooling: Reuse QUIC connections for multiple queries
- Connection Limits: Configure appropriate max streams per connection
- Idle Timeout: Set reasonable idle timeout for connection reuse
- Graceful Shutdown: Close streams and connections cleanly
Performance Tuning
- Stream Concurrency: Adjust max concurrent streams for workload
- Congestion Control: Choose appropriate algorithm (Cubic vs BBR)
- 0-RTT Enablement: Enable for repeat connections to reduce latency
- Buffer Sizes: Tune send/receive buffers for throughput
Security Configuration
- Certificate Validation: Always validate server certificates
- TLS 1.3 Only: Ensure only TLS 1.3 is enabled
- Cipher Suites: Use strong, modern cipher suites
- Session Tickets: Manage session ticket security appropriately
Network Considerations
- Firewall Rules: Ensure UDP port 3141 is open
- NAT Traversal: QUIC handles NAT better than TCP
- Path MTU: QUIC performs automatic path MTU discovery
- QoS: Configure appropriate QoS for UDP traffic
Performance Considerations
Latency Improvements
QUIC reduces latency through:
- 1-RTT Handshake: 50% faster than TCP+TLS
- 0-RTT Resumption: Eliminates handshake for repeat connections
- No Head-of-Line Blocking: Packet loss doesn’t block other streams
- Fast Retransmission: Rapid packet loss recovery
Typical improvements over TCP+TLS:
- First connection: 20-30% faster
- Resumed connection: 50-70% faster
- Lossy networks: 2-5x faster query execution
Throughput Characteristics
QUIC throughput is excellent:
- Single Stream: Comparable to TCP (within 5%)
- Multiple Streams: 50-200% better than TCP due to multiplexing
- Lossy Links: 100-300% better than TCP
- High-Latency Links: BBR congestion control excels
Resource Usage
QUIC resource consumption is modest:
- Memory: ~100KB per connection (vs ~50KB for TCP+TLS)
- CPU: ~10% higher than TCP+TLS for encryption/decryption
- Bandwidth: 1-2% overhead from QUIC headers
- Ports: Single UDP port vs separate TCP ports
Troubleshooting
Connection Failures
Symptom: Unable to establish QUIC connection
Solutions:
- Verify UDP port 3141 is reachable
- Check firewall rules allow UDP traffic
- Ensure client supports QUIC (all official clients do)
- Verify server certificate is valid
- Check for NAT or middlebox interference
High Latency
Symptom: Queries slower than expected
Solutions:
- Check network latency with ping/traceroute
- Verify no packet loss on link
- Tune congestion control algorithm
- Increase max concurrent streams
- Enable 0-RTT for repeat connections
Stream Errors
Symptom: Stream reset or connection closed unexpectedly
Solutions:
- Check for max streams exceeded
- Verify proper stream lifecycle management
- Review server logs for errors
- Ensure queries complete before closing streams
- Monitor connection idle timeout
Connection Migration Issues
Symptom: Connection fails during network transition
Solutions:
- Verify connection migration is enabled
- Check path validation succeeds
- Review NAT rebinding behavior
- Ensure sufficient timeout for migration
- Monitor connection ID management
Related Topics
- Protocol - Wire protocol specification
- Networking - Network architecture and design
- TLS/Encryption - Transport security
- Performance - Performance optimization
- Client Libraries - Client implementation details
- Connections - Connection management
Further Reading
- QUIC Specification (RFC 9000) - Official IETF specification
- Wire Protocol - Protobuf wire protocol over QUIC
- Server Configuration - Server and connection configuration options
- Performance Tuning - Optimizing connections and queries
- Protocol Documentation - Geode protocol design