Skip to main content

risingwave_storage/hummock/sstable/
multi_builder.rs

1// Copyright 2022 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::{BTreeMap, HashMap};
16use std::sync::Arc;
17use std::sync::atomic::AtomicU64;
18use std::sync::atomic::Ordering::SeqCst;
19
20use await_tree::{InstrumentAwait, SpanExt};
21use bytes::Bytes;
22use futures::StreamExt;
23use futures::stream::FuturesUnordered;
24use num_integer::Integer;
25use risingwave_common::catalog::TableId;
26use risingwave_common::hash::VirtualNode;
27use risingwave_hummock_sdk::LocalSstableInfo;
28use risingwave_hummock_sdk::key::{FullKey, UserKey};
29use tokio::task::JoinHandle;
30
31use crate::compaction_catalog_manager::CompactionCatalogAgentRef;
32use crate::hummock::compactor::task_progress::TaskProgress;
33use crate::hummock::sstable::filter::FilterBuilder;
34use crate::hummock::sstable_store::SstableStoreRef;
35use crate::hummock::value::HummockValue;
36use crate::hummock::{
37    BatchUploadWriter, BlockMeta, CachePolicy, HummockError, HummockResult, MemoryLimiter,
38    SstableBuilder, SstableBuilderOptions, SstableWriter, SstableWriterOptions, Xor16FilterBuilder,
39};
40use crate::monitor::CompactorMetrics;
41
42pub type UploadJoinHandle = JoinHandle<HummockResult<()>>;
43
44#[async_trait::async_trait]
45pub trait TableBuilderFactory {
46    type Writer: SstableWriter<Output = UploadJoinHandle>;
47    type Filter: FilterBuilder;
48    async fn open_builder(&mut self) -> HummockResult<SstableBuilder<Self::Writer, Self::Filter>>;
49}
50
51/// A wrapper for [`SstableBuilder`] which automatically split key-value pairs into multiple tables,
52/// based on their target capacity set in options.
53///
54/// When building is finished, one may call `finish` to get the results of zero, one or more tables.
55pub struct CapacitySplitTableBuilder<F>
56where
57    F: TableBuilderFactory,
58{
59    /// When creating a new [`SstableBuilder`], caller use this factory to generate it.
60    builder_factory: F,
61
62    sst_outputs: Vec<LocalSstableInfo>,
63
64    current_builder: Option<SstableBuilder<F::Writer, F::Filter>>,
65
66    /// Statistics.
67    pub compactor_metrics: Arc<CompactorMetrics>,
68
69    /// Update the number of sealed Sstables.
70    task_progress: Option<Arc<TaskProgress>>,
71
72    last_table_id: TableId,
73
74    vnode_count: usize,
75    table_vnode_partition: BTreeMap<TableId, u32>,
76    split_weight_by_vnode: u32,
77    /// When vnode of the coming key is greater than `largest_vnode_in_current_partition`, we will
78    /// switch SST.
79    largest_vnode_in_current_partition: usize,
80
81    concurrent_upload_join_handle: FuturesUnordered<UploadJoinHandle>,
82
83    concurrent_uploading_sst_count: Option<usize>,
84
85    compaction_catalog_agent_ref: CompactionCatalogAgentRef,
86}
87
88impl<F> CapacitySplitTableBuilder<F>
89where
90    F: TableBuilderFactory,
91{
92    /// Creates a new [`CapacitySplitTableBuilder`] using given configuration generator.
93    #[allow(clippy::too_many_arguments)]
94    pub fn new(
95        builder_factory: F,
96        compactor_metrics: Arc<CompactorMetrics>,
97        task_progress: Option<Arc<TaskProgress>>,
98        table_vnode_partition: BTreeMap<TableId, u32>,
99        concurrent_uploading_sst_count: Option<usize>,
100        compaction_catalog_agent_ref: CompactionCatalogAgentRef,
101    ) -> Self {
102        // TODO(var-vnode): should use value from caller
103        let vnode_count = VirtualNode::COUNT_FOR_COMPAT;
104
105        Self {
106            builder_factory,
107            sst_outputs: Vec::new(),
108            current_builder: None,
109            compactor_metrics,
110            task_progress,
111            last_table_id: 0.into(),
112            table_vnode_partition,
113            vnode_count,
114            split_weight_by_vnode: 0,
115            largest_vnode_in_current_partition: vnode_count - 1,
116            concurrent_upload_join_handle: FuturesUnordered::new(),
117            concurrent_uploading_sst_count,
118            compaction_catalog_agent_ref,
119        }
120    }
121
122    pub fn for_test(
123        builder_factory: F,
124        compaction_catalog_agent_ref: CompactionCatalogAgentRef,
125    ) -> Self {
126        Self {
127            builder_factory,
128            sst_outputs: Vec::new(),
129            current_builder: None,
130            compactor_metrics: Arc::new(CompactorMetrics::unused()),
131            task_progress: None,
132            last_table_id: 0.into(),
133            table_vnode_partition: BTreeMap::default(),
134            vnode_count: VirtualNode::COUNT_FOR_TEST,
135            split_weight_by_vnode: 0,
136            largest_vnode_in_current_partition: VirtualNode::MAX_FOR_TEST.to_index(),
137            concurrent_upload_join_handle: FuturesUnordered::new(),
138            concurrent_uploading_sst_count: None,
139            compaction_catalog_agent_ref,
140        }
141    }
142
143    /// Returns the number of [`SstableBuilder`]s.
144    pub fn len(&self) -> usize {
145        self.sst_outputs.len() + self.current_builder.is_some() as usize
146    }
147
148    /// Returns true if no builder is created.
149    pub fn is_empty(&self) -> bool {
150        self.sst_outputs.is_empty() && self.current_builder.is_none()
151    }
152
153    pub async fn add_full_key_for_test(
154        &mut self,
155        full_key: FullKey<&[u8]>,
156        value: HummockValue<&[u8]>,
157        is_new_user_key: bool,
158    ) -> HummockResult<()> {
159        self.add_full_key(full_key, value, is_new_user_key).await
160    }
161
162    pub async fn add_raw_block(
163        &mut self,
164        buf: Bytes,
165        filter_data: Vec<u8>,
166        smallest_key: FullKey<Vec<u8>>,
167        largest_key: Vec<u8>,
168        block_meta: BlockMeta,
169    ) -> HummockResult<bool> {
170        if self.current_builder.is_none() {
171            if let Some(progress) = &self.task_progress {
172                progress.inc_num_pending_write_io()
173            }
174            let builder = self
175                .builder_factory
176                .open_builder()
177                .instrument_await("multi_builder_open_builder_for_raw_block".verbose())
178                .await?;
179            self.current_builder = Some(builder);
180        }
181
182        let builder = self.current_builder.as_mut().unwrap();
183        builder
184            .add_raw_block(buf, filter_data, smallest_key, largest_key, block_meta)
185            .await
186    }
187
188    /// Adds a key-value pair to the underlying builders.
189    ///
190    /// If `allow_split` and the current builder reaches its capacity, this function will create a
191    /// new one with the configuration generated by the closure provided earlier.
192    ///
193    /// Note that in some cases like compaction of the same user key, automatic splitting is not
194    /// allowed, where `allow_split` should be `false`.
195    pub async fn add_full_key(
196        &mut self,
197        full_key: FullKey<&[u8]>,
198        value: HummockValue<&[u8]>,
199        is_new_user_key: bool,
200    ) -> HummockResult<()> {
201        let switch_builder = self.check_switch_builder(&full_key.user_key);
202
203        // We use this `need_seal_current` flag to store whether we need to call `seal_current` and
204        // then call `seal_current` later outside the `if let` instead of calling
205        // `seal_current` at where we set `need_seal_current = true`. This is because
206        // `seal_current` is an async method, and if we call `seal_current` within the `if let`,
207        // this temporary reference to `current_builder` will be captured in the future generated
208        // from the current method. Since this generated future is usually required to be `Send`,
209        // the captured reference to `current_builder` is also required to be `Send`, and then
210        // `current_builder` itself is required to be `Sync`, which is unnecessary.
211        let mut need_seal_current = false;
212        if let Some(builder) = self.current_builder.as_mut()
213            && is_new_user_key
214        {
215            need_seal_current = switch_builder || builder.reach_capacity();
216        }
217
218        if need_seal_current {
219            self.seal_current()
220                .instrument_await("multi_builder_seal_current".verbose())
221                .await?;
222        }
223
224        if self.current_builder.is_none() {
225            if let Some(progress) = &self.task_progress {
226                progress.inc_num_pending_write_io();
227            }
228            let builder = self
229                .builder_factory
230                .open_builder()
231                .instrument_await("multi_builder_open_builder".verbose())
232                .await?;
233            self.current_builder = Some(builder);
234        }
235
236        let builder = self.current_builder.as_mut().unwrap();
237        builder
238            .add(full_key, value)
239            .instrument_await("sstable_builder_add".verbose())
240            .await
241    }
242
243    pub fn check_switch_builder(&mut self, user_key: &UserKey<&[u8]>) -> bool {
244        let mut switch_builder = false;
245        if user_key.table_id != self.last_table_id {
246            let new_vnode_partition_count = self.table_vnode_partition.get(&user_key.table_id);
247
248            self.vnode_count = self
249                .compaction_catalog_agent_ref
250                .vnode_count(user_key.table_id);
251            self.largest_vnode_in_current_partition = self.vnode_count - 1;
252
253            if new_vnode_partition_count.is_some()
254                || self.table_vnode_partition.contains_key(&self.last_table_id)
255            {
256                if let Some(new_vnode_partition_count) = new_vnode_partition_count {
257                    if (*new_vnode_partition_count as usize) > self.vnode_count {
258                        tracing::warn!(
259                            "vnode partition count {} is larger than vnode count {}",
260                            new_vnode_partition_count,
261                            self.vnode_count
262                        );
263
264                        self.split_weight_by_vnode = 0;
265                    } else {
266                        self.split_weight_by_vnode = *new_vnode_partition_count;
267                    };
268                } else {
269                    self.split_weight_by_vnode = 0;
270                }
271
272                // table_id change
273                self.last_table_id = user_key.table_id;
274                switch_builder = true;
275                if self.split_weight_by_vnode > 1 {
276                    self.largest_vnode_in_current_partition =
277                        self.vnode_count / (self.split_weight_by_vnode as usize) - 1;
278                } else {
279                    // default
280                    self.largest_vnode_in_current_partition = self.vnode_count - 1;
281                }
282            }
283        }
284        if self.largest_vnode_in_current_partition != self.vnode_count - 1 {
285            let key_vnode = user_key.get_vnode_id();
286            if key_vnode > self.largest_vnode_in_current_partition {
287                // vnode partition change
288                switch_builder = true;
289
290                // SAFETY: `self.split_weight_by_vnode > 1` here.
291                let (basic, remainder) = self
292                    .vnode_count
293                    .div_rem(&(self.split_weight_by_vnode as usize));
294                let small_segments_area = basic * (self.split_weight_by_vnode as usize - remainder);
295                self.largest_vnode_in_current_partition = (if key_vnode < small_segments_area {
296                    (key_vnode / basic + 1) * basic
297                } else {
298                    ((key_vnode - small_segments_area) / (basic + 1) + 1) * (basic + 1)
299                        + small_segments_area
300                }) - 1;
301                debug_assert!(key_vnode <= self.largest_vnode_in_current_partition);
302            }
303        }
304        switch_builder
305    }
306
307    pub fn need_flush(&self) -> bool {
308        self.current_builder
309            .as_ref()
310            .map(|builder| builder.reach_capacity())
311            .unwrap_or(false)
312    }
313
314    /// Marks the current builder as sealed. Next call of `add` will always create a new table.
315    ///
316    /// If there's no builder created, or current one is already sealed before, then this function
317    /// will be no-op.
318    pub async fn seal_current(&mut self) -> HummockResult<()> {
319        if let Some(builder) = self.current_builder.take() {
320            let builder_output = builder
321                .finish()
322                .instrument_await("sstable_builder_finish".verbose())
323                .await?;
324            {
325                // report
326                if let Some(progress) = &self.task_progress {
327                    progress.inc_ssts_sealed();
328                }
329                builder_output.stats.report_stats(&self.compactor_metrics);
330            }
331
332            self.concurrent_upload_join_handle
333                .push(builder_output.writer_output);
334
335            self.sst_outputs.push(builder_output.sst_info);
336
337            if let Some(concurrent_uploading_sst_count) = self.concurrent_uploading_sst_count
338                && self.concurrent_upload_join_handle.len() >= concurrent_uploading_sst_count
339            {
340                self.concurrent_upload_join_handle
341                    .next()
342                    .instrument_await("upload".verbose())
343                    .await
344                    .unwrap()
345                    .map_err(HummockError::sstable_upload_error)??;
346            }
347        }
348        Ok(())
349    }
350
351    /// Finalizes all the tables to be ids, blocks and metadata.
352    pub async fn finish(mut self) -> HummockResult<Vec<LocalSstableInfo>> {
353        use futures::future::try_join_all;
354        self.seal_current()
355            .instrument_await("multi_builder_finish_seal_current".verbose())
356            .await?;
357        try_join_all(self.concurrent_upload_join_handle)
358            .instrument_await("multi_builder_wait_all_uploads".verbose())
359            .await
360            .map_err(HummockError::sstable_upload_error)?
361            .into_iter()
362            .collect::<HummockResult<Vec<()>>>()?;
363
364        Ok(self.sst_outputs)
365    }
366}
367
368/// Used for unit tests and benchmarks.
369pub struct LocalTableBuilderFactory {
370    next_id: AtomicU64,
371    sstable_store: SstableStoreRef,
372    options: SstableBuilderOptions,
373    policy: CachePolicy,
374    limiter: MemoryLimiter,
375}
376
377impl LocalTableBuilderFactory {
378    pub fn new(
379        next_id: u64,
380        sstable_store: SstableStoreRef,
381        options: SstableBuilderOptions,
382    ) -> Self {
383        Self {
384            next_id: AtomicU64::new(next_id),
385            sstable_store,
386            options,
387            policy: CachePolicy::NotFill,
388            limiter: MemoryLimiter::new(1000000),
389        }
390    }
391}
392
393#[async_trait::async_trait]
394impl TableBuilderFactory for LocalTableBuilderFactory {
395    type Filter = Xor16FilterBuilder;
396    type Writer = BatchUploadWriter;
397
398    async fn open_builder(
399        &mut self,
400    ) -> HummockResult<SstableBuilder<BatchUploadWriter, Xor16FilterBuilder>> {
401        let id = self.next_id.fetch_add(1, SeqCst);
402        let tracker = self
403            .limiter
404            .require_memory(1)
405            .instrument_await("local_builder_require_memory".verbose())
406            .await;
407        let writer_options = SstableWriterOptions {
408            capacity_hint: Some(self.options.capacity),
409            tracker: Some(tracker),
410            policy: self.policy,
411        };
412        let writer = self
413            .sstable_store
414            .clone()
415            .create_sst_writer(id, writer_options);
416        let table_id_to_vnode = HashMap::from_iter(vec![(
417            TableId::default().as_raw_id(),
418            VirtualNode::COUNT_FOR_TEST,
419        )]);
420        let table_id_to_watermark_serde =
421            HashMap::from_iter(vec![(TableId::default().as_raw_id(), None)]);
422        let builder = SstableBuilder::for_test(
423            id,
424            writer,
425            self.options.clone(),
426            table_id_to_vnode,
427            table_id_to_watermark_serde,
428        );
429
430        Ok(builder)
431    }
432}
433
434#[cfg(test)]
435mod tests {
436    use risingwave_common::catalog::TableId;
437    use risingwave_common::util::epoch::{EpochExt, test_epoch};
438
439    use super::*;
440    use crate::compaction_catalog_manager::{
441        CompactionCatalogAgent, FilterKeyExtractorImpl, FullKeyFilterKeyExtractor,
442    };
443    use crate::hummock::DEFAULT_RESTART_INTERVAL;
444    use crate::hummock::iterator::test_utils::mock_sstable_store;
445    use crate::hummock::test_utils::{default_builder_opt_for_test, test_key_of, test_user_key_of};
446
447    #[tokio::test]
448    async fn test_empty() {
449        let block_size = 1 << 10;
450        let table_capacity = 4 * block_size;
451        let opts = SstableBuilderOptions {
452            capacity: table_capacity,
453            block_capacity: block_size,
454            restart_interval: DEFAULT_RESTART_INTERVAL,
455            bloom_false_positive: 0.1,
456            ..Default::default()
457        };
458        let builder_factory = LocalTableBuilderFactory::new(1001, mock_sstable_store().await, opts);
459        let compaction_catalog_agent_ref = Arc::new(CompactionCatalogAgent::dummy());
460        let builder =
461            CapacitySplitTableBuilder::for_test(builder_factory, compaction_catalog_agent_ref);
462        let results = builder.finish().await.unwrap();
463        assert!(results.is_empty());
464    }
465
466    #[tokio::test]
467    async fn test_lots_of_tables() {
468        let block_size = 1 << 10;
469        let table_capacity = 4 * block_size;
470        let opts = SstableBuilderOptions {
471            capacity: table_capacity,
472            block_capacity: block_size,
473            restart_interval: DEFAULT_RESTART_INTERVAL,
474            bloom_false_positive: 0.1,
475            ..Default::default()
476        };
477        let compaction_catalog_agent_ref = CompactionCatalogAgent::for_test(vec![0]);
478
479        let builder_factory = LocalTableBuilderFactory::new(1001, mock_sstable_store().await, opts);
480        let mut builder =
481            CapacitySplitTableBuilder::for_test(builder_factory, compaction_catalog_agent_ref);
482
483        for i in 0..table_capacity {
484            builder
485                .add_full_key_for_test(
486                    FullKey::from_user_key(
487                        test_user_key_of(i).as_ref(),
488                        test_epoch((table_capacity - i) as u64),
489                    ),
490                    HummockValue::put(b"value"),
491                    true,
492                )
493                .await
494                .unwrap();
495        }
496
497        let results = builder.finish().await.unwrap();
498        assert!(results.len() > 1);
499    }
500
501    #[tokio::test]
502    async fn test_table_seal() {
503        let opts = default_builder_opt_for_test();
504        let compaction_catalog_agent_ref = CompactionCatalogAgent::for_test(vec![0]);
505        let mut builder = CapacitySplitTableBuilder::for_test(
506            LocalTableBuilderFactory::new(1001, mock_sstable_store().await, opts),
507            compaction_catalog_agent_ref,
508        );
509        let mut epoch = test_epoch(100);
510
511        macro_rules! add {
512            () => {
513                epoch.dec_epoch();
514                builder
515                    .add_full_key_for_test(
516                        FullKey::from_user_key(test_user_key_of(1).as_ref(), epoch),
517                        HummockValue::put(b"v"),
518                        true,
519                    )
520                    .await
521                    .unwrap();
522            };
523        }
524
525        assert_eq!(builder.len(), 0);
526        builder.seal_current().await.unwrap();
527        assert_eq!(builder.len(), 0);
528        add!();
529        assert_eq!(builder.len(), 1);
530        add!();
531        assert_eq!(builder.len(), 1);
532        builder.seal_current().await.unwrap();
533        assert_eq!(builder.len(), 1);
534        add!();
535        assert_eq!(builder.len(), 2);
536        builder.seal_current().await.unwrap();
537        assert_eq!(builder.len(), 2);
538        builder.seal_current().await.unwrap();
539        assert_eq!(builder.len(), 2);
540
541        let results = builder.finish().await.unwrap();
542        assert_eq!(results.len(), 2);
543    }
544
545    #[tokio::test]
546    async fn test_initial_not_allowed_split() {
547        let opts = default_builder_opt_for_test();
548        let compaction_catalog_agent_ref = CompactionCatalogAgent::for_test(vec![0]);
549        let mut builder = CapacitySplitTableBuilder::for_test(
550            LocalTableBuilderFactory::new(1001, mock_sstable_store().await, opts),
551            compaction_catalog_agent_ref,
552        );
553        builder
554            .add_full_key_for_test(test_key_of(0).to_ref(), HummockValue::put(b"v"), false)
555            .await
556            .unwrap();
557    }
558
559    #[tokio::test]
560    async fn test_check_table_and_vnode_change() {
561        let block_size = 256;
562        let table_capacity = 2 * block_size;
563        let opts = SstableBuilderOptions {
564            capacity: table_capacity,
565            block_capacity: block_size,
566            restart_interval: DEFAULT_RESTART_INTERVAL,
567            bloom_false_positive: 0.1,
568            ..Default::default()
569        };
570
571        {
572            let table_partition_vnode = BTreeMap::from([
573                (1_u32.into(), 4_u32),
574                (2_u32.into(), 4_u32),
575                (3_u32.into(), 4_u32),
576            ]);
577
578            let compaction_catalog_agent_ref =
579                CompactionCatalogAgent::for_test(vec![0, 1, 2, 3, 4, 5]);
580            let mut builder = CapacitySplitTableBuilder::new(
581                LocalTableBuilderFactory::new(1001, mock_sstable_store().await, opts.clone()),
582                Arc::new(CompactorMetrics::unused()),
583                None,
584                table_partition_vnode,
585                None,
586                compaction_catalog_agent_ref,
587            );
588
589            let mut table_key = VirtualNode::from_index(0).to_be_bytes().to_vec();
590            table_key.extend_from_slice("a".as_bytes());
591
592            let switch_builder =
593                builder.check_switch_builder(&UserKey::for_test(TableId::from(1), &table_key));
594            assert!(switch_builder);
595
596            let mut table_key = VirtualNode::from_index(62).to_be_bytes().to_vec();
597            table_key.extend_from_slice("a".as_bytes());
598            let switch_builder =
599                builder.check_switch_builder(&UserKey::for_test(TableId::from(1), &table_key));
600            assert!(!switch_builder);
601
602            let mut table_key = VirtualNode::from_index(63).to_be_bytes().to_vec();
603            table_key.extend_from_slice("a".as_bytes());
604            let switch_builder =
605                builder.check_switch_builder(&UserKey::for_test(TableId::from(1), &table_key));
606            assert!(!switch_builder);
607
608            let mut table_key = VirtualNode::from_index(64).to_be_bytes().to_vec();
609            table_key.extend_from_slice("a".as_bytes());
610            let switch_builder =
611                builder.check_switch_builder(&UserKey::for_test(TableId::from(1), &table_key));
612            assert!(switch_builder);
613
614            let switch_builder =
615                builder.check_switch_builder(&UserKey::for_test(TableId::from(2), &table_key));
616            assert!(switch_builder);
617            let switch_builder =
618                builder.check_switch_builder(&UserKey::for_test(TableId::from(3), &table_key));
619            assert!(switch_builder);
620            let switch_builder =
621                builder.check_switch_builder(&UserKey::for_test(TableId::from(4), &table_key));
622            assert!(switch_builder);
623            let switch_builder =
624                builder.check_switch_builder(&UserKey::for_test(TableId::from(5), &table_key));
625            assert!(!switch_builder);
626        }
627
628        {
629            // Test different table vnode count
630            let table_partition_vnode = BTreeMap::from([
631                (1_u32.into(), 4_u32),
632                (2_u32.into(), 4_u32),
633                (3_u32.into(), 4_u32),
634            ]);
635
636            let table_id_to_vnode =
637                HashMap::from_iter(vec![(1.into(), 64), (2.into(), 128), (3.into(), 256)]);
638            let table_id_to_watermark_serde =
639                HashMap::from_iter(vec![(1.into(), None), (2.into(), None), (3.into(), None)]);
640            let compaction_catalog_agent_ref = Arc::new(CompactionCatalogAgent::new(
641                FilterKeyExtractorImpl::FullKey(FullKeyFilterKeyExtractor),
642                table_id_to_vnode,
643                table_id_to_watermark_serde,
644                HashMap::default(),
645            ));
646
647            let mut builder = CapacitySplitTableBuilder::new(
648                LocalTableBuilderFactory::new(1001, mock_sstable_store().await, opts),
649                Arc::new(CompactorMetrics::unused()),
650                None,
651                table_partition_vnode,
652                None,
653                compaction_catalog_agent_ref,
654            );
655
656            let mut table_key = VirtualNode::from_index(0).to_be_bytes().to_vec();
657            table_key.extend_from_slice("a".as_bytes());
658
659            let switch_builder =
660                builder.check_switch_builder(&UserKey::for_test(TableId::from(1), &table_key));
661            assert!(switch_builder);
662
663            let mut table_key = VirtualNode::from_index(15).to_be_bytes().to_vec();
664            table_key.extend_from_slice("a".as_bytes());
665            let switch_builder =
666                builder.check_switch_builder(&UserKey::for_test(TableId::from(1), &table_key));
667            assert!(!switch_builder);
668
669            let mut table_key = VirtualNode::from_index(16).to_be_bytes().to_vec();
670            table_key.extend_from_slice("a".as_bytes());
671            let switch_builder =
672                builder.check_switch_builder(&UserKey::for_test(TableId::from(1), &table_key));
673            assert!(switch_builder);
674
675            let mut table_key = VirtualNode::from_index(0).to_be_bytes().to_vec();
676            table_key.extend_from_slice("a".as_bytes());
677            let switch_builder =
678                builder.check_switch_builder(&UserKey::for_test(TableId::from(2), &table_key));
679            assert!(switch_builder);
680
681            let mut table_key = VirtualNode::from_index(16).to_be_bytes().to_vec();
682            table_key.extend_from_slice("a".as_bytes());
683            let switch_builder =
684                builder.check_switch_builder(&UserKey::for_test(TableId::from(2), &table_key));
685            assert!(!switch_builder);
686
687            let mut table_key = VirtualNode::from_index(31).to_be_bytes().to_vec();
688            table_key.extend_from_slice("a".as_bytes());
689            let switch_builder =
690                builder.check_switch_builder(&UserKey::for_test(TableId::from(2), &table_key));
691            assert!(!switch_builder);
692
693            let mut table_key = VirtualNode::from_index(32).to_be_bytes().to_vec();
694            table_key.extend_from_slice("a".as_bytes());
695            let switch_builder =
696                builder.check_switch_builder(&UserKey::for_test(TableId::from(2), &table_key));
697            assert!(switch_builder);
698
699            let mut table_key = VirtualNode::from_index(64).to_be_bytes().to_vec();
700            table_key.extend_from_slice("a".as_bytes());
701            let switch_builder =
702                builder.check_switch_builder(&UserKey::for_test(TableId::from(2), &table_key));
703            assert!(switch_builder);
704
705            let mut table_key = VirtualNode::from_index(0).to_be_bytes().to_vec();
706            table_key.extend_from_slice("a".as_bytes());
707            let switch_builder =
708                builder.check_switch_builder(&UserKey::for_test(TableId::from(3), &table_key));
709            assert!(switch_builder);
710
711            let mut table_key = VirtualNode::from_index(16).to_be_bytes().to_vec();
712            table_key.extend_from_slice("a".as_bytes());
713            let switch_builder =
714                builder.check_switch_builder(&UserKey::for_test(TableId::from(3), &table_key));
715            assert!(!switch_builder);
716
717            let mut table_key = VirtualNode::from_index(32).to_be_bytes().to_vec();
718            table_key.extend_from_slice("a".as_bytes());
719            let switch_builder =
720                builder.check_switch_builder(&UserKey::for_test(TableId::from(3), &table_key));
721            assert!(!switch_builder);
722
723            let mut table_key = VirtualNode::from_index(63).to_be_bytes().to_vec();
724            table_key.extend_from_slice("a".as_bytes());
725            let switch_builder =
726                builder.check_switch_builder(&UserKey::for_test(TableId::from(3), &table_key));
727            assert!(!switch_builder);
728
729            let mut table_key = VirtualNode::from_index(64).to_be_bytes().to_vec();
730            table_key.extend_from_slice("a".as_bytes());
731            let switch_builder =
732                builder.check_switch_builder(&UserKey::for_test(TableId::from(3), &table_key));
733            assert!(switch_builder);
734        }
735    }
736}