github.com/cosmos/cosmos-sdk@v0.50.10/docs/spec/store/interblock-cache.md (about)

     1  # Inter-block Cache
     2  
     3  * [Inter-block Cache](#inter-block-cache)
     4      * [Synopsis](#synopsis)
     5      * [Overview and basic concepts](#overview-and-basic-concepts)
     6          * [Motivation](#motivation)
     7          * [Definitions](#definitions)
     8      * [System model and properties](#system-model-and-properties)
     9          * [Assumptions](#assumptions)
    10          * [Properties](#properties)
    11              * [Thread safety](#thread-safety)
    12              * [Crash recovery](#crash-recovery)
    13              * [Iteration](#iteration)
    14      * [Technical specification](#technical-specification)
    15          * [General design](#general-design)
    16          * [API](#api)
    17              * [CommitKVCacheManager](#commitkvcachemanager)
    18              * [CommitKVStoreCache](#commitkvstorecache)
    19          * [Implementation details](#implementation-details)
    20      * [History](#history)
    21      * [Copyright](#copyright)
    22  
    23  ## Synopsis
    24  
    25  The inter-block cache is an in-memory cache storing (in-most-cases) immutable state that modules need to read in between blocks. When enabled, all sub-stores of a multi store, e.g., `rootmulti`, are wrapped.
    26  
    27  ## Overview and basic concepts
    28  
    29  ### Motivation
    30  
    31  The goal of the inter-block cache is to allow SDK modules to have fast access to data that it is typically queried during the execution of every block. This is data that do not change often, e.g. module parameters. The inter-block cache wraps each `CommitKVStore` of a multi store such as `rootmulti` with a fixed size, write-through cache. Caches are not cleared after a block is committed, as opposed to other caching layers such as `cachekv`.
    32  
    33  ### Definitions
    34  
    35  * `Store key` uniquely identifies a store.
    36  * `KVCache` is a `CommitKVStore` wrapped with a cache.
    37  * `Cache manager` is a key component of the inter-block cache responsible for maintaining a map from `store keys` to `KVCaches`.
    38  
    39  ## System model and properties
    40  
    41  ### Assumptions
    42  
    43  This specification assumes that there exists a cache implementation accessible to the inter-block cache feature.
    44  
    45  > The implementation uses adaptive replacement cache (ARC), an enhancement over the standard last-recently-used (LRU) cache in that tracks both frequency and recency of use.
    46  
    47  The inter-block cache requires that the cache implementation to provide methods to create a cache, add a key/value pair, remove a key/value pair and retrieve the value associated to a key. In this specification, we assume that a `Cache` feature offers this functionality through the following methods:
    48  
    49  * `NewCache(size int)` creates a new cache with `size` capacity and returns it.
    50  * `Get(key string)` attempts to retrieve a key/value pair from `Cache.` It returns `(value []byte, success bool)`. If `Cache` contains the key, it `value` contains the associated value and `success=true`. Otherwise, `success=false` and `value` should be ignored.
    51  * `Add(key string, value []byte)` inserts a key/value pair into the `Cache`.
    52  * `Remove(key string)` removes the key/value pair identified by `key` from `Cache`.
    53  
    54  The specification also assumes that `CommitKVStore` offers the following API:
    55  
    56  * `Get(key string)` attempts to retrieve a key/value pair from `CommitKVStore`.
    57  * `Set(key, string, value []byte)` inserts a key/value pair into the `CommitKVStore`.
    58  * `Delete(key string)` removes the key/value pair identified by `key` from `CommitKVStore`.
    59  
    60  > Ideally, both `Cache` and `CommitKVStore` should be specified in a different document and referenced here.
    61  
    62  ### Properties
    63  
    64  #### Thread safety
    65  
    66  Accessing the `cache manager` or a `KVCache` is not thread-safe: no method is guarded with a lock.
    67  Note that this is true even if the cache implementation is thread-safe.
    68  
    69  > For instance, assume that two `Set` operations are executed concurrently on the same key, each writing a different value. After both are executed, the cache and the underlying store may be inconsistent, each storing a different value under the same key.
    70  
    71  #### Crash recovery
    72  
    73  The inter-block cache transparently delegates `Commit()` to its aggregate `CommitKVStore`. If the 
    74  aggregate `CommitKVStore` supports atomic writes and use them to guarantee that the store is always in a consistent state in disk, the inter-block cache can be transparently moved to a consistent state when a failure occurs.
    75  
    76  > Note that this is the case for `IAVLStore`, the preferred `CommitKVStore`. On commit, it calls `SaveVersion()` on the underlying `MutableTree`. `SaveVersion` writes to disk are atomic via batching. This means that only consistent versions of the store (the tree) are written to the disk. Thus, in case of a failure during a `SaveVersion` call, on recovery from disk, the version of the store will be consistent.
    77  
    78  #### Iteration
    79  
    80  Iteration over each wrapped store is supported via the embedded `CommitKVStore` interface.
    81  
    82  ## Technical specification
    83  
    84  ### General design
    85  
    86  The inter-block cache feature is composed by two components: `CommitKVCacheManager` and `CommitKVCache`.
    87  
    88  `CommitKVCacheManager` implements the cache manager. It maintains a mapping from a store key to a `KVStore`.
    89  
    90  ```go
    91  type CommitKVStoreCacheManager interface{
    92      cacheSize uint
    93      caches map[string]CommitKVStore
    94  }
    95  ```
    96  
    97  `CommitKVStoreCache` implements a `KVStore`: a write-through cache that wraps a `CommitKVStore`. This means that deletes and writes always happen to both the cache and the underlying `CommitKVStore`. Reads on the other hand first hit the internal cache. During a cache miss, the read is delegated to the underlying `CommitKVStore` and cached.
    98  
    99  ```go
   100  type CommitKVStoreCache interface{
   101      store CommitKVStore
   102      cache Cache
   103  }
   104  ```
   105  
   106  To enable inter-block cache on `rootmulti`, one needs to instantiate a `CommitKVCacheManager` and set it by calling `SetInterBlockCache()` before calling one of `LoadLatestVersion()`, `LoadLatestVersionAndUpgrade(...)`, `LoadVersionAndUpgrade(...)` and `LoadVersion(version)`.
   107  
   108  ### API
   109  
   110  #### CommitKVCacheManager
   111  
   112  The method `NewCommitKVStoreCacheManager` creates a new cache manager and returns it.
   113  
   114  | Name  | Type | Description |
   115  | ------------- | ---------|------- |
   116  | size  | integer | Determines the capacity of each of the KVCache maintained by the manager |
   117  
   118  ```go
   119  func NewCommitKVStoreCacheManager(size uint) CommitKVStoreCacheManager {
   120      manager = CommitKVStoreCacheManager{size, make(map[string]CommitKVStore)}
   121      return manager
   122  }
   123  ```
   124  
   125  `GetStoreCache` returns a cache from the CommitStoreCacheManager for a given store key. If no cache exists for the store key, then one is created and set.
   126  
   127  | Name  | Type | Description |
   128  | ------------- | ---------|------- |
   129  | manager  | `CommitKVStoreCacheManager` | The cache manager |
   130  | storeKey  | string | The store key of the store being retrieved |
   131  | store  | `CommitKVStore` | The store that it is cached in case the manager does not have any in its map of caches |
   132  
   133  ```go
   134  func GetStoreCache(
   135      manager CommitKVStoreCacheManager,
   136      storeKey string,
   137      store CommitKVStore) CommitKVStore {
   138  
   139      if manager.caches.has(storeKey) {
   140          return manager.caches.get(storeKey)
   141      } else {
   142          cache = CommitKVStoreCacheManager{store, manager.cacheSize}
   143          manager.set(storeKey, cache)
   144          return cache
   145      }
   146  }
   147  ```
   148  
   149  `Unwrap` returns the underlying CommitKVStore for a given store key.
   150  
   151  | Name  | Type | Description |
   152  | ------------- | ---------|------- |
   153  | manager  | `CommitKVStoreCacheManager` | The cache manager |
   154  | storeKey  | string | The store key of the store being unwrapped |
   155  
   156  ```go
   157  func Unwrap(
   158      manager CommitKVStoreCacheManager,
   159      storeKey string) CommitKVStore {
   160  
   161      if manager.caches.has(storeKey) {
   162          cache = manager.caches.get(storeKey)
   163          return cache.store
   164      } else {
   165          return nil
   166      }
   167  }
   168  ```
   169  
   170  `Reset` resets the manager's map of caches.
   171  
   172  | Name  | Type | Description |
   173  | ------------- | ---------|------- |
   174  | manager  | `CommitKVStoreCacheManager` | The cache manager |
   175  
   176  ```go
   177  function Reset(manager CommitKVStoreCacheManager) {
   178  
   179      for (let storeKey of manager.caches.keys()) {
   180          manager.caches.delete(storeKey)
   181      }
   182  }
   183  ```
   184  
   185  #### CommitKVStoreCache
   186  
   187  `NewCommitKVStoreCache` creates a new `CommitKVStoreCache` and returns it.
   188  
   189  | Name  | Type | Description |
   190  | ------------- | ---------|------- |
   191  | store  | CommitKVStore | The store to be cached |
   192  | size  | string | Determines the capacity of the cache being created |
   193  
   194  ```go
   195  func NewCommitKVStoreCache(
   196      store CommitKVStore,
   197      size uint) CommitKVStoreCache {
   198      KVCache = CommitKVStoreCache{store, NewCache(size)}
   199      return KVCache
   200  }
   201  ```
   202  
   203  `Get` retrieves a value by key. It first looks in the cache. If the key is not in the cache, the query is delegated to the underlying `CommitKVStore`. In the latter case, the key/value pair is cached. The method returns the value.
   204  
   205  | Name  | Type | Description |
   206  | ------------- | ---------|------- |
   207  | KVCache  | `CommitKVStoreCache` | The `CommitKVStoreCache` from which the key/value pair is retrieved  |
   208  | key  | string | Key of the key/value pair being retrieved |
   209  
   210  ```go
   211  func Get(
   212      KVCache CommitKVStoreCache,
   213      key string) []byte {
   214      valueCache, success := KVCache.cache.Get(key)
   215      if success {
   216          // cache hit
   217          return valueCache
   218      } else {
   219          // cache miss
   220          valueStore = KVCache.store.Get(key)
   221          KVCache.cache.Add(key, valueStore)
   222          return valueStore
   223      }
   224  }
   225  ```
   226  
   227  `Set` inserts a key/value pair into both the write-through cache and the underlying `CommitKVStore`.
   228  
   229  | Name  | Type | Description |
   230  | ------------- | ---------|------- |
   231  | KVCache  | `CommitKVStoreCache` | The `CommitKVStoreCache` to which the key/value pair is inserted |
   232  | key  | string | Key of the key/value pair being inserted |
   233  | value  | []byte | Value of the key/value pair being inserted |
   234  
   235  ```go
   236  func Set(
   237      KVCache CommitKVStoreCache,
   238      key string,
   239      value []byte) {
   240  
   241      KVCache.cache.Add(key, value)
   242      KVCache.store.Set(key, value)
   243  }
   244  ```
   245  
   246  `Delete` removes a key/value pair from both the write-through cache and the underlying `CommitKVStore`.
   247  
   248  | Name  | Type | Description |
   249  | ------------- | ---------|------- |
   250  | KVCache  | `CommitKVStoreCache` | The `CommitKVStoreCache` from which the key/value pair is deleted |
   251  | key  | string | Key of the key/value pair being deleted |
   252  
   253  ```go
   254  func Delete(
   255      KVCache CommitKVStoreCache,
   256      key string) {
   257  
   258      KVCache.cache.Remove(key)
   259      KVCache.store.Delete(key)
   260  }
   261  ```
   262  
   263  `CacheWrap` wraps a `CommitKVStoreCache` with another caching layer (`CacheKV`). 
   264  
   265  > It is unclear whether there is a use case for `CacheWrap`. 
   266  
   267  | Name  | Type | Description |
   268  | ------------- | ---------|------- |
   269  | KVCache  | `CommitKVStoreCache` | The `CommitKVStoreCache` being wrapped |
   270  
   271  ```go
   272  func CacheWrap(
   273      KVCache CommitKVStoreCache) {
   274       
   275      return CacheKV.NewStore(KVCache)
   276  }
   277  ```
   278  
   279  ### Implementation details
   280  
   281  The inter-block cache implementation uses a fixed-sized adaptive replacement cache (ARC) as cache. [The ARC implementation](https://github.com/hashicorp/golang-lru/blob/master/arc.go) is thread-safe. ARC is an enhancement over the standard LRU cache in that tracks both frequency and recency of use. This avoids a burst in access to new entries from evicting the frequently used older entries. It adds some additional tracking overhead to a standard LRU cache, computationally it is roughly `2x` the cost, and the extra memory overhead is linear with the size of the cache. The default cache size is `1000`.
   282  
   283  ## History
   284  
   285  Dec 20, 2022 - Initial draft finished and submitted as a PR
   286  
   287  ## Copyright
   288  
   289  All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).