forked from codecrafters-io/build-your-own-redis
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcourse-definition.yml
473 lines (369 loc) · 19.2 KB
/
course-definition.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
slug: "redis"
name: "Build your own Redis"
short_name: "Redis"
release_status: "live"
description_md: |-
In this challenge, you'll build a toy Redis clone
that's capable of handling basic commands like PING, GET
and SET. Along the way, we'll learn about event loops, the Redis
Protocol and more.
short_description_md: |-
Learn about TCP servers, the Redis protocol and more
completion_percentage: 30
concept_slugs: ["network-protocols", "tcp-overview"]
languages:
- slug: "c"
- slug: "clojure"
- slug: "cpp"
- slug: "crystal"
- slug: "csharp"
- slug: "elixir"
- slug: "go"
- slug: "haskell"
- slug: "java"
- slug: "javascript"
- slug: "php"
- slug: "python"
- slug: "ruby"
- slug: "rust"
marketing:
difficulty: medium
sample_extension_idea_title: "Persistence"
sample_extension_idea_description: "A Redis server that can read and write .rdb files"
testimonials:
- author_name: "Charles Guo"
author_description: "Software Engineer, Stripe"
author_avatar: "https://codecrafters.io/images/external/testimonials/charles-guo.png"
link: "https://github.com/shaldengeki"
text: |-
The Redis challenge was extremely fun. I ended up having to read the
Redis Protocol specification doc pretty carefully in its entirety! The result
felt like lightly-guided independent study, if that makes sense. (Which, again, was lots of fun)
- author_name: "Patrick Burris"
author_description: "Senior Software Developer, CenturyLink"
author_avatar: "https://codecrafters.io/images/external/testimonials/patrick-burris.jpeg"
link: "https://github.com/Jumballaya"
text: |-
I think the instant feedback right there in the git push is really cool.
Didn't even know that was possible!
extensions:
- slug: "persistence-rdb"
name: "RDB 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] 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
# - slug: "streams"
# name: "Streams"
# description_markdown: |-
# In this challenge extension you'll add support for the [Stream][redis-streams-data-type] data type to your Redis implementation.
# Along the way you'll learn about commands like [XADD][xadd-command], [XRANGE][xrange-command] and more.
# [redis-streams-data-type]: https://redis.io/docs/data-types/streams/
# [xadd-command]: https://redis.io/commands/xadd/
# [xrange-command]: https://redis.io/commands/xrange/
stages:
- slug: "init"
concept_slugs: ["network-protocols", "tcp-overview"]
name: "Bind to a port"
description_md: |-
In this stage, your task is to start a TCP server on port 6379, the default port that redis uses.
difficulty: very_easy
marketing_md: |-
In this stage, you'll start a TCP server on port 6379, which is the
default port that Redis uses.
tester_source_code_url: "https://github.com/codecrafters-io/redis-tester/blob/a58b9d58b33870fe26a164c0e323f809275a7250/internal/test_bind.go#L11"
- slug: "ping-pong"
concept_slugs: ["network-protocols", "tcp-overview"]
name: "Respond to PING"
difficulty: easy
description_md: |-
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).
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
protocol](https://redis.io/topics/protocol) to encode the reply.
tester_source_code_url: "https://github.com/codecrafters-io/redis-tester/blob/a58b9d58b33870fe26a164c0e323f809275a7250/internal/test_ping_pong.go#L9"
- slug: "ping-pong-multiple"
concept_slugs: ["network-protocols", "tcp-overview"]
name: "Respond to multiple PINGs"
difficulty: easy
description_md: |-
In this stage, you'll respond to multiple
[PING](https://redis.io/commands/ping) commands sent by 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
response back. In JavaScript however, if you're listening to the
[`data`](https://nodejs.org/api/net.html#net_event_data) event, this should be automatically handled for you. **It
is very likely that the code you had for the previous stage will pass this stage without any changes!**
{{/lang_is_javascript}}
{{^lang_is_javascript}}
You'll need to run a loop that reads input from a connection and sends a
response back.
{{/lang_is_javascript}}
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
[PING](https://redis.io/commands/ping) commands sent by the same client.
tester_source_code_url: "https://github.com/codecrafters-io/redis-tester/blob/a58b9d58b33870fe26a164c0e323f809275a7250/internal/test_ping_pong.go#L35"
- slug: "concurrent-clients"
concept_slugs: ["network-protocols", "tcp-overview"]
name: "Handle concurrent clients"
difficulty: medium
description_md: |-
In this stage, your server will need to handle multiple concurrent
clients. Just like the previous stages, all clients will only send `PING`
commands for now.
{{#lang_is_javascript}}
In most languages, you'd need to either use threads or implement an
[Event Loop](https://en.wikipedia.org/wiki/Event_loop) to do this. In JavaScript however, since [the concurrency
model itself is based on an event loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop), most
standard library functions are designed to support this kind of concurrent behaviour out of the box. **It is very
likely that the code you had for the previous stage will pass this stage without any changes!**
{{/lang_is_javascript}}
{{^lang_is_javascript}}
To achieve this, you'll need to either use threads, or, if you're feeling
adventurous, an [Event Loop](https://en.wikipedia.org/wiki/Event_loop) (like
the official Redis implementation does).
{{/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
client input in later stages.
marketing_md: |-
In this stage, you'll add support for multiple concurrent clients to your
Redis server. To achieve this you'll use an [Event
Loop](https://en.wikipedia.org/wiki/Event_loop),
like the official Redis implementation does.
tester_source_code_url: "https://github.com/codecrafters-io/redis-tester/blob/a58b9d58b33870fe26a164c0e323f809275a7250/internal/test_ping_pong.go#L56"
- slug: "echo"
name: "Implement the ECHO command"
difficulty: medium
description_md: |-
In this stage, you'll respond to the
[ECHO](https://redis.io/commands/echo) command.
The client will send you the command as a RESP array, which looks
something like this:
```
*2\r\n$4\r\nECHO\r\n$3\r\nhey\r\n
```
Seems confusing? Read up about [sending commands to a Redis
server](https://redis.io/docs/reference/protocol-spec/#send-commands-to-a-redis-server).
marketing_md: |-
In this stage, you'll respond to the
[ECHO](https://redis.io/commands/echo) command. You'll parse user input
according to the [the Redis protocol
specification](https://redis.io/topics/protocol).
tester_source_code_url: "https://github.com/codecrafters-io/redis-tester/blob/a58b9d58b33870fe26a164c0e323f809275a7250/internal/test_echo.go#L11"
# TODO: Change this to use hyphens
- slug: "set_get"
name: "Implement the SET & GET commands"
difficulty: medium
description_md: |-
In this stage, you'll need to implement the [SET](https://redis.io/commands/set) &
[GET](https://redis.io/commands/get) commands. For now, you can ignore all extra
options for `SET` and just implement the simple form: `SET key value`. You'll add support
for expiry in the next stage.
marketing_md: |-
In this stage, you'll need to implement the
[SET](https://redis.io/commands/set) &
[GET](https://redis.io/commands/get) commands.
tester_source_code_url: "https://github.com/codecrafters-io/redis-tester/blob/a58b9d58b33870fe26a164c0e323f809275a7250/internal/test_get_set.go#L11"
- slug: "expiry"
name: "Expiry"
difficulty: medium
description_md: |-
In this stage, you'll need to support setting a key with an expiry. The
expiry is provided in milliseconds using the "PX" argument to the
[SET](https://redis.io/commands/set) command.
The tester will do the following:
```bash
# First, it'll set a key with an expiry (100 milliseconds in this example)
$ redis-cli set random_key random_value px 100
# Immediately after, it'll send a GET command to retrieve the value
# The response to this should be "random_value" (encoded as a RESP bulk string)
$ redis-cli get random_key
# Then, it'll wait for the key to expire and send another GET command
# The response to this should be `-1\r\n` (a "null bulk string")
$ sleep 0.2 && redis-cli get random_key
```
{{#lang_is_haskell}}
The [time](https://hackage.haskell.org/package/time) package is available
to use as a dependency.
{{/lang_is_haskell}}
marketing_md: |-
In this stage, you'll add support for setting a key with an expiry. The
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"
# Persistence
- slug: "rdb-config"
primary_extension_slug: "persistence-rdb"
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:
```
./spawn_redis_server.sh --dir /tmp/redis-files --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 <key>` 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`
**Note**: If your repository was created before 5th Oct 2023, it's possible that your `./spawn_redis_server.sh` script
might not be passing arguments on to your program. You'll need to edit `./spawn_redis_server.sh` to fix this, check
[this PR](https://github.com/codecrafters-io/build-your-own-redis/pull/89/files) for details.
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-rdb"
name: "Read a key"
difficulty: medium
description_md: |
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 [write-up](https://rdb.fnordig.de/file_format.html)
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 <dir> --dbfilename <filename>
```
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.
- slug: "rdb-read-string-value"
primary_extension_slug: "persistence-rdb"
name: "Read a string value"
difficulty: medium
description_md: |
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 <dir> --dbfilename <filename>
```
It'll then send a `get <key>` command to your server.
```bash
$ redis-cli get "foo"
```
The response to `get <key>` 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://rdb.fnordig.de/file_format.html) 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.
- slug: "rdb-read-multiple-keys"
primary_extension_slug: "persistence-rdb"
name: "Read multiple keys"
difficulty: medium
description_md: |
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 <dir> --dbfilename <filename>
```
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.
- slug: "rdb-read-multiple-string-values"
primary_extension_slug: "persistence-rdb"
name: "Read multiple string values"
difficulty: medium
description_md: |
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 <dir> --dbfilename <filename>
```
It'll then send multiple `get <key>` commands to your server.
```bash
$ redis-cli get "foo"
$ redis-cli get "bar"
```
The response to each `get <key>` 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.
- slug: "rdb-read-value-with-expiry"
primary_extension_slug: "persistence-rdb"
name: "Read value with expiry"
difficulty: medium
description_md: |
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 <dir> --dbfilename <filename>
```
It'll then send multiple `get <key>` 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.