Skip to main content

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 db;
134pub mod demote;
135pub mod elem;
136pub mod evict;
137pub mod expiry;
138pub mod foreign;
139pub mod frozen;
140/// The geohash arithmetic, which lives one crate down so that the search
141/// index and the geo commands cover a circle in exactly the same way.
142pub use yo_common::geo;
143pub mod geos;
144pub mod grow;
145pub mod hash;
146pub mod hashes;
147pub mod hll;
148pub mod hlls;
149pub mod intset;
150pub mod keys;
151pub mod keyspace;
152pub mod lcs;
153pub mod list;
154pub mod listpack;
155pub mod lists;
156pub mod orderkey;
157pub mod parts;
158pub mod rank;
159pub mod rdb;
160pub mod scan;
161pub mod set;
162pub mod setops;
163pub mod sets;
164pub mod slab;
165pub mod snapshot;
166pub mod sort;
167pub mod stream;
168pub mod streams;
169pub mod strings;
170#[cfg(test)]
171mod tally;
172pub mod tier;
173pub mod ttl;
174pub mod value;
175pub mod walk;
176pub mod zset;
177pub mod zsetops;
178pub mod zsets;
179
180pub use array::{Array, Element as ArrayElement, INDEX_MAX, SLICE_SIZE};
181pub use blob::{Blob, Span};
182pub use clock::Clock;
183pub use cond::Compare;
184pub use counter::{Counted, IncrEx, IncrExpire, Num};
185pub use db::{Db, Holds, MAX_STRIPES};
186pub use elem::{Elements, Full, MAX_ROWS, NAME_MAX};
187pub use foreign::Foreign;
188pub use hash::{Hash, Limits as HashLimits};
189pub use intset::{Intset, Walk};
190pub use keys::{Moved, Record};
191pub use keyspace::Keyspace;
192pub use lcs::{Idx as LcsIdx, LCS_MAX_CELLS, Match as LcsMatch};
193pub use list::{Limits as ListLimits, List};
194pub use listpack::{Entry, Listpack, Malformed};
195pub use lists::{End, Movem, Order};
196pub use parts::{PART_MIN, PARTITION_AT, Parts};
197pub use rank::Rank;
198pub use scan::{Cursor, MAX_PARTS};
199pub use set::{Limits as SetLimits, Member, Set};
200pub use setops::Plan;
201pub use slab::{MAX_SLOTS, Slab};
202pub use snapshot::Snapshot;
203pub use strings::{Exists, Expire, KEY_MAX, STRING_MAX, SetOptions, SetOutcome};
204pub use ttl::{Applied, Ask, Cond, Deadlines, MAX_AT};
205pub use value::{EMBSTR_MAX, Encoding, Kind, Str};
206// Where a keyspace walk has got to, which is a different number from the
207// [`Cursor`] a collection walk hands back and is named apart from it so that a
208// caller holding both cannot pass one where the other was meant.
209pub use yo_index::Cursor as KeyCursor;
210// What arena compaction has cost, which `INFO` reports and nothing in here
211// reads, so it is only here to save the reporter a dependency on the index.
212pub use yo_index::Compaction;
213pub use zset::{Bound as ZBound, Lex, Limits as ZsetLimits, Zset};
214pub use zsetops::{Aggregate, Op as ZOp, Operand};
215pub use zsets::{By, Gate, Move, Query, Window, ZAdd};
216// Which end of a sorted set a pop works from. Renamed on the way out because
217// `From` is in every Rust prelude and a second one under that name would be a
218// trap for every file that imports this crate with a glob.
219pub use zsets::From as ZEnd;