← All work
Photon to Elixir
Real-time room and file sync moved off a busy Node server onto Phoenix Channels.
A portal kept room lists and per-room file lists in sync with Photon. An Express server held the snapshot in memory and pushed changes to browsers over server-sent events.
Every user held two long-lived streams for the room lists, plus one per open room. That traffic shared the same Node process as the rest of the API, and slowed it down.
Decisions
01
A dedicated Elixir service
- Chose
- Move sync into Elixir, with Phoenix Channels to the browsers.
- Instead of
- Scaling the Express server or adding more SSE workers.
- Why
- Channels are built for many long-lived connections, and the BEAM keeps them cheap and isolated from the rest of the API.
02
Our own Photon client
- Chose
- Write a Photon client in Elixir, using the JavaScript SDK as the reference.
- Instead of
- Keeping the JavaScript SDK and bridging to it from Node.
- Why
- It removed Node from the sync path completely, so the protocol and the fan-out live in one runtime.
03
Hot state in ETS
- Chose
- Keep inactive rooms, active rooms and file lists in ETS tables.
- Instead of
- Reading MongoDB on every push.
- Why
- Pushes read memory, not the database. MongoDB stays the durable copy.
04
One codebase, two clouds
- Chose
- Hide cloud differences behind adapters.
- Instead of
- A fork of the service per cloud.
- Why
- The same build runs in five environments across AWS and GCP.
What shipped
- Sync no longer competes with the API for the Node event loop.
- Channel topics enforce the same access rules the SSE endpoints had.
- Repeatable Docker-based releases across five environments on two clouds.
Built with
Elixir, Phoenix Channels, ETS, Photon, MongoDB, Docker Compose, AWS, GCP
Some details are kept general on purpose.





