Skip to main content

linera_storage/
migration.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use linera_base::{
5    crypto::CryptoHash,
6    identifiers::{BlobId, ChainId, EventId},
7};
8use linera_views::{
9    batch::Batch,
10    store::{KeyValueDatabase, KeyValueStore, ReadableKeyValueStore, WritableKeyValueStore},
11    ViewError,
12};
13use serde::{Deserialize, Serialize};
14use tokio::time::Duration;
15
16use crate::{
17    db_storage::{
18        to_event_key, DbStorage, MultiPartitionBatch, RootKey, BLOB_KEY, BLOB_STATE_KEY, BLOCK_KEY,
19        LITE_CERTIFICATE_KEY, NETWORK_DESCRIPTION_KEY,
20    },
21    Clock,
22};
23
24#[derive(Debug)]
25enum SchemaVersion {
26    /// No schema version detected.
27    Uninitialized,
28    /// Version 0. All the blobs, certificates, confirmed blocks, events and network
29    /// description are on the same partition.
30    Version0,
31    /// Version 1. New partitions are assigned by chain ID, crypto hash, and blob ID.
32    Version1,
33}
34
35/// How long we should wait (in minutes) before retrying when we detect another migration
36/// in progress.
37const MIGRATION_WAIT_BEFORE_RETRY_MIN: u64 = 3;
38
39const UNUSED_EMPTY_KEY: &[u8] = &[];
40// We choose the ordering of the variants in `BaseKey` and `RootKey` so that
41// the root keys corresponding to `ChainState` and `BlockExporter` remain the
42// same in their serialization.
43// This implies that data on those root keys do not need to be moved.
44// For other tag variants, that are on the shared partition, we need to move
45// the data into their own partitions.
46const MOVABLE_KEYS_0_1: &[u8] = &[1, 2, 3, 4, 5, 7];
47
48/// The total number of keys being migrated in a block.
49/// We use chunks to avoid OOM.
50const BLOCK_KEY_SIZE: usize = 90;
51
52#[derive(Debug, Serialize, Deserialize)]
53enum BaseKey {
54    ChainState(ChainId),
55    Certificate(CryptoHash),
56    ConfirmedBlock(CryptoHash),
57    Blob(BlobId),
58    BlobState(BlobId),
59    Event(EventId),
60    BlockExporterState(u32),
61    NetworkDescription,
62}
63
64// We map a serialized `BaseKey` in the shared partition to a serialized `RootKey`
65// and key in the new schema. For `ChainState` and `BlockExporterState`, there is
66// no need to move so we use `UNUSED_EMPTY_KEY`.
67fn map_base_key(base_key: &[u8]) -> Result<(Vec<u8>, Vec<u8>), ViewError> {
68    let base_key = bcs::from_bytes::<BaseKey>(base_key)?;
69    match base_key {
70        BaseKey::ChainState(chain_id) => {
71            let root_key = RootKey::ChainState(chain_id).bytes();
72            Ok((root_key, UNUSED_EMPTY_KEY.to_vec()))
73        }
74        BaseKey::Certificate(hash) => {
75            let root_key = RootKey::ConfirmedBlock(hash).bytes();
76            Ok((root_key, LITE_CERTIFICATE_KEY.to_vec()))
77        }
78        BaseKey::ConfirmedBlock(hash) => {
79            let root_key = RootKey::ConfirmedBlock(hash).bytes();
80            Ok((root_key, BLOCK_KEY.to_vec()))
81        }
82        BaseKey::Blob(blob_id) => {
83            let root_key = RootKey::Blob(blob_id).bytes();
84            Ok((root_key, BLOB_KEY.to_vec()))
85        }
86        BaseKey::BlobState(blob_id) => {
87            let root_key = RootKey::Blob(blob_id).bytes();
88            Ok((root_key, BLOB_STATE_KEY.to_vec()))
89        }
90        BaseKey::Event(event_id) => {
91            let root_key = RootKey::Event(event_id.chain_id).bytes();
92            let key = to_event_key(&event_id);
93            Ok((root_key, key))
94        }
95        BaseKey::BlockExporterState(index) => {
96            let root_key = RootKey::BlockExporterState(index).bytes();
97            Ok((root_key, UNUSED_EMPTY_KEY.to_vec()))
98        }
99        BaseKey::NetworkDescription => {
100            let root_key = RootKey::NetworkDescription.bytes();
101            Ok((root_key, NETWORK_DESCRIPTION_KEY.to_vec()))
102        }
103    }
104}
105
106impl<Database, C> DbStorage<Database, C>
107where
108    Database: KeyValueDatabase + Clone + Send + Sync + 'static,
109    Database::Store: KeyValueStore + Clone + Send + Sync + 'static,
110    C: Clock + Clone + Send + Sync + 'static,
111    Database::Error: From<bcs::Error> + Send + Sync,
112{
113    async fn migrate_shared_partition(
114        &self,
115        first_byte: &u8,
116        keys: Vec<Vec<u8>>,
117    ) -> Result<(), ViewError> {
118        tracing::info!(
119            "Migrating {} keys of shared DB partition starting with {first_byte}",
120            keys.len()
121        );
122        for (index, chunk_keys) in keys.chunks(BLOCK_KEY_SIZE).enumerate() {
123            tracing::info!("Processing chunk {index} of size {}", chunk_keys.len());
124            let chunk_base_keys = chunk_keys
125                .iter()
126                .map(|key| {
127                    let mut base_key = vec![*first_byte];
128                    base_key.extend(key);
129                    base_key
130                })
131                .collect::<Vec<Vec<u8>>>();
132            let store = self.database.open_shared(&[])?;
133            let values = store.read_multi_values_bytes(&chunk_base_keys).await?;
134            let mut batch = MultiPartitionBatch::new();
135            for (base_key, value) in chunk_base_keys.iter().zip(values) {
136                let value = value.ok_or_else(|| ViewError::MissingEntries("migration".into()))?;
137                let (root_key, key) = map_base_key(base_key)?;
138                batch.put_key_value(root_key, key, value);
139            }
140            self.write_batch(batch).await?;
141            // Now delete the keys
142            let mut batch = Batch::new();
143            for key in chunk_base_keys {
144                batch.delete_key(key.to_vec());
145            }
146            store.write_batch(batch).await?;
147        }
148        Ok(())
149    }
150
151    async fn migrate_v0_to_v1(&self) -> Result<(), ViewError> {
152        for first_byte in MOVABLE_KEYS_0_1 {
153            let store = self.database.open_shared(&[])?;
154            let keys = store.find_keys_by_prefix(&[*first_byte]).await?;
155            self.migrate_shared_partition(first_byte, keys).await?;
156        }
157        Ok(())
158    }
159
160    /// Migrates the storage to the latest schema version if it is out of date.
161    pub async fn migrate_if_needed(&self) -> Result<(), ViewError> {
162        loop {
163            if matches!(
164                self.get_storage_state().await?,
165                SchemaVersion::Uninitialized | SchemaVersion::Version1
166            ) {
167                // Nothing to do.
168                return Ok(());
169            }
170            let result = self.migrate_v0_to_v1().await;
171            if let Err(ViewError::MissingEntries(_)) = result {
172                tracing::warn!(
173                    "It looks like a migration is already in progress on this database. \
174                     I will wait for {:?} minutes and retry.",
175                    MIGRATION_WAIT_BEFORE_RETRY_MIN
176                );
177                // Duration::from_mins is not yet stable for tokio 1.36.
178                tokio::time::sleep(Duration::from_secs(MIGRATION_WAIT_BEFORE_RETRY_MIN * 60)).await;
179                continue;
180            }
181            return result;
182        }
183    }
184
185    async fn get_storage_state(&self) -> Result<SchemaVersion, ViewError> {
186        let store = self.database.open_shared(&[])?;
187        let key = bcs::to_bytes(&BaseKey::NetworkDescription).unwrap();
188        if store.contains_key(&key).await? {
189            return Ok(SchemaVersion::Version0);
190        }
191
192        let root_key = RootKey::NetworkDescription.bytes();
193        let store = self.database.open_shared(&root_key)?;
194        if store.contains_key(NETWORK_DESCRIPTION_KEY).await? {
195            return Ok(SchemaVersion::Version1);
196        }
197
198        Ok(SchemaVersion::Uninitialized)
199    }
200
201    /// Assert that the storage is at the last version (or not yet initialized).
202    pub async fn assert_is_migrated_storage(&self) -> Result<(), ViewError> {
203        let state = self.get_storage_state().await?;
204        assert!(matches!(
205            state,
206            SchemaVersion::Uninitialized | SchemaVersion::Version1
207        ));
208        Ok(())
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use std::{
215        collections::{BTreeMap, HashMap},
216        marker::PhantomData,
217        ops::Deref,
218    };
219
220    use linera_base::{
221        crypto::CryptoHash,
222        identifiers::{BlobId, BlobType, ChainId, EventId, StreamId, StreamName},
223    };
224    #[cfg(feature = "rocksdb")]
225    use linera_views::rocks_db::RocksDbDatabase;
226    #[cfg(feature = "scylladb")]
227    use linera_views::scylla_db::ScyllaDbDatabase;
228    use linera_views::{
229        batch::Batch,
230        memory::MemoryDatabase,
231        random::make_deterministic_rng,
232        store::{
233            KeyValueDatabase, KeyValueStore, ReadableKeyValueStore, TestKeyValueDatabase,
234            WritableKeyValueStore,
235        },
236        ViewError,
237    };
238    use rand::{distributions, Rng};
239    use test_case::test_case;
240
241    use crate::{
242        db_storage::RestrictedEventId,
243        migration::{
244            BaseKey, RootKey, BLOB_KEY, BLOB_STATE_KEY, BLOCK_KEY, LITE_CERTIFICATE_KEY,
245            NETWORK_DESCRIPTION_KEY,
246        },
247        DbStorage, StorageCacheConfig, WallClock,
248    };
249
250    #[derive(Clone, Debug, Eq, PartialEq)]
251    #[allow(clippy::type_complexity)]
252    struct StorageState {
253        chain_ids_key_values: BTreeMap<ChainId, Vec<(Vec<u8>, Vec<u8>)>>,
254        certificates: BTreeMap<CryptoHash, Vec<u8>>,
255        confirmed_blocks: BTreeMap<CryptoHash, Vec<u8>>,
256        blobs: BTreeMap<BlobId, Vec<u8>>,
257        blob_states: BTreeMap<BlobId, Vec<u8>>,
258        events: HashMap<EventId, Vec<u8>>,
259        block_exporter_states: BTreeMap<u32, Vec<(Vec<u8>, Vec<u8>)>>,
260        network_description: Option<Vec<u8>>,
261    }
262
263    impl StorageState {
264        fn append_storage_state(&mut self, storage_state: StorageState) {
265            self.chain_ids_key_values
266                .extend(storage_state.chain_ids_key_values);
267            self.certificates.extend(storage_state.certificates);
268            self.confirmed_blocks.extend(storage_state.confirmed_blocks);
269            self.blobs.extend(storage_state.blobs);
270            self.blob_states.extend(storage_state.blob_states);
271            self.events.extend(storage_state.events);
272            self.block_exporter_states
273                .extend(storage_state.block_exporter_states);
274            if let Some(value) = storage_state.network_description {
275                assert!(self.network_description.is_none());
276                self.network_description = Some(value);
277            }
278        }
279    }
280
281    fn create_vector(rng: &mut impl Rng, len: usize) -> Vec<u8> {
282        rng.sample_iter(distributions::Standard).take(len).collect()
283    }
284
285    fn get_hash(rng: &mut impl Rng) -> CryptoHash {
286        let rnd_val = rng.gen::<usize>();
287        CryptoHash::test_hash(format!("rnd_val={rnd_val}"))
288    }
289
290    fn get_stream_id(rng: &mut impl Rng) -> StreamId {
291        let stream_name = StreamName(create_vector(rng, 10));
292        StreamId::system(stream_name)
293    }
294
295    fn get_event_id(rng: &mut impl Rng) -> EventId {
296        let hash = get_hash(rng);
297        let chain_id = ChainId(hash);
298        let stream_id = get_stream_id(rng);
299        let index = rng.gen::<u32>();
300        EventId {
301            chain_id,
302            stream_id,
303            index,
304        }
305    }
306
307    fn get_storage_state() -> StorageState {
308        let mut rng = make_deterministic_rng();
309        let key_size = 5;
310        let value_size = 10;
311        // 0: the chain states.
312        let chain_id_count = 10;
313        let n_key = 1;
314        let mut chain_ids_key_values = BTreeMap::new();
315        for _i_chain in 0..chain_id_count {
316            let hash = get_hash(&mut rng);
317            let chain_id = ChainId(hash);
318            let mut key_values = Vec::new();
319            for _i_key in 0..n_key {
320                let key = create_vector(&mut rng, key_size);
321                let value = create_vector(&mut rng, value_size);
322                key_values.push((key, value));
323            }
324            key_values.sort_unstable();
325            chain_ids_key_values.insert(chain_id, key_values);
326        }
327        // 1: the certificates
328        let certificates_count = 10;
329        let mut certificates = BTreeMap::new();
330        for _i_certificate in 0..certificates_count {
331            let hash = get_hash(&mut rng);
332            let value = create_vector(&mut rng, value_size);
333            certificates.insert(hash, value);
334        }
335        // 2: the confirmed blocks (along with certificates)
336        let blocks_count = 10;
337        let mut confirmed_blocks = BTreeMap::new();
338        for _i_block in 0..blocks_count {
339            let hash = get_hash(&mut rng);
340            let value = create_vector(&mut rng, value_size);
341            certificates.insert(hash, value);
342            let value = create_vector(&mut rng, value_size);
343            confirmed_blocks.insert(hash, value);
344        }
345        // 3: the blobs
346        let blobs_count = 2;
347        let mut blobs = BTreeMap::new();
348        for _i_blob in 0..blobs_count {
349            let hash = get_hash(&mut rng);
350            let blob_id = BlobId {
351                blob_type: BlobType::Data,
352                hash,
353            };
354            let value = create_vector(&mut rng, value_size);
355            blobs.insert(blob_id, value);
356        }
357        // 4: the blob states
358        let blob_states_count = 2;
359        let mut blob_states = BTreeMap::new();
360        for _i_blob_state in 0..blob_states_count {
361            let hash = get_hash(&mut rng);
362            let blob_id = BlobId {
363                blob_type: BlobType::Data,
364                hash,
365            };
366            let value = create_vector(&mut rng, value_size);
367            blob_states.insert(blob_id, value);
368        }
369        // 5: the events
370        let events_count = 2;
371        let mut events = HashMap::new();
372        for _i_event in 0..events_count {
373            let event_id = get_event_id(&mut rng);
374            let value = create_vector(&mut rng, value_size);
375            events.insert(event_id, value);
376        }
377        // 6: the block exporters
378        let block_exporters_count = 2;
379        let n_key = 1;
380        let mut block_exporter_states = BTreeMap::new();
381        for _i_block_export in 0..block_exporters_count {
382            let index = rng.gen::<u32>();
383            let mut key_values = Vec::new();
384            for _i_key in 0..n_key {
385                let key = create_vector(&mut rng, key_size);
386                let value = create_vector(&mut rng, value_size);
387                key_values.push((key, value));
388            }
389            key_values.sort_unstable();
390            block_exporter_states.insert(index, key_values);
391        }
392        // 7: network description
393        let network_description = Some(create_vector(&mut rng, value_size));
394        StorageState {
395            chain_ids_key_values,
396            certificates,
397            confirmed_blocks,
398            blobs,
399            blob_states,
400            events,
401            block_exporter_states,
402            network_description,
403        }
404    }
405
406    async fn write_storage_state_old_schema<D>(
407        database: &D,
408        storage_state: StorageState,
409    ) -> Result<(), ViewError>
410    where
411        D: KeyValueDatabase + Clone + Send + Sync + 'static,
412        D::Store: KeyValueStore + Clone + Send + Sync + 'static,
413        D::Error: Send + Sync,
414    {
415        for (chain_id, key_values) in storage_state.chain_ids_key_values {
416            let root_key = bcs::to_bytes(&BaseKey::ChainState(chain_id))?;
417            let store = database.open_shared(&root_key)?;
418            let mut batch = Batch::new();
419            for (key, value) in key_values {
420                batch.put_key_value_bytes(key, value);
421            }
422            store.write_batch(batch).await?;
423        }
424        for (index, key_values) in storage_state.block_exporter_states {
425            let root_key = bcs::to_bytes(&BaseKey::BlockExporterState(index))?;
426            let store = database.open_shared(&root_key)?;
427            let mut batch = Batch::new();
428            for (key, value) in key_values {
429                batch.put_key_value_bytes(key, value);
430            }
431            store.write_batch(batch).await?;
432        }
433        // Writing in the shared partition
434        let mut batch = Batch::new();
435        for (hash, value) in storage_state.certificates {
436            let key = bcs::to_bytes(&BaseKey::Certificate(hash))?;
437            batch.put_key_value_bytes(key, value);
438        }
439        for (hash, value) in storage_state.confirmed_blocks {
440            let key = bcs::to_bytes(&BaseKey::ConfirmedBlock(hash))?;
441            batch.put_key_value_bytes(key, value);
442        }
443        for (blob_id, value) in storage_state.blobs {
444            let key = bcs::to_bytes(&BaseKey::Blob(blob_id))?;
445            batch.put_key_value_bytes(key, value);
446        }
447        for (blob_id, value) in storage_state.blob_states {
448            let key = bcs::to_bytes(&BaseKey::BlobState(blob_id))?;
449            batch.put_key_value_bytes(key, value);
450        }
451        for (event_id, value) in storage_state.events {
452            let key = bcs::to_bytes(&BaseKey::Event(event_id))?;
453            batch.put_key_value_bytes(key, value);
454        }
455        if let Some(network_description) = storage_state.network_description {
456            let key = bcs::to_bytes(&BaseKey::NetworkDescription)?;
457            batch.put_key_value_bytes(key, network_description);
458        }
459        let store = database.open_shared(&[])?;
460        store.write_batch(batch).await?;
461        Ok(())
462    }
463
464    fn is_valid_root_key(root_key: &[u8]) -> bool {
465        if root_key.is_empty() {
466            // It corresponds to the &[]
467            return false;
468        }
469        if root_key == [4] {
470            // It corresponds to the key of the database schema.
471            return false;
472        }
473        true
474    }
475
476    async fn read_storage_state_new_schema<D>(database: &D) -> Result<StorageState, ViewError>
477    where
478        D: KeyValueDatabase + Clone + Send + Sync + 'static,
479        D::Store: KeyValueStore + Clone + Send + Sync + 'static,
480        D::Error: Send + Sync,
481    {
482        let mut chain_ids_key_values = BTreeMap::new();
483        let mut certificates = BTreeMap::new();
484        let mut confirmed_blocks = BTreeMap::new();
485        let mut blobs = BTreeMap::new();
486        let mut blob_states = BTreeMap::new();
487        let mut events = HashMap::new();
488        let mut block_exporter_states = BTreeMap::new();
489        let mut network_description = None;
490        let bcs_root_keys = database.list_root_keys().await?;
491        for bcs_root_key in bcs_root_keys {
492            if is_valid_root_key(&bcs_root_key) {
493                let root_key = bcs::from_bytes(&bcs_root_key)?;
494                match root_key {
495                    RootKey::ChainState(chain_id) => {
496                        let store = database.open_shared(&bcs_root_key)?;
497                        let key_values = store.find_key_values_by_prefix(&[]).await?;
498                        chain_ids_key_values.insert(chain_id, key_values);
499                    }
500                    RootKey::ConfirmedBlock(hash) => {
501                        let store = database.open_shared(&bcs_root_key)?;
502                        let value = store.read_value_bytes(LITE_CERTIFICATE_KEY).await?;
503                        if let Some(value) = value {
504                            certificates.insert(hash, value);
505                        }
506                        let value = store.read_value_bytes(BLOCK_KEY).await?;
507                        if let Some(value) = value {
508                            confirmed_blocks.insert(hash, value);
509                        }
510                    }
511                    RootKey::Blob(blob_id) => {
512                        let store = database.open_shared(&bcs_root_key)?;
513                        let value = store.read_value_bytes(BLOB_KEY).await?;
514                        if let Some(value) = value {
515                            blobs.insert(blob_id, value);
516                        }
517                        let value = store.read_value_bytes(BLOB_STATE_KEY).await?;
518                        if let Some(value) = value {
519                            blob_states.insert(blob_id, value);
520                        }
521                    }
522                    RootKey::Event(chain_id) => {
523                        let store = database.open_shared(&bcs_root_key)?;
524                        let key_values = store.find_key_values_by_prefix(&[]).await?;
525                        for (key, value) in key_values {
526                            let restricted_event_id = bcs::from_bytes::<RestrictedEventId>(&key)?;
527                            let event_id = EventId {
528                                chain_id,
529                                stream_id: restricted_event_id.stream_id,
530                                index: restricted_event_id.index,
531                            };
532                            events.insert(event_id, value);
533                        }
534                    }
535                    RootKey::Placeholder => {
536                        // Nothing to be done
537                    }
538                    RootKey::NetworkDescription => {
539                        let store = database.open_shared(&bcs_root_key)?;
540                        let value = store.read_value_bytes(NETWORK_DESCRIPTION_KEY).await?;
541                        if let Some(value) = value {
542                            network_description = Some(value);
543                        }
544                    }
545                    RootKey::BlockExporterState(index) => {
546                        let store = database.open_shared(&bcs_root_key)?;
547                        let key_values = store.find_key_values_by_prefix(&[]).await?;
548                        block_exporter_states.insert(index, key_values);
549                    }
550                    RootKey::BlockByHeight(_) => {
551                        // Nothing to be done
552                    }
553                }
554            }
555        }
556        Ok(StorageState {
557            chain_ids_key_values,
558            certificates,
559            confirmed_blocks,
560            blobs,
561            blob_states,
562            events,
563            block_exporter_states,
564            network_description,
565        })
566    }
567
568    async fn test_storage_migration<D>() -> Result<(), ViewError>
569    where
570        D: TestKeyValueDatabase + Clone + Send + Sync + 'static,
571        D::Store: KeyValueStore + Clone + Send + Sync + 'static,
572        D::Error: Send + Sync,
573    {
574        let database = D::connect_test_namespace().await?;
575        // Get a storage state and write it.
576        let mut storage_state = get_storage_state();
577        write_storage_state_old_schema(&database, storage_state.clone()).await?;
578        // Creating a storage and migrate to the new database schema.
579        let cache_sizes = StorageCacheConfig {
580            blob_cache_size: 1000,
581            confirmed_block_cache_size: 1000,
582            certificate_cache_size: 1000,
583            certificate_raw_cache_size: 1000,
584            event_cache_size: 1000,
585            block_hash_by_height_cache_size: 1000,
586            cache_cleanup_interval_secs: crate::DEFAULT_CLEANUP_INTERVAL_SECS,
587        };
588        let storage = DbStorage::<D, WallClock>::new(database, None, cache_sizes, WallClock);
589        storage.migrate_if_needed().await?;
590        // read the storage state and compare it.
591        let read_storage_state = read_storage_state_new_schema(storage.database.deref()).await?;
592        assert_eq!(read_storage_state, storage_state);
593        // Creates a new storage state, write it and migrate it.
594        // That should simulate the partial migration interrupted for some reason and restarted.
595        let mut appended_state = get_storage_state();
596        appended_state.network_description = None;
597        write_storage_state_old_schema(storage.database.deref(), appended_state.clone()).await?;
598        storage.migrate_if_needed().await?;
599        storage_state.append_storage_state(appended_state);
600        let read_storage_state = read_storage_state_new_schema(storage.database.deref()).await?;
601        assert_eq!(read_storage_state, storage_state);
602        Ok(())
603    }
604
605    #[test_case(PhantomData::<MemoryDatabase>; "MemoryDatabase")]
606    #[cfg_attr(with_rocksdb, test_case(PhantomData::<RocksDbDatabase>; "RocksDbDatabase"))]
607    #[cfg_attr(with_scylladb, test_case(PhantomData::<ScyllaDbDatabase>; "ScyllaDbDatabase"))]
608    #[tokio::test]
609    async fn test_storage_migration_cases<D>(_storage_type: PhantomData<D>) -> Result<(), ViewError>
610    where
611        D: TestKeyValueDatabase + Clone + Send + Sync + 'static,
612        D::Store: KeyValueStore + Clone + Send + Sync + 'static,
613        D::Error: Send + Sync,
614    {
615        test_storage_migration::<D>().await
616    }
617}