Book review Patterns of Distributed Systems which ones NunDB uses

In software Naming things is hard, and a pattern is giving a problem a name, I love the quote from the book, “A Pattern Language” “Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice.” Christopher Alexander.

The new generation of programmers seems not to like software patterns, or Design Patterns for that matter, maybe because they are related to object orientation or bureaucracy. Yet, there is great value when we can all agree on a name for a problem, that means when I say let’s implement “Heartbeat” on this system, I don’t need to explain the entire problem before everyone in the conversation is thinking about the same thing, and it improves communication a lot.

Reading this book was great and gave me the opportunity to think about the solutions I implemented for the problems I found on the way while implementing NunDB. At first, I thought that there would be little to no match to my implementations, but I could find many of them in my code base: Segmented log, Leader and Follower, Replicated Log, Idempotent Receiver, and others. And a few that I am planning to implement for a while and now I have a name to give them, like “Majority Quorum” for writes.

The rest of this post may follow a different standard and are more like comments and bookmarks of where or how NunDB implemented the pattern, so there is no need to read them in order you can read the ones that may get your attention.

Segmented log (Chapter 4)

NunDB implements segmented log to divide oplog into 10 smaller files, making it simpler to manipulate and manage files. Also improving issues with disk space and performance. Here is where we implemented it.

Leader and Follower (Chapter 6)

NunDB implements leader election to ensure writes goes to a single central node, called primary. Here is the implementation. though NunDB started as a single primary node, there has been implementations to move it to a multi-primary database.

  • Having multiple primaries will allow it to scale it horizontally in reads and writes.

The idea is to have one leader per shard? Or per database or key? This is yet to be defined. Or not to have leader at all the deal with conflicts just like our arbiter feature.

Heartbeat (Chapter 7)

  • NunDB still not implemented this but it is on the plan, today we failed a replication connection from the replicas and it would be easy to implement it.

Today, we expect an event to try to access the other node. Only then will we disconnect it. It is being a while I want to implement this feature in NunDB so today I decided to create an issue to implement it, I will probably soon be working on it.

Majority Quorum (Chapter 8)

  • NunDB does not implement majority quorum so to speak, we do have something pretty close Here, where we only remove the transaction from the pending list when it has been acknowledged by all replicas. This can be used to create similar logic to majority quorum transactions.

All the consensus algorithms are based on the majority quorum principle Raft, Zab, and Paxos.

Replicated Log (Chapter 12)

NunDB uses in it Here. This is a very common patter, we implemented it based on learning from databases like MongoDB but also using minor optimizations focused on performance. Worth reading the post about the implementation in A fast-to-sync/search and space-optimized replication algorithm written in rust, The Nun-db data replication model

Single Update Queue (Chapter 13)

NunDB uses with a single thread to replicate and one thread for each node coded here. Here we guarantee that there is only one thread per node using channels so many thread can send messages and only one thread can process them.

Idempotent Receiver (Chapter 15)

NunDB uses idempotent receiver in the acknowledged operation from replica sets. When sending back an ack message to the primary, the primary stores the state of the operations and will only change the count once, even if the server sends the message multiple times.

We could also implement this to any client, since we hold an instance of each client. One interesting aspect of this implementation is that if an server sends a message with an id all smaller ids can be ignored as if the newer id means the client is Ok with the previous messages (this is very important to performance and save memory, today we store all ids in memory to ensure it is safe).

Follower reads (Chapter 16)

NunDB implements this by allowing clients to read from any node but only writing to the Primary node. This is by design, we guarantee that if the client watches a key, it will eventually receive the last value state of the key.

Version Value (Chapter 17)

Implemented here. In NunDB we use value version to detect conflicts between nodes and clients (when operating offline). If there are some conflicts we must have an arbiter to solve it and the key will be on a -2 version state until this conflict is solved.

I like the way CouchDb solved this, creating kind of a tree, NunDB does not have that instead it go creating a list of conflicts that can later be solved by an arbiter client whenever needed.

Single Socket channel (Chapter 30)

To replicate from primary to secondary NunDB uses a single socket to ensure the ordering of the messages.

In the module replication_ops we create a socket that will later be used to send the messages from primary to secondary or from primary, we later use a channel (I mean the send receiver channel class) to ensure only one thread will be handing the socket send.

Request Pipeline (Chapter 32)

Requests do not need to wait for the response from one command to send others, this is made to ensure we can process all request in parallel, and we create an list of responses to send back to the user, in the WebSocket implementation for http requests for example has the protocol is made to be request response our trick to support multiple commands was to send all of them in the request body separated by ;, process one by one and send all the responses in one single response.

Conclusion

NunDB does not support data partition yet, that is why there are no mentions in the post about those chapters. I was surprised by the amount of patterns that I found in NunDB while reading this book, most of them I learned while exploring other source code or reading books about data processing but I did not have a name to give them. I would not say this book is a must read if you are implementing a distributed system, there are others that I think will help you more (Like for example Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems).

It may be a great start though, if I had read this book before writing NunDB I may have identified some of the problems I had in the implementation and giving it a proper name.

Where to find the book?

Amazon

Written on August 25, 2024