Software,  Engineering

Pairline’s Architecture: Real-Time Systems for a Hostile Network

Author

aless

Date Published

Most real-time products do not break because of one big failure. They break because of a thousand small ones happening at once.

A user disconnects halfway through matchmaking. A mobile network flips routes during ICE negotiation. A bad actor gets reported while still live in session. A browser keeps more session state around than it should. A background worker panics at exactly the wrong moment. None of these problems are unusual on their own. Anonymous video chat just makes all of them happen continuously.

That is the environment Pairline was built for.

Pairline is an anonymous text and video chat platform, and from day one we knew the architecture could not be treated like a normal web app. This is not a product where a request comes in, a controller runs, a database is updated, and the page renders. This is a live system. Users arrive and disappear instantly. Match state changes every second. Moderation has to act during the interaction, not after it. And the networking layer is at the mercy of carrier NATs, bad Wi-Fi, restrictive firewalls, and browser behavior we do not control.

So we made an early decision that shaped everything after it:

We were not going to build Pairline as a monolith.

We split it into two very different engines.

Phoenix and Elixir run the real-time coordination layer: websocket sessions, matchmaking, session ownership, cross-node routing, and the logic that has to react immediately when a user connects, skips, disconnects, or gets removed.

Go runs the infrastructure and control plane: WebRTC bootstrap services, TURN validation, moderation workers, admin APIs, durable writes, and the lower-level systems work that benefits from tighter resource control and cleaner background concurrency.

Redis and Valkey sit between them as the coordination fabric. PostgreSQL gives us durability. Observability ties the whole thing together.

That split was not an aesthetic choice. It was survival.

Real-time state needs a runtime built for instability

The hardest part of Pairline is not video. It is volatile state.

When someone opens the app, they expect the platform to feel instant. They do not care that thousands of other users are arriving and leaving at the same time. They do not care that queues are sharded, sessions are distributed across nodes, or that every match has to be coordinated without races. They just care that the next person appears immediately.

That is why the hot path lives in Phoenix.

Each live session gets isolated process-level handling on the BEAM. Matchmaking, presence, queue movement, ownership tracking, and live session transitions all stay inside the runtime that is best at handling enormous amounts of concurrent state change without collapsing into lock-heavy complexity.

A session connects. Phoenix creates the session, stores the locator, issues the token, tracks ownership, and puts that user into the right queue. Matchmaking is tiered, not simplistic. We first try to match on exact interests, then relax the criteria over time, then fall back to random discovery. Redis-backed Lua scripts keep pairing atomic so we do not end up with duplicate matches or stale queue claims across nodes.

That is the first principle behind Pairline:

Keep the live path narrow, fast, and extremely difficult to destabilize.

WebRTC is not a feature, it is an operating environment

A lot of teams treat WebRTC like a library decision. It is not. It is a network survival problem.

Peer-to-peer media sounds simple until real users show up from hotel Wi-Fi, carrier-grade NATs, locked-down corporate networks, and mobile connections that change shape mid-session. Then suddenly every “simple” architecture starts growing exceptions.

We designed Pairline so the relay edge is a first-class part of the system, not an afterthought.

The browser gets TURN credentials from the Go public service. The TURN relays run as dedicated Go services on host networking. Those relays do not make trust decisions by themselves and they do not read cluster state directly. Instead, they call into a gRPC control plane that validates session identity, token integrity, match state, ban state, and allocation quotas before relay resources are granted.

That boundary matters.

It keeps the TURN layer focused on moving packets. It keeps policy enforcement centralized. And it gives us a clean place to reason about abuse controls, resource limits, and session legitimacy without turning the relay tier into a distributed mess.

In practice, that discipline paid off when we had to harden connectivity under more hostile network topologies. The lesson was simple: on anonymous video platforms, WebRTC reliability is not just about having TURN. It is about how precisely you control the network boundary around TURN.

Moderation has to be part of the live control loop

Anonymous platforms do not get to treat moderation as an afterthought.

If a user is abusive and the system only records that fact for later, the platform has already failed in the moment that mattered. Real moderation has to be able to influence the session while it is still alive.

That is how Pairline is designed.

When a report comes in, the public Go service validates the session context and appends the event into a Redis Stream. That part is intentionally fast. We do not block the request on heavier work. Dedicated workers then consume the stream, persist the report, assemble evidence, and run the interaction through the moderation pipeline.

From there, the platform can do three things: reject the report, escalate it for human review, or act on it.

When the decision is actionable, it becomes durable in PostgreSQL, real-time in Redis, and immediate in Phoenix. The ban is written, the live cluster is notified, and the user can be removed from the platform while the session is still active.

That loop is the difference between having moderation infrastructure and having a moderation system.

The architecture is big because the problem is big

From the outside, anonymous chat looks deceptively lightweight. Click a button. Meet someone. Move on.

From the inside, it is a dense coordination problem spread across live sockets, distributed queues, relay infrastructure, anti-abuse logic, cluster routing, worker safety, memory cleanup, and observability. If any of those systems are vague, they eventually fail in production in ways users experience as “this app feels broken.”

So yes, the Pairline architecture is large.

Phoenix nodes manage live session state and matchmaking. Go public services handle signaling support, TURN bootstrap, moderation ingestion, and shared control-plane APIs. Go workers process reports and execute automation. Go TURN relays sit at the media edge. Redis and Valkey coordinate ephemeral state. PostgreSQL stores what must survive. Nginx fronts the cluster. OpenTelemetry, Prometheus, Jaeger, and Grafana make the behavior visible enough to operate.

Every one of those pieces exists because we hit a real problem that demanded a real boundary.

We did not end up with this architecture by chasing complexity. We got here by removing ambiguity.

Phoenix owns immediacy.
Go owns infrastructure and asynchronous control.
Redis coordinates the space between them.
PostgreSQL keeps the platform honest.
Observability keeps us from guessing.

That is the system.

What Pairline really is

Pairline is not just a chat app. It is a real-time system designed for adversarial conditions.

It has to feel instant even when users are volatile. It has to keep video working across unreliable networks. It has to absorb failure without taking down the live path. And it has to enforce safety in real time, not eventually.

That is why we built it the way we did.

Not as one backend trying to be everything.
Not as a loose pile of services without a center.
But as a set of hard boundaries around the parts of the system that matter most.

That is what lets the product feel simple.

Because underneath anonymous chat is chaos.
And good architecture is what keeps chaos from reaching the user.


We Open Sourced Pairline, it is available on GitHub