arcyfelix

arcyfelix

Robust processing architecture to monitor a very high number of Nerves devices

Hello lovely people of the Elixir community!

I’m working on a system to monitor Nerves devices to ensure they maintain constant internet connectivity. I’d love to get your thoughts on the architecture I’m considering.

The Problem

I need to:

  • Monitor a very high number of Nerves devices (potentially thousands or tens of thousands)
  • Ensure each device maintains constant internet connectivity
  • Use WebSocket connections with periodic ping messages to detect connectivity issues
  • Handle the scale efficiently without overwhelming the system
  • Ensuring that none of the messages were lost is paramount.

Proposed Architecture

I’m thinking of using Kafka as a queueing buffer to decouple the WebSocket connection handling from the actual processing and storage. This would allow for a more robust and flexible architecture that can handle bursts and scale horizontally.

Here’s the architecture I’m envisioning:

┌─────────────┐
│ Nerves      │
│ Device      │
└──────┬──────┘
       │ WebSocket Connection
       │ (ping messages)
       ▼
┌─────────────────────┐
│ Phoenix             │
│ WebSocket Handler   │
│ (Phoenix Channels)  │
└──────┬──────────────┘
       │ publish_heartbeat()
       ▼
┌─────────────────────┐
│  Kafka Producer     │
│  (kafka_ex)         │
└──────┬──────────────┘
       │
       ▼
┌─────────────────────┐
│   Kafka Topic       │
│   (device_heartbeat)│
│   ┌───────────────┐ │
│   │  Partition 0  │ │
│   │  Partition 1  │ │
│   │  Partition N  │ │
│   └───────────────┘ │
└──────┬──────────────┘
       │
       ▼
┌─────────────────────┐
│ Broadway Consumer   │
│ (broadway_kafka)    │
│  - Processors       │
│  - Batchers         │
└──────┬──────────────┘
       │ batch_insert()
       │ detect_offline()
       ▼
┌─────────────────────┐
│   PostgreSQL        │
│   (device_status)   │
└─────────────────────┘

The Flow

  1. Nerves devices establish persistent WebSocket connections to a Phoenix application
  2. Devices send periodic ping/heartbeat messages (e.g., every 30 seconds)
  3. Phoenix Channels receive these messages and immediately publish them to Kafka (async, non-blocking)
  4. Kafka acts as a buffer, handling bursts and providing durability
  5. Broadway consumers process heartbeats in batches, updating device status in PostgreSQL
  6. Missing heartbeats can be detected by checking timestamps in the database

Questions for the Community

  1. Is Kafka overkill for this use case? Given that I’m dealing with potentially tens of thousands of devices, I thought Kafka would help with:

    • Handling bursts of heartbeat messages (f.e. after blackout reconnecting rush)
    • Decoupling WebSocket handling from processing
    • Horizontal scaling of consumers
    • Durability and replay capabilities

    But I’m wondering if there’s a simpler approach that would work just as well?
    Just Oban?

  2. WebSocket connection management: For this scale, should I:

    • Use Phoenix Channels with a single topic per device?
    • Use a single topic with device IDs in the message payload?
    • Consider connection pooling or other patterns?
  3. Partitioning strategy: If I go with Kafka, how should I partition the device_heartbeat topic?

    • By device ID (ensures ordering per device)?
    • Event type?
    • Round-robin (better load distribution)?
    • Other ideas?
  4. Offline detection: What’s the best approach for detecting when a device goes offline?

    • Timeout-based checks in the consumer?
    • Prescence lifecycle?
    • Use Kafka’s consumer lag or other mechanisms?
  5. Alternative architectures: Are there other patterns or libraries in the Elixir ecosystem that would be better suited for this use case? I’ve seen mentions of:

    • Direct database writes from Phoenix (simpler but less resilient)
    • GenStage/Flow for processing (lighter than Kafka)
    • “Just” Oban?
    • Other message queue systems
  6. Performance considerations: For this scale, what should I be most concerned about?

    • WebSocket connection limits per Phoenix node?
    • Kafka producer throughput?
    • Database write performance?
    • Memory usage for connection state?

Context

I already have Kafka infrastructure set up in my project (using kafka_ex and broadway_kafka), so adding this wouldn’t require new infrastructure. However, I’m open to simpler solutions if they’re more appropriate.

Thank you for your input​:folded_hands:

Most Liked

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Oh!

Yes that changes things; Kafka is a perfectly reasonable choice at that point. I do want to recommend clickhouse again though for storage, we’ve started integrating it into our stack about a year ago and the compression + query performance is tremendous. There’s definitely a learning curve in that you’re being handed sort of a high powered “data structure as a database” tool but it’s been a delight to use.

On the Websocket front though that still sounds quite doable on a node or 2.

LostKobrakai

LostKobrakai

Phoenix channels already have a heartbeat to disconnect unresponsive clients. You shouldn’t really need to duplicate that one layer higher. The channel really doesn’t matter for as long as you’re not broadcasting any messages on the channel – a topic by itself is just a string key. It’ll only consume as much performance as there’s connected clients. With separate topics you however reduce the risk of accidentally broadcasting to every device.

You could consider just a bare websocket connection using WebSock if you want to go lower level, or even just bare tcp, but if there’s other channels there’s not much a benefit in that.

Generally I’d consider if you really need to track connectivity globally and by sending/tracking an event from the device itself all the way up to the db. If you can go with per node tracking of connected devices you could e.g. use a per node Registry and every x seconds batch write to the db which devices (Registry.select) are connected to that node. That’s one write (or a handful) per node per 30 seconds. In terms of db load that should be quite fine. In the db you could then roll the data up to report on connectivity. If you do not trust the phoenix heartbeat you could always make the channel process on the server shut down / unregister if there hasn’t been a ping within a provided window.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Even at 100 messages a second kafka is way overkill. You’re still easily in Postgres territory, perhaps with some offloading of long term timeseries data storage to something like clickhouse. If you want to use some sort of queue / buffer then I’d still be reaching for like SQS or Google Cloud PubSub instead of the headache that can be managing kafka. Just shoving each payload into Oban through completely fine, we run thousands of jobs per second through Oban just fine.

Regarding managing the devices again unless I’m missing something 10k connections is well within the bounds of even a single normal sized phoenix node.

Where Next?

Popular in Questions Top

aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
vac
Hi, I’m quite new in Elixir and I’m trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and I...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

Other popular topics Top

axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 48342 226
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29603 241
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New

We're in Beta

About us Mission Statement