pub struct Server { /* private fields */ }Expand description
Everything a server holds.
One per process, however many threads are serving out of it. What is inside is either shared outright, which is the counters and the settings, or behind a lock, which is the stripes and the few pieces of state a command can change. What makes this a server rather than a shard is that it is the whole of what a connection can address.
Implementations§
Source§impl Server
impl Server
Sourcepub fn waiters(&self) -> Held<'_, Waiters>
pub fn waiters(&self) -> Held<'_, Waiters>
Who is parked, for the engine walking the list.
Takes the lock for as long as the answer is held, so a caller that only
wants to know whether anybody is waiting asks Server::parked instead
and does not take it at all.
Sourcepub fn parked(&self) -> usize
pub fn parked(&self) -> usize
How many clients are parked, without taking the lock.
What INFO clients calls blocked_clients, and what every command asks
before it goes looking for somebody to wake.
Sourcepub fn forget_waiters(&self, client: u64)
pub fn forget_waiters(&self, client: u64)
Take off every waiter belonging to a client that has gone.
Sourcepub fn bind_waiter(&self, client: u64, conn: u32)
pub fn bind_waiter(&self, client: u64, conn: u32)
Say which slot the waiter this client just registered is answered on.
Sourcepub fn serve_waiter(&self, client: u64, now: u64, out: &mut Out) -> bool
pub fn serve_waiter(&self, client: u64, now: u64, out: &mut Out) -> bool
Try to answer a parked client, writing into the buffer the engine found for it, and say whether it is finished with.
The engine cannot reach the databases and this cannot reach the connections, so the two meet here: the caller hands in one connection’s reply buffer and gets back whether to unpark the client behind it.
By client and not by position, because the caller let go of the list
between finding the client and asking about it, and in that gap another
thread can take one of its own waiters off and move everything behind it
up one. A client that is no longer parked answers false, which is the
same answer as one that is parked and has nothing waiting for it.
Source§impl Server
impl Server
Sourcepub fn with_width(width: usize) -> Server
pub fn with_width(width: usize) -> Server
A server whose databases are cut into width stripes each.
Not reachable from the command line yet. Every command group answers on a server of any width now and so does everything that walks a whole database, and the tests run each group at a width of one and a width of eight and check the two agree.
What is left before this is what --threads sets is the engine. A
database being several objects is what makes more than one thread
possible, and it is not what makes more than one thread happen.
Sourcepub fn with_clock(clock: Clock) -> Server
pub fn with_clock(clock: Clock) -> Server
A server on a clock the caller moves by hand, for tests.
Sourcepub fn striped(&self, i: usize) -> &Db
pub fn striped(&self, i: usize) -> &Db
One database, by index.
A caller that knows which key it wants names the one stripe the key is
on rather than working over the whole thing, which is what at and its
neighbours on Db are for. A caller that is about a database rather
than about a key, which is the snapshot walk and a setting, works over
all of them.
The database is marked as having had something run against it, which is
what this does that Server::striped_ref does not. Anything that only
reads asks for that one and leaves the mark alone.
The borrow is shared, and what makes that enough is that a database is several stripes behind a lock each. A caller that wants to change something holds the stripe it is changing, so two threads working on two keys work at once and two working on one key take turns, which is the whole point of cutting a database up.
§Panics
If i is not a database. SELECT is the only way a client changes the
index and it checks, so an index that is out of range here is a bug in
the caller and not something a client can ask for.
Sourcepub fn set_dir(&mut self, dir: PathBuf)
pub fn set_dir(&mut self, dir: PathBuf)
Point the server at a different directory, which yodb serve --dir does.
Only before it is serving. There is no CONFIG SET dir here and there
is none on a real server either without turning protected configs on,
for the good reason that moving it out from under a running backup would
leave files nothing can find again.
Sourcepub fn backup_expire(&self)
pub fn backup_expire(&self)
Drop a sealed backup that has outlived backup-sealed-ttl.
Once per batch, from the same maintenance turn that collects the arena. It reads two fields and returns on a server that has never taken a backup, which is nearly all of them.
Sourcepub fn stop(&self)
pub fn stop(&self)
Ask for the server to stop, which is what SHUTDOWN does.
It sets a flag and returns. Nothing here closes a socket, flushes a file or ends the process, because none of those belong to this layer, and a batch that is halfway through still has to finish and be written out.
Sourcepub fn stopping(&self) -> bool
pub fn stopping(&self) -> bool
Whether somebody has asked the server to stop.
Read once per turn by the loop, next to the flag a signal sets. The two mean the same thing and are separate only because one arrives from the operating system and the other from a client.
Sourcepub fn striped_ref(&self, i: usize) -> &Db
pub fn striped_ref(&self, i: usize) -> &Db
One database, by index, without taking it mutably.
What the prefetch stage needs. It runs for all 64 commands in a batch
before any of them executes, so it cannot hold the mutable borrow run
is about to want, and it does not need one: warming a cache line reads
nothing and changes nothing.
Sourcepub fn refresh_clock(&self)
pub fn refresh_clock(&self)
Take a new clock reading, which every database is looking at.
Once per turn of the event loop, which is the only place time moves. A
command asking what the time is gets the answer the whole batch got, so
two keys written by the same batch expire together (04 section 3).
Every thread does this on every turn of its own loop and they do not have to agree about when. The reading is only stored when the millisecond has changed, so what the threads are sharing is a line that is written about a thousand times a second and read millions.
Sourcepub fn advance_clock_ms(&self, ms: u64)
pub fn advance_clock_ms(&self, ms: u64)
Move every clock here on by ms, for tests about expiry.
The same thing Server::set_clock_ms does and by the same argument,
except that it moves from wherever the clock is rather than to a stated
moment, which is what a test that wants a key to have expired asks for.
Sourcepub fn set_clock_ms(&self, ms: u64)
pub fn set_clock_ms(&self, ms: u64)
Move every clock here to ms by hand, for tests about expiry.
A test cannot wait a hundred seconds and a test that waits a hundred
milliseconds is a test that fails on a loaded machine, so time moves on
request. The system clock underneath will overwrite this on the next
Server::refresh_clock, which is why this is only useful in a test
that drives commands directly rather than through the event loop.
Sourcepub fn uptime_secs(&self) -> u64
pub fn uptime_secs(&self) -> u64
Seconds since this server was built.
Sourcepub fn memory_bytes(&self) -> usize
pub fn memory_bytes(&self) -> usize
Bytes held by every database’s index and arena, plus the read and reply buffers of every connection.
The buffers are in here because they are real and because Redis counts its own, so leaving them out would make the one number people compare flattering rather than true. They are not a database, so nothing in the keyspace can change them and the engine has to say when they move.
Sourcepub fn dataset_bytes(&self) -> usize
pub fn dataset_bytes(&self) -> usize
What the keyspace itself is holding, live records only.
used_memory minus this is what the store costs to run: the index, the
space dead records are sitting in until compaction gets to them, and the
connections’ buffers.
Sourcepub fn arena_bytes(&self) -> usize
pub fn arena_bytes(&self) -> usize
Bytes the arenas are holding, live and dead together.
Sourcepub fn index_bytes(&self) -> usize
pub fn index_bytes(&self) -> usize
Bytes the indexes are holding.
Sourcepub fn compaction(&self) -> Compaction
pub fn compaction(&self) -> Compaction
What arena compaction has cost, across every database.
The write amplification of value separation, which is invisible from the outside otherwise: a client that writes a megabyte can leave the store copying several more, and the only sign of it without these is that the writes got slower.
Sourcepub fn segment_count(&self) -> usize
pub fn segment_count(&self) -> usize
Arena segments whose pages are real, across every database.
Sourcepub fn conn_bytes(&self) -> usize
pub fn conn_bytes(&self) -> usize
What the connections’ read and reply buffers are holding.
Sourcepub fn note_conn_bytes(&self, delta: isize)
pub fn note_conn_bytes(&self, delta: isize)
Note that the connections are holding delta bytes more than they were,
or fewer when it is negative.
A delta and not a total because the alternative is a walk over every
connection, and the walk would have to happen on a turn of the loop
rather than when INFO asks, which puts the cost of a report on the
command path of a server nobody is asking.
Sourcepub fn expired_keys(&self) -> u64
pub fn expired_keys(&self) -> u64
Keys reclaimed by running into them after their deadline.
Sourcepub fn evicted_keys(&self) -> u64
pub fn evicted_keys(&self) -> u64
Keys thrown away to make room, which is the other number entirely.
Sourcepub fn command_stats(&self) -> impl Iterator<Item = (&'static str, CommandStat)>
pub fn command_stats(&self) -> impl Iterator<Item = (&'static str, CommandStat)>
Every command that has been seen, with its counters.
Only the ones that have. A server reports a handful of lines rather than one per command in the table, which is what Redis does and is the difference between a section a person can read and one they cannot.
Sourcepub fn counted(&self) -> &Stats
pub fn counted(&self) -> &Stats
The counters the calling thread writes into.
The first call on a thread claims a set and every call after it is a
thread local read and an index. A server asked to count from more threads
than it was built for wraps round and shares a set, which loses the odd
count between two threads and cannot happen to a server yodb serve
built, because that one is told how many threads it will have before it
starts any of them.
Sourcepub fn next_client(&self) -> u64
pub fn next_client(&self) -> u64
The next client id, taken.
Every accept anywhere on this server comes through here, so no two clients share a number however many threads are accepting.
Sourcepub fn my_slot(&self) -> usize
pub fn my_slot(&self) -> usize
Which set of per thread state the calling thread is on.
The number a blocked client is filed under, so that the thread holding
that client’s connection is the one that answers it. Claims a set on the
first call the same way Server::counted does, and gives back the same
number every time after.
Sourcepub fn totals(&self) -> Totals
pub fn totals(&self) -> Totals
Every thread’s numbers added together, which is what INFO reports.
Sourcepub fn reset_stats(&self)
pub fn reset_stats(&self)
Put the totals back to zero, which is CONFIG RESETSTAT.
Every thread’s set and not only the one asking, since the number the client is resetting is the sum it was just shown. The open connections are left alone because that is a gauge and not a total: the connections are still open.
Sourcepub fn set_threads(&mut self, threads: usize)
pub fn set_threads(&mut self, threads: usize)
Say how many threads will run commands here, before any of them does.
What it changes is how many sets of counters there are. Called once at startup by whoever is about to start the threads, and calling it on a running server throws away what has been counted so far, which is why it wants the server to itself.
Sourcepub fn set_maxmemory(&self, bytes: u64)
pub fn set_maxmemory(&self, bytes: u64)
Set the limit, and take a reading straight away.
The reading is here rather than left to the next maintenance turn because a client that sets the limit and sends a write in the same batch expects the write to be judged against the limit it just set, and because the cached number is meaningless until the first time there is a limit to compare it with.
Turning the limit on also turns on the running total every slab keeps of what its collections hold, and turning it off turns that back off, so a server with no limit is not paying to count something nobody reads. The first reading after switching it on is the walk that the total starts from, and it is the only walk.
Sourcepub fn set_store_source(
&mut self,
source: impl FnMut(usize) -> Option<Store> + Send + 'static,
)
pub fn set_store_source( &mut self, source: impl FnMut(usize) -> Option<Store> + Send + 'static, )
Say where a database should get its store from when it needs one.
This is what turns the eviction inversion on. Until it is called every database answers a memory limit by evicting, which is Redis, and after it is called a database under memory pressure moves values to whatever the closure hands back instead of throwing keys away.
Called at most once per database and only under pressure, so a server that is given a file and never fills memory never touches it.
Sourcepub fn has_store_source(&self) -> bool
pub fn has_store_source(&self) -> bool
Whether this server has been given somewhere to put cold values.
Sourcepub fn set_maxstore(&self, bytes: Option<u64>)
pub fn set_maxstore(&self, bytes: Option<u64>)
Set the storage limit, or clear it with None.
Nothing is read here the way Server::set_maxmemory reads the memory
total, because this limit is compared against a number the store keeps
and answers on demand, not against a walk.
Sourcepub fn store_bytes(&self) -> u64
pub fn store_bytes(&self) -> u64
What every attached store is holding, for INFO memory.
Zero on a server with nothing attached, which is not the same as a server
whose file is empty, and Server::regime is the field that tells those
two apart.
Sourcepub fn cold_stats(&self) -> Stats
pub fn cold_stats(&self) -> Stats
What the file has been asked to do, added up over every database.
Counters and not levels, so they only ever go up and a run is the difference between two readings. G9 is a ratio over these: the faults a run took, divided by the point reads it issued, has to come out at 1.05 or less with a working set ten times memory. There is no way to work that out from outside the server, so it is reported rather than inferred.
A fault is a read that went to the store. Whether it also went to the device depends on the store: a log serves a read out of a resident page without touching anything. At ten times memory almost every fault is a real read, which is why the gate is written against this number, but the two are not the same thing and a run tight against the bar should be checked against what the operating system says.
Sourcepub fn regime(&self) -> &'static str
pub fn regime(&self) -> &'static str
Which way this server answers a memory limit, in one word for INFO.
evict is Redis: a memory limit throws keys away. migrate is the
inversion: a memory limit moves values to the file and nothing stored is
lost. A server reports one word rather than leaving an operator to work
it out from a limit, a setting and whether a file happens to be open.
Sourcepub fn refresh_memory(&self)
pub fn refresh_memory(&self)
Take a fresh memory reading, which the maintenance turn does once a batch.
Nothing at all when there is no limit, which is the default and is every server that has not asked for one.
Sourcepub fn make_room(&self) -> bool
pub fn make_room(&self) -> bool
Make room under the maxmemory limit, throwing keys away if that is what
it takes. Answers whether there is anything left it could throw away.
Redis runs the same thing from processCommand before every command and
so does this: a client that writes has to be judged at the moment it
writes, not a batch later, or the limit is a suggestion.
Three things happen in the loop and all three are needed. Eviction picks a key and drops it. Compaction gives the pages back, because dropping a key marks its record dead and returns nothing on its own, so a loop that only evicted would throw the whole keyspace away and watch the number stay where it was. The reading is taken again each time round, because the two of them together are the only thing that moves it.
§Why running out of budget is not a no
false means there was nothing left to evict, which is noeviction, or
a volatile policy on a database where nothing has a deadline, or a
keyspace that is already empty. It does not mean the server is still over
its limit, and that difference is Redis’s: performEvictions answers
EVICT_FAIL only when it has run out of things to delete, and
processCommand refuses the client on that and on nothing else. Running
out of time part way through a job it is doing well comes back as
EVICT_RUNNING and the command goes through, because a server that is
evicting steadily and refusing every write while it does it is worse for
the client than a little overshoot.
§What the limit is worth
Space comes back a segment at a time and a segment is two megabytes, so
this holds a server to its limit give or take a segment. A maxmemory of
a few hundred megabytes gets what it asked for. A maxmemory of four
megabytes is asking for a precision this store does not have.
Sourcepub fn expire_slice(&self, budget: usize) -> usize
pub fn expire_slice(&self, budget: usize) -> usize
The sweep the shard loop calls, at most once a millisecond.
The gate is the whole difference between this and Server::expire_step.
A maintenance slice runs on every turn of the loop and a turn is a
hundred nanoseconds, so an ungated sweep would draw a fresh sample ten
thousand times per millisecond and spend a real share of the shard on
looking for keys that cannot have died since the last look. Nothing in a
database changes fast enough to be worth asking about more often than the
clock can tell the difference, and the clock here is milliseconds.
A millisecond is also far finer than Redis, whose slow cycle runs at ten hertz, so this is not the thing that decides how promptly memory comes back. What it decides is that an idle server sweeps a thousand times a second rather than a million.
Sourcepub fn expire_step(&self, budget: usize) -> usize
pub fn expire_step(&self, budget: usize) -> usize
Sweep dead keys out of the databases, spending at most budget looks.
Answers what it spent, so the caller can charge its maintenance slice for
it. See yo_kv::expiry for why the budget is in keys looked at.
Round robin from its own cursor, and every database gets offered whatever is left of the budget rather than a sixteenth of it each, so a server on database zero only, which is nearly every server, spends the whole slice where the keys are. The fifteen empty ones cost a comparison apiece because a database with no key carrying a deadline says so without drawing anything.
The cursor moves to the database after whichever one did the work, so two busy databases take turns instead of the lower numbered one starving the other.
Sourcepub fn compact_step(&self) -> Option<usize>
pub fn compact_step(&self) -> Option<usize>
Give one database’s dead space back, if any database has enough of it to
be worth the move. None when no database had a candidate.
Once per batch, next to the clock. Overwriting a key writes a new record and counts the old one dead, so without this a server holds everything it has ever written: 400000 sets over 100000 keys measured at 742 bytes a key against Redis at 144 for the same load, and the whole difference was dead records nothing ever came back for.
At most one segment moves per call and the search starts one database further along each time, so the cost of asking is a comparison per database and the cost of acting is bounded by a segment.