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.

Server structure

Drawio version

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.