garrison

garrison

Postgres and guaranteed orderings - what is the correct way to do this?

Moderator note: This was split from Elixir-postgresql-message-queue - Pure PostgreSQL Message Queue for Elixir

What is the correct way to do this with Postgres? Lock a “counter” row and live with no concurrency?

Are there tricks you can do with LSNs or the WAL?

Most Liked

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Hey folks, I have split this out from the post in the libraries section. I think this is a great discussion but as it is mostly focused on the abstract challenges of these sorts of systems and isn’t about the specific library posted in the other thread, I felt it important to split it out.

LostKobrakai

LostKobrakai

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

A big disconnect here is about whether you are ordering a set after the fact vs maintaining a cursor. If you have a set of messages and some error, then sure you can look at the set and say “this order is the order I will say it’s in”

With a cursor though that’s totally different. You are saying “I have seen messages up to time X”. If a message is created with a time before X you’re screwed since you will never see it at all.

EDIT: and to be clear I understand the OP to be focused more on this “systemic messaging” type system not “user messaging”. He’s making comparisons to RabbitMQ and such, where the goal is to have systems work together by sending messages and handling them. It’s critical to not miss messages, and it’s also generally critical to offer some means of guaranteeing order (perhaps scoped, perhaps global). These requirements can be difficult to satisfy jointly in Postgres without sacrificing a fair bit of performance.

Last Post!

garrison

garrison

To answer more generally, from a theory perspective an event log and a “database” are equivalent as discussed earlier.

But in practice performance matters very much. Storing all of your events in an immutable log (i.e. event sourcing in the limit) means that reading state is extremely expensive. It also means that nothing is ever deleted, which is a problem in practice.

There are “log-structured” storage engines which are designed to soften the blow, like an LSM tree. LSMs make tradeoffs where they keep keys sorted (for faster binary search) but they are split into separate “files” which are immutable and then merged later. The merge also ensures old entries are (eventually) deleted. But once you’re using an LSM you have already departed the “log” abstraction for a K/V abstraction, the storage is just vaguely log-structured.

But the thing is, order of keys matters a lot for many use-cases too. The log-structured runs all have to be scanned (and sometimes they overlap), so range queries (and especially range deletes) are expensive. For point lookups you can use bloom filters, but for range lookups it is not so simple.

So what I’m getting at is you want a distributed log/wal for correctness because it makes replication easier, but you want your actual data materialized into a format which is useful for lookups like an LSM or, even better, a btree. That is exactly what FoundationDB does.

The WAL is used for replication, and crucially it can be truncated during recovery if a node fails while a transaction is only partially replicated. Then the storage engines can pull data from the log and put them into an ordered (btree) format for performant queries.

The advantage of this log approach becomes more clear the deeper you get. For example, how do you “move” a shard of data from one set of servers to another while serving fully consistent reads and writes without any unavailability?

If you already have a 100% consistent and durable log (the hard part), it’s actually rather simple: you pull the keys in chunks from one storage engine to the other and then replay the log over the new shard as you go. If the log operations are idempotent (true for simple sets/clears) then this is correct. What’s critical about this algorithm is that nothing is blocked for very long, as it can all be done incrementally. And since everything is properly versioned by the transaction system, an inconsistent snapshot is never observed by any client.

It goes deeper, though! How does a client know to read from the old shard until exactly the moment that the new shard is ready, and then switch over? Any inconsistency here would break the correctness guarantees. Well, it turns out it’s (relatively) simple: the “changeover” is part of the database, and therefore part of the log.

FDB really is a beautiful database.

Where Next?

Popular in Questions Top

rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
Lily
In templates/appointment/index.html.eex: <%= for appointment <- @appointments do %> <tr> <td><%= appoi...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
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
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

Other popular topics Top

electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
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
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
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New

We're in Beta

About us Mission Statement