chore: Update ADR-065 (#18862)

Co-authored-by: Marko <marbar3778@yahoo.com>
Co-authored-by: Cool Developer <cool199966@outlook.com>
Co-authored-by: cool-developer <51834436+cool-develope@users.noreply.github.com>
This commit is contained in:
Aleksandr Bezobchuk
2024-01-03 16:50:47 +00:00
committed by GitHub
co-authored by Marko Cool Developer cool-developer
parent 77cbb3cd4f
commit a298594263
2 changed files with 95 additions and 93 deletions
+1 -1
View File
@@ -61,6 +61,7 @@ When writing ADRs, follow the same best practices for writing RFCs. When writing
* [ADR 060: ABCI 1.0 (Phase I)](adr-060-abci-1.0.md)
* [ADR 061: Liquid Staking](./adr-061-liquid-staking.md)
* [ADR 070: Un-Ordered Transaction Inclusion](./adr-070-unordered-account.md)
* [ADR 065: Store v2](./adr-065-store-v2.md)
### Proposed
@@ -86,7 +87,6 @@ When writing ADRs, follow the same best practices for writing RFCs. When writing
* [ADR 059: Test Scopes](./adr-059-test-scopes.md)
* [ADR 062: Collections State Layer](./adr-062-collections-state-layer.md)
* [ADR 063: Core Module API](./adr-063-core-module-api.md)
* [ADR 065: Store v2](./adr-065-store-v2.md)
* [ADR 067: Simulator v2](./adr-067-simulator-v2.md)
* [ADR 069: `x/gov` modularity, multiple choice and optimisic proposals](./adr-069-gov-improvements.md)
+94 -92
View File
@@ -3,10 +3,11 @@
## Changelog
* Feb 14, 2023: Initial Draft (@alexanderbez)
* Dec 21, 2023: Updates after implementation (@alexanderbez)
## Status
DRAFT
ACCEPTED
## Abstract
@@ -51,7 +52,7 @@ design of storage in the Cosmos SDK:
* Note, we can still allow modules to have their own IAVL `KVStore`, but the
IAVL library will need to support the ability to pass a DB instance as an
argument to various IAVL APIs.
* Since IAVL is responsible for both state storage and commitment, running an
* Since IAVL is responsible for both state storage and commitment, running an
archive node becomes increasingly expensive as disk space grows exponentially.
* As the size of a network increases, various performance bottlenecks start to
emerge in many areas such as query performance, network upgrades, state
@@ -81,73 +82,73 @@ less intrusive. Specifically, we propose to:
* Separate the concerns of state commitment (**SC**), needed for consensus, and
state storage (**SS**), needed for state machine and clients.
* Reduce layers of abstractions necessary between the RMS and underlying stores.
* Provide atomic module store commitments by providing a batch database object
to core IAVL APIs.
* Reduce complexities in the `CacheKVStore` implementation while also improving
performance<sup>[3]</sup>.
* Remove unnecessary store types and implementations such as `CacheKVStore`.
* Simplify the branching logic.
* Ensure the `RootStore` interface remains as lightweight as possible.
* Allow application developers to easily swap out SS and SC backends.
Furthermore, we will keep the IAVL is the backing [commitment](https://cryptography.fandom.com/wiki/Commitment_scheme)
store for the time being. While we might not fully settle on the use of IAVL in
Furthermore, we will keep IAVL as the default [SC](https://cryptography.fandom.com/wiki/Commitment_scheme)
backend for the time being. While we might not fully settle on the use of IAVL in
the long term, we do not have strong empirical evidence to suggest a better
alternative. Given that the SDK provides interfaces for stores, it should be sufficient
to change the backing commitment store in the future should evidence arise to
warrant a better alternative. However there is promising work being done to IAVL
that should result in significant performance improvement <sup>[1,2]</sup>.
Note, we will provide applications with the ability to use IAVL v1 and IAVL v2 as
either SC backend, with the latter showing extremely promising performance improvements
over IAVL v0 and v1, at the cost of a state migration.
### Separating SS and SC
By separating SS and SC, it will allow for us to optimize against primary use cases
and access patterns to state. Specifically, The SS layer will be responsible for
direct access to data in the form of (key, value) pairs, whereas the SC layer (IAVL)
direct access to data in the form of (key, value) pairs, whereas the SC layer (e.g. IAVL)
will be responsible for committing to data and providing Merkle proofs.
Note, the underlying physical storage database will be the same between both the
SS and SC layers. So to avoid collisions between (key, value) pairs, both layers
will be namespaced.
#### State Commitment (SC)
Given that the existing solution today acts as both SS and SC, we can simply
repurpose it to act solely as the SC layer without any significant changes to
access patterns or behavior. In other words, the entire collection of existing
IAVL-backed module `KVStore`s will act as the SC layer.
A foremost design goal is that SC backends should be easily swappable, i.e. not
necessarily IAVL. To this end, the scope of SC has been reduced, it must only:
However, in order for the SC layer to remain lightweight and not duplicate a
majority of the data held in the SS layer, we encourage node operators to keep
tight pruning strategies.
* Provide a stateful root app hash for height h resulting from applying a batch
of key-value set/deletes to height h-1.
* Fulfill (though not necessarily provide) historical proofs for all heights < h.
* Provide an API for snapshot create/restore to fulfill state sync requests.
An SC implementation may choose not to provide historical proofs past height h - n (n can be 0)
due to the time and space constraints, but since store v2 defines an API for historical
proofs there should be at least one configuration of a given SC backend which
supports this.
#### State Storage (SS)
In the RMS, we will expose a *single* `KVStore` backed by the same physical
database that backs the SC layer. This `KVStore` will be explicitly namespaced
to avoid collisions and will act as the primary storage for (key, value) pairs.
The goal of SS is to provide a modular storage backend, i.e. multiple implementations,
to facilitate storing versioned raw key/value pairs in a fast embedded database.
The responsibility and functions of SS include the following:
While we most likely will continue the use of `cosmos-db`, or some local interface,
to allow for flexibility and iteration over preferred physical storage backends
as research and benchmarking continues. However, we propose to hardcode the use
of RocksDB as the primary physical storage backend.
* Provided fast and efficient queries for versioned raw key/value pairs
* Provide versioned CRUD operations
* Provide versioned batching functionality
* Provide versioned iteration (forward and reverse) functionality
* Provide pruning functionality
Since the SS layer will be implemented as a `KVStore`, it will support the
following functionality:
All of the functionality provided by an SS backend should work under a versioned
scheme, i.e. a user should be able to get, store, and iterate over keys for the latest
and historical versions efficiently and a store key, which is used for name-spacing
purposes.
* Range queries
* CRUD operations
* Historical queries and versioning
* Pruning
We propose to have three defaulting SS backends for applications to choose from:
The RMS will keep track of all buffered writes using a dedicated and internal
`MemoryListener` for each `StoreKey`. For each block height, upon `Commit`, the
SS layer will write all buffered (key, value) pairs under a [RocksDB user-defined timestamp](https://github.com/facebook/rocksdb/wiki/User-defined-Timestamp-%28Experimental%29) column
family using the block height as the timestamp, which is an unsigned integer.
This will allow a client to fetch (key, value) pairs at historical and current
heights along with making iteration and range queries relatively performant as
the timestamp is the key suffix.
Note, we choose not to use a more general approach of allowing any embedded key/value
database, such as LevelDB or PebbleDB, using height key-prefixed keys to
effectively version state because most of these databases use variable length
keys which would effectively make actions likes iteration and range queries less
performant.
* RocksDB
* CGO based
* Usage of User-Defined Timestamps as a built-in versioning mechanism
* PebbleDB
* Native
* Manual implementation of MVCC keys for versioning
* SQLite
* CGO based
* Single table for all state
Since operators might want pruning strategies to differ in SS compared to SC,
e.g. having a very tight pruning strategy in SC while having a looser pruning
@@ -165,58 +166,61 @@ otherwise, a snapshot could be triggered on a height that is not available in SC
The state sync process should be largely unaffected by the separation of the SC
and SS layers. However, if a node syncs via state sync, the SS layer of the node
will not have the state synced height available, since the IAVL import process is
not setup in way to easily allow direct key/value insertion. A modification of
the IAVL import process would be necessary to facilitate having the state sync
height available.
not setup in way to easily allow direct key/value insertion.
Note, this is not problematic for the state machine itself because when a query
is made, the RMS will automatically direct the query correctly (see [Queries](#queries)).
We propose a simple `SnapshotManager` that consumes and produces snapshots. SC
backends will be responsible for providing a snapshot of the state at a given
height and both SS and SC consume snapshots to restore state.
#### Queries
#### RootStore
To consolidate the query routing between both the SC and SS layers, we propose to
have a notion of a "query router" that is constructed in the RMS. This query router
will be supplied to each `KVStore` implementation. The query router will route
queries to either the SC layer or the SS layer based on a few parameters. If
`prove: true`, then the query must be routed to the SC layer. Otherwise, if the
query height is available in the SS layer, the query will be served from the SS
layer. Otherwise, we fall back on the SC layer.
We will define a `RootStore` interface and default implementation that will be
the primary interface for the application to interact with. The `RootStore` will
be responsible for housing SS and SC backends. Specifically, a `RootStore` will
provide the following functionality:
If no height is provided, the SS layer will assume the latest height. The SS
layer will store a reverse index to lookup `LatestVersion -> timestamp(version)`
which is set on `Commit`.
* Manage commitment of state (both SS and SC)
* Provide modules access to state
* Query delegation (i.e. get a value for a <key, height> tuple)
* Providing commitment proofs
#### Store Keys
Naturally, if a single SC tree is used in all RootStore implementations, then the
notion of a store key becomes entirely useless. However, we cannot dictate or
predicate how all applications will implement their RooStore (if they choose to).
Since an app can choose to have multiple SC trees, we need to keep the notion of
store keys. Unlike store v1, we represent store keys as simple strings as opposed
to concrete types to provide OCAP functionality. The store key strings act to
solely provide key prefixing/namespacing functionality for modules.
#### Proofs
Since the SS layer is naturally a storage layer only, without any commitments
to (key, value) pairs, it cannot provide Merkle proofs to clients during queries.
Since the pruning strategy against the SC layer is configured by the operator,
we can therefore have the RMS route the query SC layer if the version exists and
`prove: true`. Otherwise, the query will fall back to the SS layer without a proof.
So providing inclusion and exclusion proofs, via a `CommitmentOp` type, will be
the responsibility of the SC backend. Retrieving proofs will be done through the
a `RootStore`, which will internally route the request to the SC backend.
We could explore the idea of using state snapshots to rebuild an in-memory IAVL
tree in real time against a version closest to the one provided in the query.
However, it is not clear what the performance implications will be of this approach.
#### Commitment
### Atomic Commitment
Before ABCI 2.0, specifically before `FinalizeBlock` was introduced, the flow of state
commitment in BaseApp was defined by writes being written to the `RootMultiStore`
and then a single Commit call on the `RootMultiStore` during the ABCI Commit method.
We propose to modify the existing IAVL APIs to accept a batch DB object instead
of relying on an internal batch object in `nodeDB`. Since each underlying IAVL
`KVStore` shares the same DB in the SC layer, this will allow commits to be
atomic.
With the advent of ABCI 2.0, the commitment flow has now changed to `WorkingHash` being
called during `FinalizeBlock` and then Commit being called on ABCI Commit. Note,
`WorkingHash` does not actually commit state to disk, but rather computes an
uncommitted work-in-progress hash, which is returned in `FinalizeBlock`. Then,
during the ABCI Commit phase, the state is finally flushed to disk.
Specifically, we propose to:
* Remove the `dbm.Batch` field from `nodeDB`
* Update the `SaveVersion` method of the `MutableTree` IAVL type to accept a batch object
* Update the `Commit` method of the `CommitKVStore` interface to accept a batch object
* Create a batch object in the RMS during `Commit` and pass this object to each
`KVStore`
* Write the database batch after all stores have committed successfully
Note, this will require IAVL to be updated to not rely or assume on any batch
being present during `SaveVersion`.
In store v2, we must respect this flow. Thus, a caller is expected to call `WorkingHash`
during `FinalizeBlock`, which takes the latest changeset in the `RootStore`,
writes that to the SC tree in a single batch and returns a hash. Finally, during
the ABCI Commit phase, we call `Commit` on the `RootStore` which commits the SC
tree and flushes the changeset to the SS backend.
## Consequences
@@ -252,6 +256,11 @@ be broken or modified.
### Neutral
* Removal of OCAP-based store keys in favor of simple strings for state retrieval
and name-spacing. We consider this neutral as removal of OCAP functionality can
be seen as a negative, however, we're simply moving the OCAP functionality upstream
to the KVStore service. The SS and SC layers shouldn't have to concern themselves
with OCAP responsibilities.
* Keeping IAVL as the primary commitment data structure, although drastic
performance improvements are being made
@@ -275,16 +284,9 @@ commitment proofs for historical state. While solutions can be devised such as
rebuilding trees on the fly based on state snapshots, it is not clear what the
performance implications are for such solutions.
### Physical DB Backends
This ADR proposes usage of RocksDB to utilize user-defined timestamps as a
versioning mechanism. However, other physical DB backends are available that may
offer alternative ways to implement versioning while also providing performance
improvements over RocksDB. E.g. PebbleDB supports MVCC timestamps as well, but
we'll need to explore how PebbleDB handles compaction and state growth over time.
## References
* [1] https://github.com/cosmos/iavl/pull/676
* [2] https://github.com/cosmos/iavl/pull/664
* [3] https://github.com/cosmos/cosmos-sdk/issues/14990
* [4] https://docs.google.com/document/d/e/2PACX-1vSCFfXZm2vsRsACOPoxGqysMaUg7jY833LwR3YyjA1S3FNHfXRiJor-qLjzx833TavLXLPSIcFZJhyh/pub