Introduction
This post describes the first step in creating a Redis server in Typescript. The goal of this first
step is to have a functional server with a single command: PING
. All code related to this post can
be found in this repository.
Implementing
Overall structure
The planned server structure is as follows (see a diagram below): for each client, a connection handler is created which manages all communication between the server and the client. This connection handler is responsible for processing the user input, routing the command to the correct processor and returning the output.
The command processors are responsible for communicating with the storage layer and other background resources (if any). This follows a strategy pattern which will allow for implementing different commands more easily and without affecting already implemented ones.
Creating a shared repository
While implementing the code for this server, some logic was the same as the one implemented for the CLI. This is to be expected as much of the logic is naturally shared (for example, the protocol is the same for both the CLI and the server), however, since I wasn’t sure what parts would be truly shared, I decided not to start by creating a shared module as it felt as an early optimization. This proved to be the right choice as this allowed for more specific implementations of the server and CLI encoding and decoding algorithms.
All shared code was moved to this repository. Working with custom modules proved to be a very nice experience in Deno as it amounted to only importing from the GitHub URL.
Dealing with undocumented behavior
For implementing a true clone, even undocumented behavior should be implemented for compability reasons. This is limiting and can be annoying as some quirks might require special handling only to replicate the original behavior. Since the main objective of this project is to better understand how Redis works, I will not necessarily implement all undocumented behavior.
That being said, one undocumented behavior I found and implemented is that when the input is sent not in an array of bulk strings, Redis will parse it as a plain string. This is very useful as it allows for quick testing the server by directly connecting to it using netcat. Of course, it would still be possible to do this without this feature but it would be a lot more annoying as I would need to manually encode each request.
Known bugs
Multiple writes to the same connection
When a command is split across multiple payloads, the server is unable to correctly parse it. This happens because the server will parse each payload as it was a command and, thus, a command split accross multiple payloads will be handled as if it were multiple commands. The correct approach would be to enqueue every payload and parse them when the request is fully read. I’m not sure how to do it correctly in Deno but I will attempt to fix it later. GitHub issue.
Closing connections abruptly crashes the server
This should be an overall easy fix and it will be implemented in the near future. GitHub issue
Conclusion
Although one command has been successfully implemented, this is only the foundation upon which the
server will be built. The next steps will be to implement the other commands starting with the most
basic of them: GET
and SET
.