DEV Community

Cover image for Adventure: Building with NATS Jetstream KV Store -Part 3
Richard
Richard

Posted on • Edited on

Adventure: Building with NATS Jetstream KV Store -Part 3

Welcome back again!

Welcome to my adventure series on the NATS JetStream Key/Value Store! This is Part 3 of our series exploring the NATS JetStream KV Store! Let's just get to it and keep exploring!

Where did we leave off??

We're were just about to start messing around with lockers… I mean keys! I gotta stop doing that!


Creating Keys

Ok - let's add some keys to these Buckets. For this, we're going to have a Pop Quiz! I want you to delete any Buckets that exist.

What's that? We don't like Pop Quizzes? Ok fine. No Pop Quiz. Just run these commands.

$ nats kv ls              # list your buckets
$ nats kv del <bucket>    # delete them
Enter fullscreen mode Exit fullscreen mode

Now starting fresh let's make a new Bucket.

$ nats kv add Bucket1

Information for Key-Value Store Bucket Bucket1 created 2025-02-07T12:37:39-05:00

Configuration:

          Bucket Name: Bucket1
         History Kept: 1
        Values Stored: 0
           Compressed: false
   Backing Store Kind: JetStream
          Bucket Size: 0 B
  Maximum Bucket Size: unlimited
   Maximum Value Size: unlimited
          Maximum Age: unlimited
     JetStream Stream: KV_Bucket1
              Storage: File

Cluster Information:

                 Name: 
               Leader: NAHBJSFROLS2WSIXPPDBIUZXWJX6XIAKK36PTRF72ONLECU22XPPNV23
Enter fullscreen mode Exit fullscreen mode

As we saw earlier from nats kv -h, we can use the create command to create keys.

Let's try it!

$ nats kv create Bucket1 Key1

Enter fullscreen mode Exit fullscreen mode

Empty output from the response? Let's try again!

$ nats kv create Bucket1 Key1
nats: error: nats: wrong last sequence: 1: key exists
Enter fullscreen mode Exit fullscreen mode

Hmm, what's going on? Let's check the create command again.

$ nats kv

 nats kv
error: a subcommand from the list below is required, use --help for full help including flags and arguments

usage: nats kv <command> [<args> ...]

Interacts with a JetStream based Key-Value store

The JetStream Key-Value store uses streams to store key-value pairs for an indefinite period
or a per-bucket configured TTL.

Subcommands:
  kv add      Adds a new KV Store Bucket
  kv put      Puts a value into a key
  kv get      Gets a value for a key
  kv create   Puts a value into a key only if the key is new or it's last operation was a
              delete
  kv update   Updates a key with a new value if the previous value matches the given revision
  kv del      Deletes a key or the entire bucket
  kv purge    Deletes a key from the bucket, clearing history before creating a delete marker
  kv history  Shows the full history for a key
  kv revert   Reverts a value to a previous revision using put
  kv info     View the status of a KV store
  kv watch    Watch the bucket or a specific key for updated
  kv ls       List available buckets or the keys in a bucket
  kv compact  Reclaim space used by deleted keys

Pass --help to see global flags applicable to this command.
Enter fullscreen mode Exit fullscreen mode

Ok so, it will only create a new key if:

  • The key is new.
  • The Key's last operation was a delete.

Ok the key isn't new since we had created it before and it also wasn't previously deleted. This makes sense. It was weird though because we had an empty output upon creation.

Let's check the Bucket.

$ nats kv ls Bucket1
Key1
Enter fullscreen mode Exit fullscreen mode

Ok so the key was definitely created! Let's get it's value.

Reading Keys

Let's check the keys value. For this we can use get.

$ nats kv get Bucket1 Key1



Bucket1 > Key1 revision: 1 created @ 19 Jan 25 03:54 UTC
Enter fullscreen mode Exit fullscreen mode

Once again, we can see that the key is indeed present, along with extra details like its revision and the time it was created.

We’ll cover those details soon, but if you just want the value itself, you can use the raw flag.

$ nats kv get --raw Bucket1 Key1
Enter fullscreen mode Exit fullscreen mode

Nothing, and we had gotten an empty response when we created it. I think a picture is starting to emerge.

Let's try something else to be sure. We can check the history of a key like this.

$ nats kv history Bucket1 Key1

╭──────────────────────────────────────────────────────────────╮
│                  History for Bucket1 > Key1                  │
├──────┬──────────┬─────┬─────────────────────┬────────┬───────┤
│ Key  │ Revision │ Op  │ Created             │ Length │ Value │
├──────┼──────────┼─────┼─────────────────────┼────────┼───────┤
│ Key1 │        1 │ PUT │ 2025-01-18 22:54:59 │ 0      │       │
╰──────┴──────────┴─────┴─────────────────────┴────────┴───────╯
Enter fullscreen mode Exit fullscreen mode

Ohh ok! The Value was empty when we created the key, remember?

You can see the length of the value is 0. If you recall we did nats kv create Bucket1 Key1, not nats kv create Bucket1 Key1 Value1.

We never specified a Value!

Let's thank the history command for solving this mystery. We should probably talk about it!


Key History

So far, we’ve been busy creating keys and checking out their history—but let’s be honest, we’ve been winging it without really understanding.

To clear things up, we need to take a little step back. Let’s start with history (no, not that kind of history, the history of keys!).

When you create a Bucket, you get to decide how it handles keys and keeps track of their changes. Up until now, we’ve been rolling with the default setting, which, by the way, is set to 1.

But what does that even mean? Let’s dig in.

Start fresh again with no Buckets. Delete all the buckets! nats kv del <Bucket>

$ nats kv add --history=3 Bucket1

Information for Key-Value Store Bucket Bucket1 created 2025-02-07T13:01:15-05:00

Configuration:

          Bucket Name: Bucket1
         History Kept: 3
        Values Stored: 0
           Compressed: false
   Backing Store Kind: JetStream
          Bucket Size: 0 B
  Maximum Bucket Size: unlimited
   Maximum Value Size: unlimited
          Maximum Age: unlimited
     JetStream Stream: KV_Bucket1
              Storage: File

Cluster Information:

                 Name: 
               Leader: NAHBJSFROLS2WSIXPPDBIUZXWJX6XIAKK36PTRF72ONLECU22XPPNV23

Enter fullscreen mode Exit fullscreen mode

Notice History Kept: 3 rather than History Kept: 1. If you backtrack you'll see it was 1 when we made a Bucket without using the history flag. Go ahead and scroll up and check!

Ok so we have created a Bucket with History Kept: 3. Let's create a key again and check the history.

$ nats kv create Bucket1 Key1 Value1
Value1

$ nats kv history Bucket1 Key1
╭───────────────────────────────────────────────────────────────╮
│                   History for Bucket1 > Key1                  │
├──────┬──────────┬─────┬─────────────────────┬────────┬────────┤
│ Key  │ Revision │ Op  │ Created             │ Length │ Value  │
├──────┼──────────┼─────┼─────────────────────┼────────┼────────┤
│ Key1 │        1 │ PUT │ 2025-02-07 13:03:56 │ 6      │ Value1 │
╰──────┴──────────┴─────┴─────────────────────┴────────┴────────╯
Enter fullscreen mode Exit fullscreen mode

Ok this makes sense. We only have one entry in the history because we only just created a key. How do we update the value? I want to see my history!

We can use update!


Updating a Key

Let's try updating the key.

$ nats kv update Bucket1 Key1 Value2
nats: error: nats: wrong last sequence: 1
Enter fullscreen mode Exit fullscreen mode

Wait what? Maybe we should check what update does?

Subcommands:
  kv add      Adds a new KV Store Bucket
  kv put      Puts a value into a key
  kv get      Gets a value for a key
  kv create   Puts a value into a key only if the key is new or it's last operation was a delete
  kv update   Updates a key with a new value if the previous value matches the given revision
  kv del      Deletes a key or the entire bucket
  kv purge    Deletes a key from the bucket, clearing history before creating a delete marker
  kv history  Shows the full history for a key
  kv revert   Reverts a value to a previous revision using put
  kv info     View the status of a KV store
  kv watch    Watch the bucket or a specific key for updated
  kv ls       List available buckets or the keys in a bucket
  kv compact  Reclaim space used by deleted keys
Enter fullscreen mode Exit fullscreen mode

Ahh ok! It will only update the value if the previous value matches the given revision. We didn't give a revision.

What was the revision again?

$ nats kv history Bucket1 Key1

╭───────────────────────────────────────────────────────────────╮
│                   History for Bucket1 > Key1                  │
├──────┬──────────┬─────┬─────────────────────┬────────┬────────┤
│ Key  │ Revision │ Op  │ Created             │ Length │ Value  │
├──────┼──────────┼─────┼─────────────────────┼────────┼────────┤
│ Key1 │        1 │ PUT │ 2025-02-07 13:03:56 │ 6      │ Value1 │
╰──────┴──────────┴─────┴─────────────────────┴────────┴────────╯
Enter fullscreen mode Exit fullscreen mode

Duh! It was Revision 1. The first revision! Since we had only created the key. Let's supply the revision!

$ nats kv update Bucket1 Key1 Value2 1
Value2
Enter fullscreen mode Exit fullscreen mode

Bingo Bango Bongo! We got it!

For fun let's check the history again!

$ nats kv history Bucket1 Key1

╭───────────────────────────────────────────────────────────────╮
│                   History for Bucket1 > Key1                  │
├──────┬──────────┬─────┬─────────────────────┬────────┬────────┤
│ Key  │ Revision │ Op  │ Created             │ Length │ Value  │
├──────┼──────────┼─────┼─────────────────────┼────────┼────────┤
│ Key1 │        1 │ PUT │ 2025-02-07 13:03:56 │ 6      │ Value1 │
│ Key1 │        2 │ PUT │ 2025-02-07 13:09:54 │ 6      │ Value2 │
╰──────┴──────────┴─────┴─────────────────────┴────────┴────────╯
Enter fullscreen mode Exit fullscreen mode

Oh neat! We have another revision and another entry! This is because we set history=3! If you had created the Bucket with history=1 or omitted the history flag and went with default, you wouldn't see two entries!

Let's experiment. Create another Bucket with history=1. Then we will add a key and then update that key and check the history.

$ nats kv add Bucket2

Information for Key-Value Store Bucket Bucket2 created 2025-02-07T13:14:22-05:00

Configuration:

          Bucket Name: Bucket2
         History Kept: 1
        Values Stored: 0
           Compressed: false
   Backing Store Kind: JetStream
          Bucket Size: 0 B
  Maximum Bucket Size: unlimited
   Maximum Value Size: unlimited
          Maximum Age: unlimited
     JetStream Stream: KV_Bucket2
              Storage: File

Cluster Information:

                 Name: 
               Leader: NAHBJSFROLS2WSIXPPDBIUZXWJX6XIAKK36PTRF72ONLECU22XPPNV23

$ nats kv create Bucket2 Key1 Value1
Value1

$ nats kv update Bucket2 Key1 Value2 1
Value2

$ nats kv history Bucket2 Key1
╭───────────────────────────────────────────────────────────────╮
│                   History for Bucket2 > Key1                  │
├──────┬──────────┬─────┬─────────────────────┬────────┬────────┤
│ Key  │ Revision │ Op  │ Created             │ Length │ Value  │
├──────┼──────────┼─────┼─────────────────────┼────────┼────────┤
│ Key1 │        2 │ PUT │ 2025-02-07 13:14:32 │ 6      │ Value2 │
╰──────┴──────────┴─────┴─────────────────────┴────────┴────────╯
Enter fullscreen mode Exit fullscreen mode

See! There's still only 1 entry, whereas Bucket1 has 2 entries! You can see the revision number is 2 but there's still just 1 entry!

Let's mess with Bucket1 again. Let's update the key twice and see what happens.

nats kv update Bucket1 Key1 Value3 2
Value3

$ nats kv history Bucket1 Key1
╭───────────────────────────────────────────────────────────────╮
│                   History for Bucket1 > Key1                  │
├──────┬──────────┬─────┬─────────────────────┬────────┬────────┤
│ Key  │ Revision │ Op  │ Created             │ Length │ Value  │
├──────┼──────────┼─────┼─────────────────────┼────────┼────────┤
│ Key1 │        1 │ PUT │ 2025-02-07 13:03:56 │ 6      │ Value1 │
│ Key1 │        2 │ PUT │ 2025-02-07 13:09:54 │ 6      │ Value2 │
│ Key1 │        3 │ PUT │ 2025-02-07 13:16:45 │ 6      │ Value3 │
╰──────┴──────────┴─────┴─────────────────────┴────────┴────────╯

$ nats kv update Bucket1 Key1 Value4 3
Value4

$ nats kv history Bucket1 Key1
╭───────────────────────────────────────────────────────────────╮
│                   History for Bucket1 > Key1                  │
├──────┬──────────┬─────┬─────────────────────┬────────┬────────┤
│ Key  │ Revision │ Op  │ Created             │ Length │ Value  │
├──────┼──────────┼─────┼─────────────────────┼────────┼────────┤
│ Key1 │        2 │ PUT │ 2025-02-07 13:09:54 │ 6      │ Value2 │
│ Key1 │        3 │ PUT │ 2025-02-07 13:16:45 │ 6      │ Value3 │
│ Key1 │        4 │ PUT │ 2025-02-07 13:17:05 │ 6      │ Value4 │
╰──────┴──────────┴─────┴─────────────────────┴────────┴────────╯
Enter fullscreen mode Exit fullscreen mode

See! 3 entries! We no longer have Revision 1. It's sort of like a stack but revision 1 was replaced when the revisions went over the configured history number.

  • Bucket1 has a history of 3 and keeps track of up to 3 revisions.
  • Bucket2 has a history of 1 and keeps track of up to 1 revision.

Makes sense.

Ok updating each time and having to provide a revision number can be annoying. What if we just want to change the value and not worry about a revision number?

We can use put!


Updating a key with put

Image description

Like Bernie Sanders, once again I am going to ask you to check our commands.

Subcommands:
  kv add      Adds a new KV Store Bucket
  kv put      Puts a value into a key
  kv get      Gets a value for a key
  kv create   Puts a value into a key only if the key is new or it's last operation was a delete
  kv update   Updates a key with a new value if the previous value matches the given revision
  kv del      Deletes a key or the entire bucket
  kv purge    Deletes a key from the bucket, clearing history before creating a delete marker
  kv history  Shows the full history for a key
  kv revert   Reverts a value to a previous revision using put
  kv info     View the status of a KV store
  kv watch    Watch the bucket or a specific key for updated
  kv ls       List available buckets or the keys in a bucket
  kv compact  Reclaim space used by deleted keys
Enter fullscreen mode Exit fullscreen mode

Ok so put just puts a value into our key, nothing else is necessary. Let's try it.

$ nats kv put Bucket1 Key1 Value5
Value5

$ nats kv history Bucket1 Key1

╭───────────────────────────────────────────────────────────────╮
│                   History for Bucket1 > Key1                  │
├──────┬──────────┬─────┬─────────────────────┬────────┬────────┤
│ Key  │ Revision │ Op  │ Created             │ Length │ Value  │
├──────┼──────────┼─────┼─────────────────────┼────────┼────────┤
│ Key1 │        3 │ PUT │ 2025-02-07 13:16:45 │ 6      │ Value3 │
│ Key1 │        4 │ PUT │ 2025-02-07 13:17:05 │ 6      │ Value4 │
│ Key1 │        5 │ PUT │ 2025-02-07 13:33:51 │ 6      │ Value5 │
╰──────┴──────────┴─────┴─────────────────────┴────────┴────────╯
Enter fullscreen mode Exit fullscreen mode

There we go. We changed the value of Key1 to Value5 now. We also increased the revision number in the history which still has 3 entries.

put makes things easy when you need it to be!


Let's go over the write commands for the keys.

Ok so, what’s the difference between Create, Put and Update?

Create

Ensures a new key is added only if it doesn’t already exist, failing if the key is present. Use when you want to ensure a key is added without risk of overwriting.

Put

Sets a value for a key, creating it if it doesn’t exist or overwriting it if it does. Use for general-purpose operations where overwriting is acceptable.

Update

Updates a key’s value only if its current revision matches a specified version, ensuring safe conditional updates. Use for conditional updates where revision conflicts need to be avoided.

Sweet! Easy-peasy!

There was also another command in there you might have noticed called revert.

Let's check it out.


Revert

What if you want to revert to a previous value. Well-- it's a great thing we have history! NATS is great man!

$ nats kv history Bucket1 Key1

╭───────────────────────────────────────────────────────────────╮
│                   History for Bucket1 > Key1                  │
├──────┬──────────┬─────┬─────────────────────┬────────┬────────┤
│ Key  │ Revision │ Op  │ Created             │ Length │ Value  │
├──────┼──────────┼─────┼─────────────────────┼────────┼────────┤
│ Key1 │        3 │ PUT │ 2025-02-07 13:16:45 │ 6      │ Value3 │
│ Key1 │        4 │ PUT │ 2025-02-07 13:17:05 │ 6      │ Value4 │
│ Key1 │        5 │ PUT │ 2025-02-07 13:33:51 │ 6      │ Value5 │
╰──────┴──────────┴─────┴─────────────────────┴────────┴────────╯

$ nats kv revert Bucket1 Key1 3
Revision: 3

Value3

? Really revert to revision 3 Yes
╭───────────────────────────────────────────────────────────────╮
│                   History for Bucket1 > Key1                  │
├──────┬──────────┬─────┬─────────────────────┬────────┬────────┤
│ Key  │ Revision │ Op  │ Created             │ Length │ Value  │
├──────┼──────────┼─────┼─────────────────────┼────────┼────────┤
│ Key1 │        4 │ PUT │ 2025-02-07 13:17:05 │ 6      │ Value4 │
│ Key1 │        5 │ PUT │ 2025-02-07 13:33:51 │ 6      │ Value5 │
│ Key1 │        6 │ PUT │ 2025-02-07 13:38:09 │ 6      │ Value3 │
╰──────┴──────────┴─────┴─────────────────────┴────────┴────────╯
Enter fullscreen mode Exit fullscreen mode

Sweet! You can see the current Revision 6 has the value of Revision 3 like we asked!


Ok this was a doozy. We're not finished yet. Continue on to Part 4 of our journey as we continue our adventures with the NATS JetStream KV Store CLI!

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs