Skip to main content

Module value

Module value 

Source
Expand description

How a string value sits in a record, and what comes back out of one.

The map underneath stores opaque bytes against a key. Everything a string needs beyond those bytes, which is its encoding and its expiry deadline, is carried in a one byte header in front of them:

+--------+-------------------------+-------------------+-----------+
| meta   | expire at, u64 LE       | access, 24 bits   | payload   |
| u8     | 8 bytes, only if tagged | 3 bytes, tagged   | see below |
+--------+-------------------------+-------------------+-----------+

One byte, and eight more only for a key that has a deadline, which most keys do not. The alternative is a second lookup into a side table for the TTL, and a second lookup is a second cache miss on a path whose whole budget is one.

§The access field

Three bytes saying when the key was last read, or how often, depending on which eviction policy is in force. It is crate::access::Access and the reasoning about what goes in it lives there.

It is behind a tag bit the same way the deadline is, but unlike the deadline every record written now has one. The bit is there so that a record written before the field existed still reads back correctly rather than to make the field optional, and it sits after the deadline for the same reason: the deadline stays at offset one and everything that reads one goes on working.

Three bytes on every key is a real cost and it is worth being straight about why it is paid unconditionally rather than only under a policy that reads it. A key written under noeviction and then read under allkeys-lru has to be rankable, and it cannot become rankable later without the record growing, which means moving it, on what is usually a read. Paying three bytes always is the version where switching policy at runtime does the obvious thing.

The payload depends on the encoding. An int holds the eight bytes of the integer and not its digits, which is what makes INCR a probe, an add and a store with no arena traffic at all (08 section 2). An embstr and a raw hold the bytes as given. The difference between those two is the name OBJECT ENCODING reports and nothing else, which is also true in Redis: embstr there means the value was allocated next to its object header, a distinction yo does not have because every value is already next to its key.

§The type tag

The meta byte also says which type the key holds, in three bits that were spare. That is what TYPE reads and it is what a command will read before it decides whether it is looking at its own type or at somebody else’s, and both of those want the answer to come out of the byte the lookup already fetched rather than out of a second structure. A string is zero, so nothing written before the tag existed reads back as anything else.

§The tier tag

The last bit says whether the payload is the value or a place on the file where the value is. That is 14 section 4.1’s tier tag, and the reason it is a bit here rather than a lookup somewhere else is the number it exists to make possible: at most 1.05 device reads for every point read. A point read that has to consult a second structure to find out whether the value is resident has already spent the miss it was trying to avoid.

A demoted record keeps its deadline, its access field, its type and its encoding, and adds a length. So TTL, TYPE, OBJECT ENCODING, STRLEN, EXISTS and the whole of eviction go on answering at memory speed on a key whose value is on the device, and only the commands that actually want the bytes pay for them. write_cold_record writes one and cold is the branch that reads it.

Structs§

Cold
Where a demoted value is and how big it is.
Meta
The meta byte in front of every stored string.

Enums§

Encoding
What OBJECT ENCODING calls a string.
Kind
What TYPE calls a key, and what the meta byte’s tag holds.
Str
A stored value, read back out of a record.

Constants§

EMBSTR_MAX
The longest value Redis calls embstr rather than raw.

Functions§

access
What the access field in a record says, or None if it has no room for one.
cold
Where the value of a demoted record is, or None if the record is resident.
cold_record_len
How many bytes a record pointing at a demoted value occupies.
expire_at
The deadline in a record, if it has one.
has_expiry
Whether a record carries a deadline at all, without reading it.
is_expired
Whether a record’s deadline has passed at now_ms.
kind
The type a record holds.
raw_in_place
The bytes of a raw record, to be written over where they lie.
read
The value in a record.
read_int_in_place
The integer in an int encoded record, and where its bytes start.
record_len
How many bytes a record holding this value will occupy.
set_access
Stamp the access field, in place, over whatever was there.
slot
The slab number in a record that has one.
slot_record_len
How many bytes a record pointing at a slab slot occupies.
str_len
How long the value in a record is, without reading it.
write_cold_record
Write a record saying the value is at at on the file and is len bytes.
write_int_in_place
Store n back over an int payload that starts at at.
write_int_record
Write a record whose value is an integer the caller already has.
write_record
Write a whole record into out, which must be exactly record_len long.
write_slot_record
Write a record that points at slot in the slab for kind.