Skip to main content

Your first bucket

Time to make the INVENTORY bucket real. The inventory service keeps a stock count for each SKU, and a bucket is where those counts live. This page creates the bucket with one command, puts a count, gets it back, and reads the bucket's status, and does nothing more than that.

The previous chapter gave you JetStream. A bucket rides on top of it: a bucket is a JetStream stream the key-value API creates and configures for you, so you never write the stream by hand. You ask for a bucket, and the server stores one stream named KV_INVENTORY behind the friendly name INVENTORY. The stream is the topic of the under the hood page; here you only need to know it exists.

Create the bucket

A running nats-server with JetStream enabled is the one prerequisite. The key-value API is part of JetStream, so without -js the next command has nothing to talk to:

nats-server -js

In another terminal, create the bucket:

#!/bin/bash
# Create the INVENTORY bucket. A bucket is created as a JetStream stream,
# so this one command sets up the backing stream KV_INVENTORY on the
# subjects $KV.INVENTORY.>.
#
# --history 1 keeps only the current value of each key. We raise this
# later on the history-and-revisions page; 1 is the default and is all
# the inventory service needs to start.

nats kv add INVENTORY --history 1

# Expected output ends with a line confirming the bucket was created:
#
# INVENTORY Key-Value Store
#
# Configuration:
# Bucket Name: INVENTORY
# History: 1
# ...

Two parts of this command matter. The first is the bucket name: INVENTORY. Bucket names are case-sensitive identifiers, and they show up in every command and every error message in this chapter. The name maps straight onto the backing stream: INVENTORY becomes KV_INVENTORY.

The second is --history 1. History is how many prior values the bucket keeps for each key. One means the bucket holds only the current value of a key and forgets the rest. That's the default and all the inventory service needs to start. The depth can go as high as 64, but no higher; History and revisions raises it so a key remembers where it's been, and for now, one is enough.

You didn't set any other configuration. A bucket has the same long list of stream knobs underneath, all filled with sensible defaults. The full set of bucket configuration options lives in Reference → Create Stream, since a bucket is created as a stream. We use only History here.

Put a value, get an entry

The bucket is empty. Put the first stock count into it. The key is the SKU, and the value is the count stored as bytes:

#!/bin/bash
# Put the stock count for widget-blue into the bucket. The key is the SKU,
# the value is the count as bytes. A put is unconditional: it writes the
# value whether or not the key already exists.
#
# This is the inventory service recording that there are 42 widget-blue
# units in stock.

nats kv put INVENTORY widget-blue 42

# The put returns the new revision of the key. The first write to a fresh
# key lands at revision 1:
#
# INVENTORY > widget-blue revision: 1 created @ ...

That's a put: an unconditional write. It stores the value whether or not the key already exists, and it hands back the key's new revision, a number the bucket bumps on every write. The first write to a fresh key lands at revision 1. Revisions are how the bucket tracks change over time; History and revisions builds on them, and for now the number only confirms the write happened.

Now read it back:

#!/bin/bash
# Get widget-blue back. By default a get returns the whole entry: the
# value plus its revision and timestamp. --raw asks for only the value
# bytes, which is what a program usually wants.

nats kv get INVENTORY widget-blue --raw

# Prints just the value:
#
# 42

# Without --raw you see the full entry the server returns:
nats kv get INVENTORY widget-blue

# INVENTORY > widget-blue created @ ...
#
# 42
#
# A get for a key that was never put fails with a key-not-found error,
# which is distinct from a key whose value is empty. Check for that error
# before using the value:
nats kv get INVENTORY widget-green --raw || echo "widget-green not in stock yet"

A get returns an entry rather than a bare value: the value together with its revision and the time it was written. The CLI's --raw flag strips the entry down to just the value bytes (42), which is usually what a program wants, but the full object is what the server actually sends.

That shape is intentional, because the inventory service rarely wants only the count; it wants the count and the revision, because history and revisions uses that revision to decrement the value safely. The entry carries both in one read, so you never have to make a second call to learn which revision you just saw.

The timestamp on the entry matters too. It records when the value was written, not when you read it, so the inventory service can tell a count taken seconds ago from one that has sat untouched for a week. You get all three facts — value, revision, and write time — from the single get you already made.

Read the bucket's status

One command summarizes the bucket as a whole:

#!/bin/bash
# Ask the server about the bucket as a whole. Status reports the bucket's
# configuration and how many values it currently holds.

nats kv status INVENTORY

# Expected output reports the bucket name, history depth, value count, and
# the backing store:
#
# INVENTORY Key-Value Store Status
#
# Bucket Name: INVENTORY
# History: 1
# TTL: 0s
# Backing Store: JetStream
# Backing Store Name: KV_INVENTORY
# Values: 1
#
# Backing Store Name names the stream under the bucket: KV_INVENTORY.
# That is the proof the bucket is a stream; the under-the-hood page opens
# it up.

The status reports the bucket name, the history depth you set, and how many values it holds. It also reports the backing stream: the stream the bucket is built on, named KV_INVENTORY. That line is your first concrete proof that a bucket is a stream with a friendlier name. The under the hood page opens that stream and reads it directly.

Pitfalls

Two mistakes are common on your very first bucket, and each is easy to avoid once you've seen it.

A get returns an entry rather than a value, and a missing key is an error rather than an empty value. Reaching straight for the value bytes works only when the key exists. A key that was never put doesn't return an empty entry; it returns a key-not-found error. Those are two different situations: an empty value is a value, and a missing key is the absence of one. Don't treat a failed get as "the count is zero." The get example above ends with exactly this case: its last line gets a SKU that was never stocked, the get fails, and the program decides what a missing SKU means instead of reading a stale or zero count by accident. Check the error first, then read the value.

Bucket and key names are validated. A bucket name may contain only letters, digits, dash, and underscore, and it can't be empty. A key is more permissive (letters, digits, and the characters -, /, _, =, and .), but nothing beyond that set, no leading or trailing dot, and no two dots in a row (a..b is rejected even though a.b is fine). An order id like ord:8w2k has a colon, so it can't be a key; the server rejects the write rather than storing a broken key. Pick names from the allowed set, and reach for an underscore or dash where you'd have used a colon:

#!/bin/bash
# Key and bucket names are validated. A bucket name may use only letters,
# digits, dash, and underscore. A key may use letters, digits, and the
# characters - / _ = . — and nothing else.
#
# An order id like "ord:8w2k" has a colon, which is not allowed as a key.
# The server rejects the put rather than storing a broken key:

nats kv put INVENTORY "ord:8w2k" 42 || echo "rejected: ':' is not a legal key character"

# A legal key with the same intent uses an allowed separator:
nats kv put INVENTORY ord_8w2k 42

Where you are

You now have:

  • An INVENTORY bucket, backed by the stream KV_INVENTORY.
  • The key widget-blue holding the value 42 at revision 1.
  • The ability to put a value, get back its entry, and read the bucket's status.

What's next

The next page puts the warehouse dashboard on the bucket: a watch that streams every stock change live, starting with a snapshot of what's already there.

Continue to Watching.

See also