* update WIP list
* prefer local max_frame_no
this assumes that cache is always up to date and uses that instead
of calling the storage server. later we will add replication and make
sure that cache is upto date.
* Prefer local reads and also save page versions locally
Let's store `page_no` along with the `frame_no`. And then while reading,
we will do a range read, read frame which is less than txn's
`max_frame_no`
* minor improvements: index creation, fix max_frame_num query
* update local cache on inserts
* avoid unnecessary call to ss in `find_frame`
* remove unused `get_frame`
* eliminiate all calls to storage server during read path
- Changed transaction table to use `(txn_id, page_no)` as composite
primary key
- Updated `insert_frame` to ignore unique constraint violation since
frames are immutable
- Change `insert_page` method to upsert
* Add basic tonic error handling
Added a simple error to handle `WriteConflict`s
* Improve the way we handle Foundation DB keys
Foundation DB keys support tuples. Earlier we used plain strings. For
example to store page 5, which had frame no 8, we did `ns/f/5/8`.
However this breaks lexicographic sorting. For e.g. we want `ns/f/5/8`
to be smaller than `ns/f/5/18`, but since we store as a plain string
the comparison breaks.
So we let FDB handle encoding with the `pack` API which encodes the
key properly as tuple `(ns, f, 5, 8)`
* Send `max_frame_no` while doing inserts and find frame
Whenever a txn is started, we keep the current `max_frame_no` in the
transaction's state. For the subsequent find and insert requests, we
send this value to storage server.
For a read txn, it should not read any frames greater than it's
max_frame_no. For a write txn, if the max_frame_no has changed
meanwhile, then txn should be aborted.
* Allow multiple writers at the storage server
This patch has two important changes:
- Storage server now uses `max_frame_no` from the txn to read frames
It will make sure to not read any frames beyond the `max_frame_no`
of the transaction. This fixes the bug in isolation level.
- Storage server now checks `max_frame_no` before inserting. If it
has changed meanwhile, then it rejects the insertion request.
This lets us have multiple (non concurrent) writers or even multiple
logical primary instances.
* minor cargo fmt fixes
* replace in-memory cache with local sqlite db
Previously, `libsql-storage` stored all the data in-memory. It kept a
in-memory frame cache and also cached all the transient transaction
writes in-memory. This patch changes it to store the data on local disk.
This data is transient in nature, disk loss should not cause any issues.
* fix: remove unnecessary `namespace` column in local cache
Each namespace gets its own local cache. So there is no need of
namespace column at all.
* Add some explainer about how we use the local cache
We use LocalCache to cache frames and transaction state. Each namespace gets its own cache
which is currently stored in a SQLite DB file, along with the main database file.
Frames Cache:
Frames are immutable. So we can cache all the frames locally, and it does not require them
to be fetched from the storage server. We cache the frame data with frame_no being the key.
Transaction State:
Whenever a transaction reads any pages from storage server, we cache them in the transaction
state. Since we want to provide a consistent view of the database, for the next reads we can
serve the pages from the cache. Any writes a transaction makes are cached too. At the time of
commit they are removed from the cache and sent to the storage server.
* Update proto to send `max_frame_no` while inserting frames
We plan to allow multiple (non concurrent) writers. If a transaction's
`max_frame_no` does not match the server's `max_frame_no`, then it
has missed some new writes and we can abort the transaction
* Updates storage trait's insert_frames definition
- Change parameter type `FrameData` to `Frame` from rpc def, to
avoid unnecessary copying
- Take `max_frame_no` param
* Use `insert_frames` method
* cleanup: remove `insert_frame` method and impl
* Remove `FrameData`, use `Frame` from proto instead
* Add storage server RPC client
* Address review comments
- Use `#[tracing::instrument]` wherever appropriate
- Remove `Mutex` for `client` and `clone` it wherever required
- Avoid reading env variable inside the lib, rather take a config object
- Make the private methods of `DurableWal` async
- Don't try to create a runtime, instead assume it always exists. Let the caller create it
- Update proto:
- remove `max_frame_no` from `InsertFramesRequest`
- make `page_no` to `u32` in the proto
- Update storage server to have `page_no` as `u32`