Challenge #4: Grow-Only Counter

In this challenge, you’ll need to implement a stateless, grow-only counter which will run against Maelstrom’s g-counter workload. This challenge is different than before in that your nodes will rely on a sequentially-consistent key/value store service provided by Maelstrom.

Specification

Your node will need to accept two RPC-style message types: add & read. Your service need only be eventually consistent: given a few seconds without writes, it should converge on the correct counter value.

Please note that the final read from each node should return the final & correct count.

RPC: add

Your node should accept add requests and increment the value of a single global counter. Your node will receive a request message body that looks like this:

{
  "type": "add",
  "delta": 123
}

and it will need to return an "add_ok" acknowledgement message:

{
  "type": "add_ok"
}

RPC: read

Your node should accept read requests and return the current value of the global counter. Remember that the counter service is only sequentially consistent. Your node will receive a request message body that looks like this:

{
  "type": "read"
}

and it will need to return a "read_ok" message with the current value:

{
  "type": "read_ok",
  "value": 1234
}

Service: seq-kv

Maelstrom provides a sequentially-consistent key/value store called seq-kv which has read, write, & cas operations. The Go library provides a KV wrapper for this service that you can instantiate with NewSeqKV():

node := maelstrom.NewNode()
kv := maelstrom.NewSeqKV(node)

The API is as follows:

func (kv *KV) Read(ctx context.Context, key string) (any, error)
    Read returns the value for a given key in the key/value store. Returns an
    *RPCError error with a KeyDoesNotExist code if the key does not exist.

func (kv *KV) ReadInt(ctx context.Context, key string) (int, error)
    ReadInt reads the value of a key in the key/value store as an int.

func (kv *KV) Write(ctx context.Context, key string, value any) error
    Write overwrites the value for a given key in the key/value store.

func (kv *KV) CompareAndSwap(ctx context.Context, key string, from, to any, createIfNotExists bool) error
    CompareAndSwap updates the value for a key if its current value matches the
    previous value. Creates the key if createIfNotExists is true.

    Returns an *RPCError with a code of PreconditionFailed if the previous value
    does not match. Return a code of KeyDoesNotExist if the key did not exist.

Evaluation

Build your Go binary as maelstrom-counter and run it against Maelstrom with the following command:

./maelstrom test -w g-counter --bin ~/go/bin/maelstrom-counter --node-count 3 --rate 100 --time-limit 20 --nemesis partition

This will run a 3-node cluster for 20 seconds and increment the counter at the rate of 100 requests per second. It will induce network partitions during the test.

If you’re successful, right on! Continue on to the Kafka-Style Log challenge. If you’re having trouble, ask for help on the Fly.io Community forum.

  1. Read More About Echo
  2. Read More About Unique ID Generation
  3. Read More About Broadcast
  4. Read More About Grow-Only Counter
  5. Read More About Kafka-Style Log
  6. Read More About Totally-Available Transactions