Architecture
bytetourist is four small Go services around a handful of stateful dependencies. The design rule: keep the data plane thin and direct, push everything else off the hot path.
Services
Section titled “Services”| Service | Responsibility | Not responsible for |
|---|---|---|
| core | Auth, plan limits, rate limiting, matching, maintaining gRPC streams to nodes, returning responses | Analytics storage, scaling |
| edge | Execute the outbound HTTP call, return response + metadata | Caching, retries, routing |
| scaler | Ingest events, write ClickHouse, evaluate rules, provision/recycle nodes | Request routing |
| server | Dashboard API, Auth0, Postgres tenancy CRUD, billing rollups | Request serving |
Dependencies
Section titled “Dependencies”- etcd — node membership. Nodes self-register with a lease; core/scaler watch the membership prefix.
- Redis — the broker: pub/sub and atomic in-flight rate-limit counters.
- Kafka — the immutable
request-eventslog (analytics/billing only — never on the routing path). - ClickHouse — request-event storage and aggregation.
- Postgres — orgs, users, API keys, plans, edges.
Request flow
Section titled “Request flow”Client ──HTTP proxy──▶ core ├─ authenticate (API key → org) ├─ clamp to plan (regions, ip_type, concurrency) ├─ rate-limit check (Redis INCR) ├─ match: filter fleet → strategy → one node └─ send ProxyRequest over the node's gRPC stream │ ▼ edge node ├─ execute HTTP to target └─ stream ProxyResponse back (+ latency, bytes) │ ▼core ── deliver to the waiting request ── publish event to Kafka (async) └─ decrement in-flight (Redis DECR) ── return response to clientThe hot path is client → core → node → target, a direct gRPC bidi stream with no broker in the middle. Analytics is strictly off to the side.
How nodes connect
Section titled “How nodes connect”Most nodes are reached over a persistent gRPC stream. Nodes that can’t be dialled into — sandboxes, mobile devices behind NAT — instead dial the core and register over a reverse-connect gateway, then receive requests back down the same stream. Either way they appear as matchable nodes.
Scaling model
Section titled “Scaling model”- core is a stateless deployment; every core connects to every node, so any core can serve any request.
- scaler runs separately and owns metrics + scaling decisions.
- edge nodes scale autonomously — the scaler provisions them and they self-register in etcd.
See Autoscaling for the scaling rules and Self-hosting to run it all locally.