Architecture

System design, plugin pipeline, request context, embedded data, and concurrency model.

Architecture

System overview

┌─────────────────────────────────────────────────────────┐
│                    QUAYEL GATEWAY                        │
│                  (Single Rust Binary)                    │
│                                                         │
│  ┌─────────────────────────────────────────────────┐   │
│  │              Pingora HTTP Proxy                  │   │
│  │  (Connection handling, TLS, HTTP/1.1, H2)       │   │
│  └──────────────────────┬──────────────────────────┘   │
│                         │                               │
│                         ▼                               │
│  ┌─────────────────────────────────────────────────┐   │
│  │           Request Context (per-request)          │   │
│  │  client_ip, headers, path, method, scheme, ...   │   │
│  └──────────────────────┬──────────────────────────┘   │
│                         │                               │
│                         ▼                               │
│  ┌─────────────────────────────────────────────────┐   │
│  │             PLUGIN PIPELINE (fixed order)        │   │
│  │                                                  │   │
│  │  1. IP Intelligence   ─→  country, ASN, VPN,     │   │
│  │                            Tor, DC, risk score   │   │
│  │                                                  │   │
│  │  2. Firewall          ─→  allow/block/redirect   │   │
│  │                                                  │   │
│  │  3. Rate Limiting     ─→  throttle/block         │   │
│  │                                                  │   │
│  │  4. Load Balancer     ─→  select upstream target  │   │
│  │                                                  │   │
│  │  5. Cache             ─→  HIT/MISS/REVALIDATING/STALE/BYPASS  │
│  └──────────────────────┬──────────────────────────┘   │
│                         │                               │
│                         ▼                               │
│  ┌─────────────────────────────────────────────────┐   │
│  │           Upstream Proxy → Backend Servers       │   │
│  └──────────────────────┬──────────────────────────┘   │
│                         │                               │
│                         ▼                               │
│  ┌─────────────────────────────────────────────────┐   │
│  │        Response Filter + Logging (JSONL)         │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  ┌─────────────────────────────────────────────────┐   │
│  │        EMBEDDED DATA (140 MB in binary)          │   │
│  │  MMDB: city, ASN, country                        │   │
│  │  CIDR: VPN, Tor, Cloudflare, proxy               │   │
│  │  ASN:  provider registry, hosting/abuse ASNs     │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

Plugin pipeline

Every request flows through the pipeline in this exact order:

Request In
    │
    ▼
┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│  IP Intel    │───→│  Firewall    │───→│ Rate Limit   │
│              │    │              │    │              │
│ geo, ASN,    │    │ allow/block/ │    │ throttle/    │
│ VPN, Tor, DC │    │ redirect     │    │ 429          │
└──────┬───────┘    └──────┬───────┘    └──────┬───────┘
       │                   │                   │
       │   Any plugin can return Respond ──────┘
       │   (short-circuits remaining plugins)
       ▼
┌──────────────┐    ┌──────────────┐
│Load Balancer │───→│    Cache     │───→ Upstream
│              │    │              │
│ select target│    │ HIT → respond│
│ round-robin  │    │ MISS → fetch │
│              │    │ REVALIDATING │
│              │    │ → serve stale│
│              │    │ → first req  │
│              │    │   fetches    │
└──────────────┘    └──────────────┘
                            │
                            ▼
                    ┌──────────────┐
                    │   Logging    │
                    │   (JSONL)    │
                    └──────────────┘

Short-circuit behavior

PluginCan short-circuit?When?
IP IntelNoAlways continues
FirewallYesBlock, redirect, or set-header action
Rate LimitYesLimit exceeded → 429
CacheYesCache HIT → serve cached response; REVALIDATING/STALE → serve stale cached data
Load BalancerYesNo healthy upstream → 503

Request context

One RequestContext is created per request. All plugins read and write to the same context:

struct RequestContext {
    // Identity
    gateway_id, gateway_name, server_name, config_version,
    request_id: UUID v4,

    // Client
    peer_ip,          // Direct connection IP
    client_ip,        // Resolved real client IP
    client_ip_source, // How client_ip was determined

    // HTTP
    host, path, method, scheme, user_agent,
    headers: HashMap<String, String>,

    // IP Intelligence (populated by ip_intel plugin)
    ip_intel: IpIntel,

    // Plugin results
    firewall: FirewallResult,
    rate_limit: RateLimitResult,
    cache: CacheResult,
    load_balancer: LoadBalancerResult,

    // Upstream
    upstream_name, upstream_target, upstream_protocol,

    // Response / cache capture
    status_code: Option<u16>,
    response_headers_captured: Option<Vec<(String, String)>>,
    response_body_buffer: Vec<u8>,
    cache_should_store: bool,              // Signal to store response in cache
    revalidation_guard: Option<RevalidationGuard>, // Per-key lock for stale revalidation

    // Bandwidth
    client_bytes_in, client_bytes_out,
    upstream_bytes_in, upstream_bytes_out,

    // Timing (microseconds)
    ip_intel_latency_us, firewall_latency_us,
    rate_limit_latency_us, cache_lookup_latency_us,
    lb_latency_us, upstream_connect_latency_us,
    upstream_ttfb_us, total_latency_us,
}

RevalidationGuard

The RevalidationGuard holds a per-key OwnedMutexGuard<()> for the duration of a stale cache refresh request. When the request completes and ctx is dropped, the guard drops → lock releases → concurrent requests see fresh cache data.

Embedded data architecture

All IP intelligence data is compiled into the binary at build time using Rust's include_bytes!() and include_str!() macros. This means:

  • Zero network requests on startup
  • Zero disk I/O for IP intel lookups
  • Deterministic startup — no download failures, no timeouts
  • ~140 MB added to binary size (3 MMDB files + 4 CIDR lists + 2 ASN lists)

Data files in src/data/

FileSizeFormatPurpose
dbip-city-lite.mmdb122 MBMMDBCountry, region, city, lat/lon
dbip-asn-lite.mmdb9.2 MBMMDBASN number + organization
dbip-country.mmdb8.0 MBMMDBCountry-only fallback
provider-asns.txt~1.5 KBASN registryASN|Label provider map (e.g. 47583|Hostinger)
vpn.txt177 KBCIDR list11K VPN provider ranges
tor.txt19 KBCIDR list1.3K Tor exit node IPs
hosting-asns.txt4.3 KBASN listBad/hosting ASN numbers
cloudflare.txt336 BCIDR listCloudflare edge ranges
proxy.txt0 BCIDR listFireHOL anonymous (empty)
See Data Sources for complete dataset documentation, source URLs, and update instructions.

Update process

# 1. Copy fresh data files
cp /path/to/new/dbip-city-lite.mmdb src/data/
cp /path/to/new/dbip-asn-lite.mmdb src/data/
cp /path/to/new/vpn.txt src/data/

# 2. Rebuild
cargo build --release
strip target/release/quayel-gateway

# 3. Deploy
systemctl restart quayel-gateway

See Data Sources — Updating Embedded Data for detailed instructions.

Concurrency model

  • Pingora handles connection management and HTTP parsing on Tokio
  • Plugin pipeline is async but most operations are CPU-bound (CIDR lookups, MMDB queries)
  • LRU cache uses parking_lot::RwLock for concurrent read access
  • DashMap for rate limit counters and cache store (lock-free sharded HashMap)
  • ArcSwap for hot-reloading config without restarts
  • Per-key Mutex for cache revalidation singleflight (prevents stampedes)

Memory layout

ComponentSizeNotes
Binary (code)~13 MBStripped release
Embedded data~140 MBMMDB + CIDR lists
LRU cache~13 MB65K entries × ~200 bytes
Rate limit counters~1 MBGrows with unique keys
Cache storeVariableBounded by max_object_size
Total RSS~29 MBAt startup
Copyright © 2026