acco

acco

Sequin - Elixir and Postgres for a feature-rich message queue

Hey everyone,

Excited to share a new open-source project built with Elixir. Sequin combines Elixir and Postgres into a feature-rich message queue.

We (the maintainers) were searching for the goldilocks message stream/queue. Kafka’s great when you need tremendous scale, but the trade-off is that it has few features and is hard to debug/configure/operate. SQS also has limited features, and we don’t like the dev/test story. RabbitMQ was closest to what we were seeking, but was the most unfamiliar to the team, and looked like it would be a black box.

Databases and messaging systems are at the heart of most applications. But having both adds complexity. We already know and love Postgres – can’t it do both?

Teams pass on using Postgres for streaming/queuing use cases for two primary reasons:

  1. They underestimate Postgres’ ability to scale to their requirements/needs
  2. Building a stream/queue on Postgres is DIY/open-ended

Getting DIY right requires some diligence to build a performant system that doesn’t drop messages (MVCC can work against you). We also wanted a ton of features from other streams/queues that weren’t available off-the-shelf, namely:

  • message routing with subjects
  • a CLI with observability
  • webhook and WAL ingestion (more on that in a bit)

So, we built something that leans into Postgres’ strengths. We love the stream-based model, where messages persist in one or more streams. Each of those streams have their own retention policies (like Kafka). And then you can create consumers that process a subset of messages based on filters exactly-once.

We didn’t like the idea of a Postgres extension. We wanted something we could use with any existing hosted Postgres database. And the Elixir layer gives us a lot of performance benefits. We use Elixir to do stuff that Postgres is bad at, like cache and keep counters.

Plus, who doesn’t want to build in Elixir as much as they can :slight_smile:

Because it’s all Just Postgres™, observability comes out-of-the-box. But we love a good CLI, so we’ve already built out a lot there to make debugging/observing easy (sequin observe is like htop for your messages).

We think killer use-cases for a Postgres-based stream are (1) processing Postgres WAL data and (2) ingesting webhooks.

For (1), we built on the shoulders of giants (h/t Postgrex, Realtime, Cainophile). It’s really neat to be able to process the WAL as a message queue!

For (2), we’re planning a way to expose HTTP endpoints so you can go from API → Sequin endpoint → Postgres → your app.

Under the hood, Sequin uses a very simple and familiar schema for the stream. We tried a lot of fancy stuff, but the simplest route turned out to be the most performant.

Messages flow into the messages table (partitioned by the stream_id). In the same transaction, messages are fanned out to each consumer that’s filtering for that message (consumer_messages).

In terms of performance, this means Sequin can ingest messages about as fast as Postgres can insert them. On the read side, consuming messages involves “claiming” rows in consumer_messages by marking them as delivered, then later deleting those rows on ack.

We benched on a db.m5.xlarge (4-core, 16GB RAM, $260/mo) RDS instance and the system was happy at 5k messages/sec, bursting up to 10k messages/sec. Your laptop is beefier than this, obviously bigger machines can do more.

We still have a lot to build. It’s pre-1.0. And we’re curious if the model of “combine a stateless docker container (Elixir) with your existing Postgres db” will resonate with people.

You can see an example of using Sequin with Broadway here:

https://github.com/sequinstream/sequin/tree/main/examples/elixir_broadway

We’re looking forward to feedback and are happy to shape the roadmap according to your real-world needs! Leave a comment or send me a DM if there’s anything you’d like to see

https://github.com/sequinstream/sequin

Most Liked

acco

acco

Hey everyone –

A few months ago, we announced Sequin, an open-source project that combined Elixir and Postgres for message queuing. Since then, we’ve made a big pivot in our focus, and I’m excited to share where we’re headed.

https://github.com/sequinstream/sequin

v0.6 – CDC

While we started with a broader focus of being a full-featured message queue, we’ve learned the real challenge is Change Data Capture (CDC). That’s what we’re focusing on now: Sequin is a lightweight, Elixir-powered alternative to Debezium.

With Sequin, you can stream Postgres rows and changes to message queues like SQS or GCP Pub/Sub and streams like Kafka. You can start with a backfill of current data in your Postgres table(s), then continue to stream changes in real-time.

If you don’t have a queue/stream, we also support webhook destinations and offer our own native stream, Sequin Stream. Both guarantee exactly-once processing.

Why the change?

Most of our early users came to us specifically for our WAL processing capabilities – they already had their data in Postgres and needed a reliable way to capture changes.

While tools like Debezium exist for this use case, they’re complex to set up and maintain. Debezium requires Kafka and has limited tooling for monitoring and debugging. By focusing solely on Postgres, we’ve built a simpler solution that can match Debezium’s performance without the operational overhead.

What’s new in v0.6?

  • We’ve refined our core CDC functionality and now support native destinations for message queues and streams.

  • We’ve introduced sequin.yaml for declarative configuration (think lightweight Terraform).

  • Added “Change Retention” which uses a Postgres table to retain WAL changes for a period of time. This lets you replay changes to a destination, which is super useful for debugging or error recovery.

  • Added “backfills” for streaming data already in a Postgres table to a destination.

  • Improved our own native stream, Sequin Stream. It has a HTTP-based consume interface a lot like SQS, but it’s Postgres-based.

  • Improved support for very large transactions, large rows, and TOAST columns.

  • Various performance and stability improvements.

How it works

We’re still building on the same foundation:

  • Elixir implementation (no Postgres extensions needed)

  • Works with any hosted Postgres database

  • Web console and CLI

  • Exactly-once processing guarantees

(The web console is actually LiveView + LiveSvelte, which we’ve written about here.)

Looking forward

Upcoming features we’re most excited about are transforms (write your own in Elixir/JS) and joins (combine multiple tables into one object).

Try It Out

If you’re looking for a lightweight, Elixir-native way to handle CDC from Postgres, try Sequin.

Check out the repo: GitHub - sequinstream/sequin: Postgres change data capture to streams, queues, and search indexes like Kafka, SQS, Elasticsearch, HTTP endpoints, and more · GitHub

Check out one of our quickstarts: Get started with webhooks - Sequin

Or, try it out in the cloud: https://console.sequinstream.com/

We’re excited about this new direction and would love your feedback. Whether you’re using CDC today, have used Debezium in the past, or are just exploring CDC, your thoughts and requirements are invaluable for us to hear. Feel free to comment here or send a DM.

17
Post #9
acco

acco

Hey everyone,

Wanted to share all the updates we’ve made to Sequin.

We made a significant overhaul to Sequin in v0.4. Before, you’d push messages to Sequin streams via HTTP. Streams were persisted in Sequin-managed Postgres tables.

As of v0.4, Sequin instead streams your existing Postgres tables.

You can use Sequin to add streaming capabilities to existing data, like you might with a Debezium + Kafka pipeline. Or you can create new tables that you’ll use specifically to power streaming use cases as a pure Kafka alternative.

After connecting a Postgres table to Sequin, you can setup consumers for that table. A consumer can start at any “position” in the table: the beginning, at a specific row, or at the end. If it’s a push consumer, Sequin will push changes to your application via HTTP POST. If it’s a pull consumer, Sequin will provision a new HTTP endpoint that your application’s workers will use to pull messages.

You can have 1 or 1000 workers subscribe to a consumer. And you get guarantees like exactly-once processing of all rows and changes.

So, a table in Sequin is like a topic in Kafka. A consumer is like a Kafka consumer group. And instead of offsets, consumers traverse tables using a sorted column on your table.

Like Kafka, Sequin also delivers changes FIFO. So if a Postgres row changes multiple times in quick succession, each change will only be delivered after the prior change was acknowledged, avoiding race conditions.

So you can think of Sequin as an Elixir app that sits on top of your Postgres database and does 3 things:

  1. Safely paginates and detects changes to your Postgres tables.

  2. Manages consumer state (FIFO, fan-out, delivery, etc).

  3. Offers a control and observability plane on top of all this.

Our bet is that for many (most?) teams and use cases, a Postgres table is a better storage layer than Kafka. You’re already operating Postgres. And data in Postgres is far easier to manage: you can migrate tables to change your data model and easily read it at rest with SQL. Your data is typically already in Postgres – why have a longterm copy in another system just to stream it?

Postgres also allows us to more easily build a ton of useful features, like observability tooling, replays, SQL-based filters on consumers, etc.

While you write directly to your Postgres tables with insert/update/delete, the consumer interface is via HTTP (push or pull). There are a lot of moving parts with message delivery, and we lean on Elixir/OTP to make this smooth.

You can follow along on GitHub, we open source everything: GitHub - sequinstream/sequin: Postgres change data capture to streams, queues, and search indexes like Kafka, SQS, Elasticsearch, HTTP endpoints, and more · GitHub

We have an example with an Elixir+Broadway consumer, which is a great experience:

We also just launched our hosted option, which is a good way to kick the tires.

Finally, I recorded a video yesterday that shows how all this comes together:

Much more to come – let me know if you have any thoughts or questions! We’re adding features all the time, so if we’re missing something, we’d love to hear it :slight_smile:

15
Post #8
acco

acco

Hey all –

Excited to share that we released an Elixir client for Sequin on Hex!

Sequin’s HTTP interface is straightforward enough, but the SDK is a nice wrapper for those that prefer. It’s a good fit if you don’t need a full-blown Broadway producer/pipeline. And you can use the SDK in test to create/delete streams and consumers.

Here’s an example:

# Define your stream and consumer
stream = "your-stream-name"
consumer = "your-consumer-name"

# Send a message
case Sequin.send_message(stream, "test.1", "Hello, Sequin!") do
{:ok, %{published: 1}} ->
  IO.puts("Message sent successfully")

  {:error, error} ->
    IO.puts("Error sending message: #{Exception.message(error)}")
end

# Receive a message
with {:ok, %{message: message, ack_id: ack_id}} <- Sequin.receive_message(stream, consumer),
     :ok <- YourApp.process_message(message),
     :ok <- Sequin.ack_message(stream, consumer, ack_id) do
  IO.puts("Received and acked message: #{inspect(message)}")
else
  {:ok, nil} ->
    IO.puts("No messages available")

  {:error, error} ->
    IO.puts("Error: #{Exception.message(error)}")
end

Let me know if you have any thoughts or questions :pray:

Last Post!

krishna_vaguelyright

krishna_vaguelyright

This is super interesting. I am new to the world of messaging & have previously done mostly CRUD apps with REST APIs. I want to understand the whole world of queues, CDCs etc…

I am building backend for a Chat app & have realised I need messaging queuing for proper guarantees to make sure no messages are lost. From what I read here I believe Sequin should be the best fit for us coz we are at MVP level & not thinking about crazy scale so dont want to get into Kafka, RabbitMQ, etc… at this point…

Can you link me to some resources which can explain the world to me, whatever I read on internet & watched on YouTube is mostly about implementation or confusing…

Its a offline first app where the messages are stored at the client side. I just need to make sure the messages reach the respective clients in a fail-safe manner, esp when they connect back after being offline…

Many Thanks :slight_smile:

Where Next?

Popular in Announcing Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36689 110
New
mindok
What is ContEx? A pure Elixir server-side data plotting/charting library outputting SVG. It has nice barcharts in particular and works g...
New
mathieuprog
Hello :waving_hand: Allow me to introduce you to Tz, an alternative time zone database support to Tzdata. Why another library? First a...
New
benlime
LiveMotion enables high performance animations declared on the server and run on the client. As a follow up to my previous thread A libr...
New
type1fool
WebAuthnLiveComponent WebAuthnComponents See this post about renaming the package. Passwordless authentication for Phoenix LiveView app...
New
zachdaniel
Ash Framework What is Ash? Ash Framework is a declarative, resource-oriented application development framework for Elixir. A resource can...
New
zoltanszogyenyi
Hey everyone :waving_hand: Excited to join this forum - I am one of the founders and current project maintainers of a popular and open-s...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 130579 1222
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 54996 245
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New

We're in Beta

About us Mission Statement