From f0ea3fa98bfd96f0d4e590898cb8a9bf7c88132a Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Tue, 26 Sep 2023 15:45:45 +0100 Subject: [PATCH 1/4] Add support for RDB file config and reading keys and values from RDB files --- course-definition.yml | 104 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 95 insertions(+), 9 deletions(-) diff --git a/course-definition.yml b/course-definition.yml index 6e516c96..3785e6ba 100644 --- a/course-definition.yml +++ b/course-definition.yml @@ -52,17 +52,17 @@ marketing: I think the instant feedback right there in the git push is really cool. Didn't even know that was possible! -# extensions: -# - slug: "persistence" -# name: "Persistence" -# description_markdown: | -# In this challenge extension you'll add [persistence][redis-persistence] support to your Redis implementation. +extensions: + - slug: "persistence" + name: "Persistence" + description_markdown: | + In this challenge extension you'll add [persistence][redis-persistence] support to your Redis implementation. -# Along the way you'll learn about Redis's [RDB file format][rdb-file-format], the [SAVE][save-command] command, and more. + Along the way you'll learn about Redis's [RDB file format][rdb-file-format], the [SAVE][save-command] command, and more. -# [redis-persistence]: https://redis.io/docs/manual/persistence/ -# [rdb-file-format]: https://github.com/sripathikrishnan/redis-rdb-tools/blob/548b11ec3c81a603f5b321228d07a61a0b940159/docs/RDB_File_Format.textile -# [save-command]: https://redis.io/commands/save/ + [redis-persistence]: https://redis.io/docs/manual/persistence/ + [rdb-file-format]: https://github.com/sripathikrishnan/redis-rdb-tools/blob/548b11ec3c81a603f5b321228d07a61a0b940159/docs/RDB_File_Format.textile + [save-command]: https://redis.io/commands/save/ # - slug: "streams" # name: "Streams" @@ -231,3 +231,89 @@ stages: expiry is provided using the "PX" argument to the [SET](https://redis.io/commands/set) command. tester_source_code_url: "https://github.com/codecrafters-io/redis-tester/blob/master/internal/test_expiry.go" + + + # RDB Config + - slug: "rdb-config" + primary_extension_slug: "persistence" + name: "RDB file config" + difficulty: easy + description_md: | + Redis uses `.rdb` files for persistence. In this stage, you'll add support for reading the config values related to where RDB files are stored. + + There are two config values that determine where RDB files are stored: + + - `dir`: The directory where RDB files are stored + - `dbfilename`: The name of the RDB file + + These values will be passed into your program like this: + + ``` + ./your_server.sh --dir /tmp/redisfiles --dbfilename dump.rdb + ``` + + To verify whether your program is reading config values correctly, the tester will send you two commands: + + ```bash + redis-cli CONFIG GET dir + redis-cli CONFIG GET dbfilename + ``` + + The response to `CONFIG GET ` should be a RESP array with two elements: the key and the value. + + For example, let's say the `dir` value is `/tmp/redis-files`. The expected response will be: + + ``` + *2\r\n$3\r\ndir\r\n$16\r\n/tmp/redis-files\r\n + ``` + + - `*2\r\n` indicates that the array has two elements + - `$3\r\ndir\r\n` indicates that the first element is a bulk string with the value `dir` + - `$16\r\n/tmp/redis-files\r\n` indicates that the second element is a bulk string with the value `/tmp/redis-files` + marketing_md: | + In this stage, you'll add support for reading the config values related to where RDB files are stored. You'll implement the `CONFIG GET` command. + + - slug: "rdb-read-key" + primary_extension_slug: "persistence" + name: "Read a key" + difficulty: medium + description_md: | + **TODO**: This stage doesn't have instructions yet. + marketing_md: | + In this stage, you'll add support for reading a key from an RDB file that contains a single key-value pair. You'll do this by implementing the `KEYS *` command. + + - slug: "rdb-read-string-value" + primary_extension_slug: "persistence" + name: "Read a string value" + difficulty: medium + description_md: | + **TODO**: This stage doesn't have instructions yet. + marketing_md: | + In this stage, you'll add support for reading the value of a key from an RDB file that contains a single key-value pair. + + - slug: "rdb-read-multiple-keys" + primary_extension_slug: "persistence" + name: "Read multiple keys" + difficulty: medium + description_md: | + **TODO**: This stage doesn't have instructions yet. + marketing_md: | + In this stage, you'll add support for reading multiple keys from an RDB file. You'll do this by extending the `KEYS *` command to support multiple keys. + + - slug: "rdb-read-multiple-string-values" + primary_extension_slug: "persistence" + name: "Read multiple string values" + difficulty: medium + description_md: | + **TODO**: This stage doesn't have instructions yet. + marketing_md: | + In this stage, you'll add support for reading multiple string values from an RDB file. + + - slug: "rdb-read-value-with-expiry" + primary_extension_slug: "persistence" + name: "Read value with expiry" + difficulty: medium + description_md: | + **TODO**: This stage doesn't have instructions yet. + marketing_md: | + In this stage, you'll add support for reading values that have an expiry set. \ No newline at end of file From f29d5b78500f11ad57e7de67d24eacbdb890bc1c Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Tue, 26 Sep 2023 16:31:30 +0100 Subject: [PATCH 2/4] Update persistence extension slug and name in course-definition.yml --- course-definition.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/course-definition.yml b/course-definition.yml index 3785e6ba..29a14c7f 100644 --- a/course-definition.yml +++ b/course-definition.yml @@ -53,8 +53,8 @@ marketing: Didn't even know that was possible! extensions: - - slug: "persistence" - name: "Persistence" + - slug: "persistence-rdb" + name: "RDB Persistence" description_markdown: | In this challenge extension you'll add [persistence][redis-persistence] support to your Redis implementation. @@ -232,10 +232,10 @@ stages: [SET](https://redis.io/commands/set) command. tester_source_code_url: "https://github.com/codecrafters-io/redis-tester/blob/master/internal/test_expiry.go" + # Persistence - # RDB Config - slug: "rdb-config" - primary_extension_slug: "persistence" + primary_extension_slug: "persistence-rdb" name: "RDB file config" difficulty: easy description_md: | @@ -274,7 +274,7 @@ stages: In this stage, you'll add support for reading the config values related to where RDB files are stored. You'll implement the `CONFIG GET` command. - slug: "rdb-read-key" - primary_extension_slug: "persistence" + primary_extension_slug: "persistence-rdb" name: "Read a key" difficulty: medium description_md: | @@ -283,7 +283,7 @@ stages: In this stage, you'll add support for reading a key from an RDB file that contains a single key-value pair. You'll do this by implementing the `KEYS *` command. - slug: "rdb-read-string-value" - primary_extension_slug: "persistence" + primary_extension_slug: "persistence-rdb" name: "Read a string value" difficulty: medium description_md: | @@ -292,7 +292,7 @@ stages: In this stage, you'll add support for reading the value of a key from an RDB file that contains a single key-value pair. - slug: "rdb-read-multiple-keys" - primary_extension_slug: "persistence" + primary_extension_slug: "persistence-rdb" name: "Read multiple keys" difficulty: medium description_md: | @@ -301,7 +301,7 @@ stages: In this stage, you'll add support for reading multiple keys from an RDB file. You'll do this by extending the `KEYS *` command to support multiple keys. - slug: "rdb-read-multiple-string-values" - primary_extension_slug: "persistence" + primary_extension_slug: "persistence-rdb" name: "Read multiple string values" difficulty: medium description_md: | @@ -310,7 +310,7 @@ stages: In this stage, you'll add support for reading multiple string values from an RDB file. - slug: "rdb-read-value-with-expiry" - primary_extension_slug: "persistence" + primary_extension_slug: "persistence-rdb" name: "Read value with expiry" difficulty: medium description_md: | From 1a9ec3f48a55d60cc22177259defcc6619002b56 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Thu, 28 Sep 2023 17:25:38 +0100 Subject: [PATCH 3/4] Update course-definition.yml: Add Redis persistence support and learn about RDB file format --- course-definition.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/course-definition.yml b/course-definition.yml index 29a14c7f..a6b54ec0 100644 --- a/course-definition.yml +++ b/course-definition.yml @@ -58,11 +58,10 @@ extensions: description_markdown: | In this challenge extension you'll add [persistence][redis-persistence] support to your Redis implementation. - Along the way you'll learn about Redis's [RDB file format][rdb-file-format], the [SAVE][save-command] command, and more. + Along the way you'll learn about Redis's [RDB file format][rdb-file-format] and more. [redis-persistence]: https://redis.io/docs/manual/persistence/ [rdb-file-format]: https://github.com/sripathikrishnan/redis-rdb-tools/blob/548b11ec3c81a603f5b321228d07a61a0b940159/docs/RDB_File_Format.textile - [save-command]: https://redis.io/commands/save/ # - slug: "streams" # name: "Streams" From 5ecdef9ff104a864de217803a04d5faf08ec186e Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 30 Sep 2023 00:30:22 +0100 Subject: [PATCH 4/4] Add support for reading values with expiry from RDB files --- course-definition.yml | 171 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 156 insertions(+), 15 deletions(-) diff --git a/course-definition.yml b/course-definition.yml index a6b54ec0..817ed4fa 100644 --- a/course-definition.yml +++ b/course-definition.yml @@ -94,11 +94,25 @@ stages: In this stage, you'll respond to the [PING](https://redis.io/commands/ping) command. + The tester will execute your program like this: + + ```bash + $ ./spawn_redis_server.sh + ``` + + It'll then send a `PING` command to your server and expect a `+PONG\r\n` response. + + ```bash + $ redis-cli PING + ``` + Your server should respond with `+PONG\r\n`, which is "PONG" encoded as a [RESP simple string](https://redis.io/docs/reference/protocol-spec/#resp-simple-strings). - Since the tester client _only_ sends the PING command at the moment, it's okay to - ignore what the client sends and hardcode a response. We'll get to parsing - client input in later stages. + You can ignore the data that the tester sends you for this stage. We'll get to parsing + client input in later stages. For now, you can just hardcode `+PONG\r\n` as the response. + + **Note**: The exact bytes your program will receive won't be just `ping`, you'll receive something like this: `*1\r\n$4\r\nping\r\n`, + which is the Redis protocol encoding of the `PING` command. We'll learn more about this in later stages. marketing_md: | In this stage, you'll respond to the [PING](https://redis.io/commands/ping) command. You'll use [the Redis @@ -113,9 +127,19 @@ stages: In this stage, you'll respond to multiple [PING](https://redis.io/commands/ping) commands sent by the same connection. - To test your implementation using the [official Redis CLI](https://redis.io/docs/ui/cli/), you can start your server using - `./spawn_redis_server.sh` and then run `echo -e "ping\nping" | redis-cli` from your terminal. This will send two PING commands - using the same connection. + The tester will execute your program like this: + + ```bash + $ ./spawn_redis_server.sh + ``` + + It'll then send two PING commands using the same connection: + + ```bash + $ echo -e "ping\nping" | redis-cli + ``` + + The tester will expect to receive two `+PONG\r\n` responses. {{#lang_is_javascript}} In most languages, you'd need to run a loop that reads input from a connection and sends a @@ -129,8 +153,7 @@ stages: response back. {{/lang_is_javascript}} - Since the tester client _only_ sends the PING command at the moment, it's okay to - ignore what the client sends and hardcode a response. We'll get to parsing + Just like the previous stage, you can hardcode `+PONG\r\n` as the response for this stage. We'll get to parsing client input in later stages. marketing_md: | In this stage, you'll respond to multiple @@ -248,7 +271,7 @@ stages: These values will be passed into your program like this: ``` - ./your_server.sh --dir /tmp/redisfiles --dbfilename dump.rdb + ./spawn_redis_server.sh --dir /tmp/redisfiles --dbfilename dump.rdb ``` To verify whether your program is reading config values correctly, the tester will send you two commands: @@ -277,7 +300,38 @@ stages: name: "Read a key" difficulty: medium description_md: | - **TODO**: This stage doesn't have instructions yet. + In this stage, you'll add support for reading a key from an RDB file. + + To keep things simple, we'll start out by supporting RDB files that contain a single key. + + Jan-Erik Rediger (author of [rdb-rs](https://rdb.fnordig.de/)) has a great [blog post](https://jan-erik.red/blog/2019/12/30/redis-rdb-file-format/) + that explains the RDB file format in detail. We recommended using it as a reference when working on this stage. + + The tester will create an RDB file with a single key and execute your program like this: + + ``` + ./spawn_redis_server.sh --dir --dbfilename + ``` + + It'll then send a `keys *` command to your server. + + ```bash + $ redis-cli keys "*" + ``` + + The response to `keys *` should be a RESP array with one element: the key. + + For example, let's say the RDB file contains a key called `foo`. The expected response will be: + + ``` + *1\r\n$3\r\nfoo\r\n + ``` + + - `*1\r\n` indicates that the array has one element + - `$3\r\nfoo\r\n` indicates that the first element is a bulk string with the value `foo` + + **Note**: Remember, in this stage you only need to support RDB files that contain a single key, and you can ignore the value of the key. We'll + get to handling multiple keys and reading values in later stages. marketing_md: | In this stage, you'll add support for reading a key from an RDB file that contains a single key-value pair. You'll do this by implementing the `KEYS *` command. @@ -286,7 +340,35 @@ stages: name: "Read a string value" difficulty: medium description_md: | - **TODO**: This stage doesn't have instructions yet. + In this stage, you'll add support for reading the value corresponding to a key from an RDB file. + + Just like with the previous stage, we'll stick to supporting RDB files that contain a single key for now. + + The tester will create an RDB file with a single key and execute your program like this: + + ``` + ./spawn_redis_server.sh --dir --dbfilename + ``` + + It'll then send a `get ` command to your server. + + ```bash + $ redis-cli get "foo" + ``` + + The response to `get ` should be a RESP bulk string with the value of the key. + + For example, let's say the RDB file contains a key called `foo` with the value `bar`. The expected response will be `$3\r\nbar\r\n`. + + Strings can be encoded in three different ways in the RDB file format: + + - Length-prefixed strings + - Integers as strings + - Compressed strings + + In this stage, you only need to support length-prefixed strings. We won't cover the other two types in this challenge. + + We recommend using [this blog post](https://jan-erik.red/blog/2019/12/30/redis-rdb-file-format/) as a reference when working on this stage. marketing_md: | In this stage, you'll add support for reading the value of a key from an RDB file that contains a single key-value pair. @@ -295,7 +377,31 @@ stages: name: "Read multiple keys" difficulty: medium description_md: | - **TODO**: This stage doesn't have instructions yet. + In this stage, you'll add support for reading multiple keys from an RDB file. + + The tester will create an RDB file with multiple keys and execute your program like this: + + ```bash + $ ./spawn_redis_server.sh --dir --dbfilename + ``` + + It'll then send a `keys *` command to your server. + + ```bash + $ redis-cli keys "*" + ``` + + The response to `keys *` should be a RESP array with the keys as elements. + + For example, let's say the RDB file contains two keys: `foo` and `bar`. The expected response will be: + + ``` + *2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n + ``` + + - `*2\r\n` indicates that the array has two elements + - `$3\r\nfoo\r\n` indicates that the first element is a bulk string with the value `foo` + - `$3\r\nbar\r\n` indicates that the second element is a bulk string with the value `bar` marketing_md: | In this stage, you'll add support for reading multiple keys from an RDB file. You'll do this by extending the `KEYS *` command to support multiple keys. @@ -304,7 +410,22 @@ stages: name: "Read multiple string values" difficulty: medium description_md: | - **TODO**: This stage doesn't have instructions yet. + In this stage, you'll add support for reading multiple string values from an RDB file. + + The tester will create an RDB file with multiple keys and execute your program like this: + + ```bash + $ ./spawn_redis_server.sh --dir --dbfilename + ``` + + It'll then send multiple `get ` commands to your server. + + ```bash + $ redis-cli get "foo" + $ redis-cli get "bar" + ``` + + The response to each `get ` command should be a RESP bulk string with the value corresponding to the key. marketing_md: | In this stage, you'll add support for reading multiple string values from an RDB file. @@ -313,6 +434,26 @@ stages: name: "Read value with expiry" difficulty: medium description_md: | - **TODO**: This stage doesn't have instructions yet. + In this stage, you'll add support for reading values that have an expiry set. + + The tester will create an RDB file with multiple keys. Some of these keys will have an expiry set, and some won't. The expiry timestamps + will also be random, some will be in the past and some will be in the future. + + The tester will execute your program like this: + + ```bash + $ ./spawn_redis_server.sh --dir --dbfilename + ``` + + It'll then send multiple `get ` commands to your server. + + ```bash + $ redis-cli get "foo" + $ redis-cli get "bar" + ``` + + When a key has expired, the expected response is `$-1\r\n` (a "null bulk string"). + + When a key hasn't expired, the expected response is a RESP bulk string with the value corresponding to the key. marketing_md: | - In this stage, you'll add support for reading values that have an expiry set. \ No newline at end of file + In this stage, you'll add support for reading values that have an expiry set.