yo_kv/lib.rs
1//! The Redis data structures, as plain Rust types with no protocol attached.
2//!
3//! This crate is the answer to a question the design keeps asking: where does
4//! `INCR` actually live. It does not live in the codec, because an embedded
5//! caller never speaks RESP and should not have to. It does not live in the
6//! typed API either, because the wire has to reach the same code the typed API
7//! reaches, or there are two implementations of `INCR` and one of them is wrong
8//! (Y23). So it lives here, one method per command, taking and returning
9//! ordinary Rust values, with `yo-resp` above it turning frames into calls and
10//! the typed API above it turning generics into calls.
11//!
12//! What that buys is worth being clear about. An embedded program calls
13//! [`Keyspace::incr`] and gets an `i64` or a [`yo_common::Error`]. It does not
14//! serialise a command, it does not cross a socket, and it does not parse a
15//! reply. That is the whole point of P1 and it is why the sub 150 nanosecond
16//! number in `bench/00` is measured through here and not through a client.
17//!
18//! # What is here so far
19//!
20//! The string type, which is the first row of M2, and all 26 of its commands.
21//!
22//! The hash, in both of its representations and with field TTL, which is the
23//! `HEXPIRE` family and the third answer `OBJECT ENCODING` can give.
24//!
25//! The bitmaps, which are the same string values seen a bit at a time. [`bits`]
26//! is the kernels, popcount and scan and the eight ways of combining two
27//! bitmaps and the packed integer fields, and [`bitmaps`] is where a key turns
28//! into bytes and where Redis's edges are kept. There is no bitmap type,
29//! because in Redis there is not one either: `SET k A` then `GETBIT k 1`
30//! answers one.
31//!
32//! The HyperLogLogs, which are those same string values again with a documented
33//! layout inside them. [`hll`] is the sketch, the hash and the two
34//! representations and Ertl's estimator, and [`hlls`] is where a key turns into
35//! one. It is byte for byte Redis's format, on purpose: a client can `GET` a
36//! sketch out of a real server and `SET` it into us, and it has to count the
37//! same, so the hash function and the opcodes and the promotion threshold are
38//! copied rather than improved on.
39//!
40//! The places, which are sorted sets seen as a map. [`geo`] is the arithmetic,
41//! the 52 bit interleave of longitude and latitude that a score is, the
42//! haversine, the eleven character geohash string and the boxes a search covers,
43//! and [`geos`] is where a key turns into a search. There is no geo type either,
44//! for the same reason there is no bitmap one: `GEOADD` writes a member with a
45//! score and `ZSCORE` reads that score straight back out, so a place is a sorted
46//! set entry that somebody has agreed to read as a coordinate. What the search
47//! costs is nine score ranges, one for the box the centre falls in and one for
48//! each neighbour, at a precision picked so the nine cover the shape, and every
49//! candidate is measured properly afterwards so nothing outside the circle or
50//! the rectangle reaches the caller.
51//!
52//! The set, which is the first row of M3, in all seventeen of its commands:
53//! `SADD`, `SREM`, `SCARD`, `SISMEMBER`, `SMISMEMBER`, `SMEMBERS`, `SPOP`,
54//! `SRANDMEMBER`, `SSCAN`, `SMOVE`, `SINTER`, `SINTERCARD`, `SUNION`, `SDIFF`
55//! and the three store forms. A [`Set`] is one of three representations and
56//! moves between them on the same rules Redis uses, with `OBJECT ENCODING`
57//! saying which one it is on and the five `CONFIG` thresholds moving the lines.
58//! [`Keyspace`] is where a key gets to be
59//! something other than a string: the record holds a number, the number points
60//! into a [`Slab`], and every path that deletes a key or writes over one frees
61//! what it was pointing at. That last part is why `WRONGTYPE` exists as of this
62//! milestone. There was no way to trigger it while a string was all there was.
63//!
64//! Lists, hashes and the sorted set follow the same shape and land in M3 too.
65//!
66//! [`orderkey`] is the allocator that decides what sort key a list element gets,
67//! which is the piece that stops `LINSERT` from renumbering everything behind
68//! the element it inserted. It is the variable width scheme Y19 settles on, it
69//! reproduces K14's eight inserts per byte, and like aki's own first slice of
70//! this it ships proven and wired to nothing: the representation that stores a
71//! list by key rather than by position is the partitioned band below, and the
72//! wiring is a later piece of M4 than either of them.
73//!
74//! [`Parts`] is the partitioned band, which is what a collection becomes once it
75//! is too large to be one element table. It is P tables with a member's
76//! partition taken out of its hash, and in front of them the descriptor cache
77//! `05` calls mandatory, which is what lets an operation know how the elements
78//! are spread without adding up all P of them. What partitioning is really for is
79//! the merge, the growth and the reclaim, none of which want to touch a million
80//! element table at once. The cache turned out not to be the locality problem it
81//! reads as, and `benches/parts.rs` has the measurement and the module doc has
82//! the argument.
83//!
84//! The pieces of M3 that are here already are the ones every collection shares.
85//! [`Elements`] is the element table a hash, a set and a sorted set are all
86//! built out of, and [`Cursor`] is the scan cursor they all hand back to a
87//! client. Neither is a set or a hash on its own, and both are where the
88//! decisions that make those fast were made.
89//!
90//! [`Listpack`] is the band underneath both of them. A collection of a few dozen
91//! elements does not want an index at all, so up to a hundred and twenty eight
92//! it is one packed blob walked linearly, in Redis's own byte layout so that an
93//! RDB export is a copy and `OBJECT ENCODING` can honestly say `listpack`.
94//!
95//! The four commands Redis added in 8.4 and 8.8 are the interesting ones and
96//! they were checked against a real 8.8 rather than written from the
97//! documentation. [`Keyspace::digest`] is the XXH3 of a value, and it is bit for
98//! bit Redis's number, which is what makes [`Compare::DigestEqual`] worth
99//! anything: a client comparing against a large value sends eight bytes instead
100//! of the value. [`Keyspace::increx`] is not the rate limiter it looks like at
101//! first, it is a counter with a bound, a saturation policy and four things it
102//! can do to the deadline, and the rate limiter is one setting of it.
103//!
104//! # Divergences
105//!
106//! Four, all recorded in `divergences.toml` rather than left to be discovered.
107//!
108//! A string is capped at [`strings::STRING_MAX`] rather than Redis's 512 MiB,
109//! because a value lives in one arena segment until the log backed band lands in
110//! M5. Expiry is lazy only: a key past its deadline is dropped when something
111//! touches it, and the active cycle that would reclaim a key nobody ever touches
112//! again is maintenance slice work in M5. And `LCS` refuses a table over
113//! [`LCS_MAX_CELLS`], where Redis has no explicit limit and fails on the
114//! allocation instead, which on a server that has overcommitted is a kill rather
115//! than an error. And the float counters count in `f64` where Redis counts in
116//! the C `long double`, which is eighty bit on x86-64 and a hundred and twenty
117//! eight bit on aarch64, so Redis does not agree with itself across machines
118//! and we agree with ourselves everywhere.
119
120#![deny(missing_docs)]
121
122pub mod access;
123pub mod array;
124pub mod arrays;
125pub mod bitmaps;
126pub mod bits;
127pub mod blob;
128pub mod chunk;
129pub mod clock;
130pub mod cold;
131pub mod cond;
132pub mod counter;
133pub mod demote;
134pub mod elem;
135pub mod evict;
136pub mod expiry;
137pub mod foreign;
138pub mod frozen;
139pub mod geo;
140pub mod geos;
141pub mod grow;
142pub mod hash;
143pub mod hashes;
144pub mod hll;
145pub mod hlls;
146pub mod intset;
147pub mod keys;
148pub mod keyspace;
149pub mod lcs;
150pub mod list;
151pub mod listpack;
152pub mod lists;
153pub mod orderkey;
154pub mod parts;
155pub mod rank;
156pub mod rdb;
157pub mod scan;
158pub mod set;
159pub mod setops;
160pub mod sets;
161pub mod slab;
162pub mod sort;
163pub mod stream;
164pub mod streams;
165pub mod strings;
166#[cfg(test)]
167mod tally;
168pub mod tier;
169pub mod ttl;
170pub mod value;
171pub mod walk;
172pub mod zset;
173pub mod zsetops;
174pub mod zsets;
175
176pub use array::{Array, Element as ArrayElement, INDEX_MAX, SLICE_SIZE};
177pub use blob::{Blob, Span};
178pub use clock::Clock;
179pub use cond::Compare;
180pub use counter::{Counted, IncrEx, IncrExpire, Num};
181pub use elem::{Elements, Full, MAX_ROWS, NAME_MAX};
182pub use foreign::Foreign;
183pub use hash::{Hash, Limits as HashLimits};
184pub use intset::{Intset, Walk};
185pub use keys::{Moved, Record};
186pub use keyspace::Keyspace;
187pub use lcs::{Idx as LcsIdx, LCS_MAX_CELLS, Match as LcsMatch};
188pub use list::{Limits as ListLimits, List};
189pub use listpack::{Entry, Listpack, Malformed};
190pub use lists::End;
191pub use parts::{PART_MIN, PARTITION_AT, Parts};
192pub use rank::Rank;
193pub use scan::{Cursor, MAX_PARTS};
194pub use set::{Limits as SetLimits, Member, Set};
195pub use setops::Plan;
196pub use slab::{MAX_SLOTS, Slab};
197pub use strings::{Exists, Expire, KEY_MAX, STRING_MAX, SetOptions, SetOutcome};
198pub use ttl::{Applied, Ask, Cond, Deadlines, MAX_AT};
199pub use value::{EMBSTR_MAX, Encoding, Kind, Str};
200// Where a keyspace walk has got to, which is a different number from the
201// [`Cursor`] a collection walk hands back and is named apart from it so that a
202// caller holding both cannot pass one where the other was meant.
203pub use yo_index::Cursor as KeyCursor;
204pub use zset::{Bound as ZBound, Lex, Limits as ZsetLimits, Zset};
205pub use zsetops::{Aggregate, Op as ZOp, Operand};
206pub use zsets::{By, Gate, Move, Query, Window, ZAdd};
207// Which end of a sorted set a pop works from. Renamed on the way out because
208// `From` is in every Rust prelude and a second one under that name would be a
209// trap for every file that imports this crate with a glob.
210pub use zsets::From as ZEnd;