Skip to content

Optimistic Locking Stage Breakdown #338

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 66 additions & 1 deletion course-definition.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,16 @@ extensions:
[subscribe-command]: https://redis.io/docs/latest/commands/subscribe/
[publish-command]: https://redis.io/docs/latest/commands/publish/

- slug: "optimistic-locking"
name: "Optimistic Locking"
description_markdown: |
In this challenge extension you'll add support for [Optimistic Locking][redis-optimistic-locking] to your Redis implementation.

Along the way you'll learn about the [WATCH][watch-command] command and how it works in synergy with the `MULTI`, `EXEC`, and `DISCARD` commands to achieve optimistic locking.

[redis-optimistic-locking]: https://redis.io/docs/latest/develop/using-commands/transactions/#cas
[watch-command]: https://redis.io/docs/latest/commands/watch/

stages:
- slug: "jm1"
concept_slugs:
Expand Down Expand Up @@ -676,4 +686,59 @@ stages:
primary_extension_slug: "pub-sub"
name: "Unsubscribe"
difficulty: medium
marketing_md: In this stage, you'll add support for the `UNSUBSCRIBE command`, which is used to unsubscribe from a channel.
marketing_md: In this stage, you'll add support for the `UNSUBSCRIBE command`, which is used to unsubscribe from a channel.

# Optimistic Locking
- slug: "xj2"
primary_extension_sug: "optimistic-locking"
name: "Watch a key"
difficuly: easy
marketing_md: In this stage, you'll add support for the `WATCH` command.

- slug: "zs3"
primary_extension_sug: "optimistic-locking"
name: "Disallow WATCH in a transaction"
difficuly: easy
marketing_md: In this stage, you'll add support for disallowing the `WATCH` command in a transaction.

- slug: "qc8"
primary_extension_slug: "optimistic-locking"
name: "Fail EXEC after WATCH"
difficulty: hard
marketing_md: In this stage, you'll add support for failing transactions after a `WATCH` command is issued.

- slug: "bu1"
primary_extension_slug: "optimistic-locking"
name: "Optimistic Locking"
difficulty: medium
marketing_md: In this stage, you'll add support for optimistic locking using the `WATCH` command.

- slug: "al4"
primary_extension_slug: "optimistic-locking"
name: "Watch uninitialized keys"
difficulty: easy
marketing_md: In this stage, you'll add support for watching a non-existent key.

- slug: "ew6"
primary_extension_slug: "optimistic-locking"
name: "Watch multiple keys"
difficulty: easy
marketing_md: In this stage, you'll add support for watching multiple keys.

- slug: "mn2"
primary_extension_slug: "optimistic-locking"
name: "UNWATCH a key"
difficulty: easy
marketing_md: In this stage, you'll add support for the `UNWATCH` command.

- slug: "au8"
primary_extension_slug: "optimistic-locking"
name: "Unwatch on EXEC"
difficulty: easy
marketing_md: In this stage, you'll add support clearing watched keys on `EXEC`.

- slug: "vh1"
primary_extension_slug: "optimistic-locking"
name: "Unwatch on DISCARD"
difficulty: easy
marketing_md: In this stage, you'll add support clearing watched keys on `DISCARD`.
32 changes: 32 additions & 0 deletions stage_descriptions/optimistic-locking-01-xj2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
In this stage, you'll add support for the `WATCH` command.

### The `WATCH` command

[The `WATCH` command](https://redis.io/docs/latest/commands/watch/) marks a key to be monitored for changes.
If the watched key is modified by another client before the transaction is executed, the transaction will be aborted. This enables simple optimistic locking behavior in Redis.

Example Usage:
```
$ redis-cli WATCH key
OK
```

The response is always the simple string `"+OK\r\n"`.


### Tests
The tester will execute your program like this:

```bash
$ ./your_program.sh
```

The tester will then send a `WATCH` command with a key.

```bash
$ redis-cli WATCH key (expecting "+OK\r\n" as the response)
```

### Notes
- In this stage, you'll only need to handle replying to the `WATCH` command outside of a transaction.
- We will get to disallowing the `WATCH` command inside a transaction in the next stage.
44 changes: 44 additions & 0 deletions stage_descriptions/optimistic-locking-02-zs3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
In this stage, you'll add support for disallowing the `WATCH` command in a transaction.

### `WATCH` in transaction

[The `WATCH` command](https://redis.io/docs/latest/commands/watch/) is not allowed inside a transaction. If a `WATCH` command is issued from a client when it has begun a transaction using the `MULTI` command, the response to the `WATCH` is an error.

Example Usage:
```
$ redis-cli
> MULTI
OK
(TX)> WATCH foo
(error) ERR WATCH inside MULTI is not allowed
```

### Tests
The tester will execute your program like this:

```bash
$ ./your_program.sh
```

The tester will then begin a transaction using the `MULTI` command, and send a `WATCH` command with a random key.

```bash
$ redis-cli
> MULTI (expecting "+OK" as the response)

> WATCH key
# Expect: (error) ERR WATCH inside MULTI is not allowed
```
The response is a RESP simple string, which is encoded as:
```
-ERR WATCH inside MULTI is not allowed\r\n
```

The tester will then abort the transaction using the `DISCARD` command, and again send a `WATCH` command. It will expect the response to be `"+OK\r\n` in this case.

```bash
> DISCARD (expecting "+OK\r\n" as the response)

> WATCH key2 (expecting "+OK\r\n" as the response)
```

100 changes: 100 additions & 0 deletions stage_descriptions/optimistic-locking-03-qc8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
In this stage, you'll add support for failing transactions after a `WATCH` command is issued.

### Optimistic locking using `WATCH`
The `WATCH` command enables optimistic locking by monitoring the watched key for changes.
If the watched key is modified after `WATCH` is called and before `EXEC` is run, the transaction is aborted.

This facilitates the optimistic locking in Redis. Example Usage:

```bash
# Client A
> SET foo 100
OK

> WATCH foo
OK

# Client B
> SET foo 200
OK

# Client A
> MULTI
OK

> SET foo bar
QUEUED

> EXEC
(nil) # Transaction aborts due to watched key being modified
```

### Tests

The tester will execute your program like this:

```
$ ./your_program.sh
```

The tester will spawn two clients.

Using the first client, it will set the value of two keys and, issue a `WATCH` command specifying one of the keys

```bash
# Client 1
> SET foo 100 (Expecting "+OK\r\n")
> SET bar 200 (Expecting "+OK\r\n")
> WATCH foo
```

Using the second client, the tester will update the value of the watched variable.
```bash
# Client 2
> SET foo 200 (Expecing "+OK\r\n")
```

Using the first client, the tester will attempt to execute a transaction.
```bash
# Client 1
> MULTI (Expecting "+OK\r\n")
> SET bar 300 (Expecting "+QUEUED\r\n")
> EXEC (Expecting "*-1\r\n")
```

The response to `EXEC` should be a RESP null array.

Using the first client, the tester will retrieve the value of unwatched key to check if the transaction was aborted.

```bash
# Client 1
> GET bar (Expecting bulk string "200")
```

Using the second client, the tester will attempt to execute a transaction.
```bash
# Client 2
> MULTI (Expecting "+OK\r\n")
> SET bar 300 (Expecting "+QUEUED\r\n")
> PING (Expecting "+QUEUED\r\n")
> EXEC (Expecting an array of responses for the queued commands)
```
The response to the `EXEC` command should be its usual response because `WATCH` command was not issued from this client previously.


Using the first client, the tester will retrieve the value of unwatched key to check if the transaction succeeded.

```bash
# Client 1
> GET bar (Expecting bulk string "300")
```


### Notes

- In this stage, you will implement aborting a transaction if a `WATCH` command was issued by the client previously.
- Assume that the watched variable is guaranteed to be modified.

- If a `WATCH` command was not issued previously by a client, the transaction execution should continue with its usual operation.

- We will get to implementing failing transactions based on modification of watched keys in the next stage.
83 changes: 83 additions & 0 deletions stage_descriptions/optimistic-locking-04-bu1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
In this stage, you'll add support for failing transactions based on the modification of watched keys.

### Tests

The tester will execute your program like this:

```
$ ./your_program.sh
```

The tester will spawn four clients.

Using the first client, it will set the value of two keys and, issue a `WATCH` command specifying one of the keys

```bash
# Client 1
> SET foo 100 (Expecting "+OK\r\n")
> SET bar 200 (Expecting "+OK\r\n")
> WATCH foo
```

Using the second client, the tester will update the value of the watched variable.
```bash
# Client 2
> SET foo 200 (Expecing "+OK\r\n")
> SET foo 100 (Expecing "+OK\r\n")
```

Using the first client, the tester will attempt to execute a transaction.
```bash
# Client 1
> MULTI (Expecting "+OK\r\n")
> SET bar 300 (Expecting "+QUEUED\r\n")
> EXEC (Expecting "*-1\r\n")
```

The response to `EXEC` should be a RESP null array. Please note that even if the key's value was restored to its original value before the `WATCH` command was issued, the transaction will still fail, because the key was **modified**.

Using the first client, the tester will retrieve the value of unwatched key to check if the transaction was aborted.

```bash
# Client 1
> GET bar (Expecting bulk string "200")
```
---

Using the third client, the tester will set the value of two keys and, issue a `WATCH` command specifying one of the keys

```bash
# Client 1
> SET baz 100 (Expecting "+OK\r\n")
> SET caz 200 (Expecting "+OK\r\n")
> WATCH baz
```

Using the fourth client, the tester will modify the value of the **un-watched** variable.
```bash
# Client 2
> SET caz 200 (Expecing "+OK\r\n")
```

Using the third client, the tester will attempt to execute a transaction.
```bash
# Client 1
> MULTI (Expecting "+OK\r\n")
> SET caz 400 (Expecting "+QUEUED\r\n")
> EXEC (Expecting an array of responses for the queued commands)
```

The response to `EXEC` should be its usual response because the watched variable was un-modified.

Using the fourth client, the tester will retrieve the value of the unwatched variable to check if the transaction succeeded.

```bash
# Client 1
> GET caz (Expecting bulk string "400")
```


### Notes

- The transaction should fail if the watched key was modified before the execution of `EXEC`.
- It is not enough to check the equality of values of watched keys at the time of `WATCH` and `EXEC`. If the watched key was modified, it should be marked as modified, even if its value was restored to its original value before the `WATCH` command.
Loading
Loading