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    /// Returns the raw written length, or `None` if the block was coalesced.
163    pub async fn add_raw_block(
164        &mut self,
165        buf: Bytes,
166        filter_data: Vec<u8>,
167        smallest_key: FullKey<Vec<u8>>,
168        largest_key: Vec<u8>,
169        block_meta: BlockMeta,
170    ) -> HummockResult<Option<usize>> {
171        if self.current_builder.is_none() {
172            if let Some(progress) = &self.task_progress {
173                progress.inc_num_pending_write_io()
174            }
175            let builder = self
176                .builder_factory
177                .open_builder()
178                .instrument_await("multi_builder_open_builder_for_raw_block".verbose())
179                .await?;
180            self.current_builder = Some(builder);
181        }
182
183        let builder = self.current_builder.as_mut().unwrap();
184        builder
185            .add_raw_block(buf, filter_data, smallest_key, largest_key, block_meta)
186            .await
187    }
188
189    /// Adds a key-value pair to the underlying builders.
190    ///
191    /// If `allow_split` and the current builder reaches its capacity, this function will create a
192    /// new one with the configuration generated by the closure provided earlier.
193    ///
194    /// Note that in some cases like compaction of the same user key, automatic splitting is not
195    /// allowed, where `allow_split` should be `false`.
196    pub async fn add_full_key(
197        &mut self,
198        full_key: FullKey<&[u8]>,
199        value: HummockValue<&[u8]>,
200        is_new_user_key: bool,
201    ) -> HummockResult<()> {
202        let switch_builder = self.check_switch_builder(&full_key.user_key);
203
204        // We use this `need_seal_current` flag to store whether we need to call `seal_current` and
205        // then call `seal_current` later outside the `if let` instead of calling
206        // `seal_current` at where we set `need_seal_current = true`. This is because
207        // `seal_current` is an async method, and if we call `seal_current` within the `if let`,
208        // this temporary reference to `current_builder` will be captured in the future generated
209        // from the current method. Since this generated future is usually required to be `Send`,
210        // the captured reference to `current_builder` is also required to be `Send`, and then
211        // `current_builder` itself is required to be `Sync`, which is unnecessary.
212        let mut need_seal_current = false;
213        if let Some(builder) = self.current_builder.as_mut()
214            && is_new_user_key
215        {
216            need_seal_current = switch_builder || builder.reach_capacity();
217        }
218
219        if need_seal_current {
220            self.seal_current()
221                .instrument_await("multi_builder_seal_current".verbose())
222                .await?;
223        }
224
225        if self.current_builder.is_none() {
226            if let Some(progress) = &self.task_progress {
227                progress.inc_num_pending_write_io();
228            }
229            let builder = self
230                .builder_factory
231                .open_builder()
232                .instrument_await("multi_builder_open_builder".verbose())
233                .await?;
234            self.current_builder = Some(builder);
235        }
236
237        let builder = self.current_builder.as_mut().unwrap();
238        builder
239            .add(full_key, value)
240            .instrument_await("sstable_builder_add".verbose())
241            .await
242    }
243
244    pub fn check_switch_builder(&mut self, user_key: &UserKey<&[u8]>) -> bool {
245        let mut switch_builder = false;
246        if user_key.table_id != self.last_table_id {
247            let new_vnode_partition_count = self.table_vnode_partition.get(&user_key.table_id);
248
249            self.vnode_count = self
250                .compaction_catalog_agent_ref
251                .vnode_count(user_key.table_id);
252            self.largest_vnode_in_current_partition = self.vnode_count - 1;
253
254            if new_vnode_partition_count.is_some()
255                || self.table_vnode_partition.contains_key(&self.last_table_id)
256            {
257                if let Some(new_vnode_partition_count) = new_vnode_partition_count {
258                    if (*new_vnode_partition_count as usize) > self.vnode_count {
259                        tracing::warn!(
260                            "vnode partition count {} is larger than vnode count {}",
261                            new_vnode_partition_count,
262                            self.vnode_count
263                        );
264
265                        self.split_weight_by_vnode = 0;
266                    } else {
267                        self.split_weight_by_vnode = *new_vnode_partition_count;
268                    };
269                } else {
270                    self.split_weight_by_vnode = 0;
271                }
272
273                // table_id change
274                self.last_table_id = user_key.table_id;
275                switch_builder = true;
276                if self.split_weight_by_vnode > 1 {
277                    self.largest_vnode_in_current_partition =
278                        self.vnode_count / (self.split_weight_by_vnode as usize) - 1;
279                } else {
280                    // default
281                    self.largest_vnode_in_current_partition = self.vnode_count - 1;
282                }
283            }
284        }
285        if self.largest_vnode_in_current_partition != self.vnode_count - 1 {
286            let key_vnode = user_key.get_vnode_id();
287            if key_vnode > self.largest_vnode_in_current_partition {
288                // vnode partition change
289                switch_builder = true;
290
291                // SAFETY: `self.split_weight_by_vnode > 1` here.
292                let (basic, remainder) = self
293                    .vnode_count
294                    .div_rem(&(self.split_weight_by_vnode as usize));
295                let small_segments_area = basic * (self.split_weight_by_vnode as usize - remainder);
296                self.largest_vnode_in_current_partition = (if key_vnode < small_segments_area {
297                    (key_vnode / basic + 1) * basic
298                } else {
299                    ((key_vnode - small_segments_area) / (basic + 1) + 1) * (basic + 1)
300                        + small_segments_area
301                }) - 1;
302                debug_assert!(key_vnode <= self.largest_vnode_in_current_partition);
303            }
304        }
305        switch_builder
306    }
307
308    pub fn need_flush(&self) -> bool {
309        self.current_builder
310            .as_ref()
311            .map(|builder| builder.reach_capacity())
312            .unwrap_or(false)
313    }
314
315    /// Marks the current builder as sealed. Next call of `add` will always create a new table.
316    ///
317    /// If there's no builder created, or current one is already sealed before, then this function
318    /// will be no-op.
319    pub async fn seal_current(&mut self) -> HummockResult<()> {
320        if let Some(builder) = self.current_builder.take() {
321            let builder_output = builder
322                .finish()
323                .instrument_await("sstable_builder_finish".verbose())
324                .await?;
325            {
326                // report
327                if let Some(progress) = &self.task_progress {
328                    progress.inc_ssts_sealed();
329                }
330                builder_output.stats.report_stats(&self.compactor_metrics);
331            }
332
333            self.concurrent_upload_join_handle
334                .push(builder_output.writer_output);
335
336            self.sst_outputs.push(builder_output.sst_info);
337
338            if let Some(concurrent_uploading_sst_count) = self.concurrent_uploading_sst_count
339                && self.concurrent_upload_join_handle.len() >= concurrent_uploading_sst_count
340            {
341                self.concurrent_upload_join_handle
342                    .next()
343                    .instrument_await("upload".verbose())
344                    .await
345                    .unwrap()
346                    .map_err(HummockError::sstable_upload_error)??;
347            }
348        }
349        Ok(())
350    }
351
352    /// Finalizes all the tables to be ids, blocks and metadata.
353    pub async fn finish(mut self) -> HummockResult<Vec<LocalSstableInfo>> {
354        use futures::future::try_join_all;
355        self.seal_current()
356            .instrument_await("multi_builder_finish_seal_current".verbose())
357            .await?;
358        try_join_all(self.concurrent_upload_join_handle)
359            .instrument_await("multi_builder_wait_all_uploads".verbose())
360            .await
361            .map_err(HummockError::sstable_upload_error)?
362            .into_iter()
363            .collect::<HummockResult<Vec<()>>>()?;
364
365        Ok(self.sst_outputs)
366    }
367}
368
369/// Used for unit tests and benchmarks.
370pub struct LocalTableBuilderFactory {
371    next_id: AtomicU64,
372    sstable_store: SstableStoreRef,
373    options: SstableBuilderOptions,
374    policy: CachePolicy,
375    limiter: MemoryLimiter,
376}
377
378impl LocalTableBuilderFactory {
379    pub fn new(
380        next_id: u64,
381        sstable_store: SstableStoreRef,
382        options: SstableBuilderOptions,
383    ) -> Self {
384        Self {
385            next_id: AtomicU64::new(next_id),
386            sstable_store,
387            options,
388            policy: CachePolicy::NotFill,
389            limiter: MemoryLimiter::new(1000000),
390        }
391    }
392}
393
394#[async_trait::async_trait]
395impl TableBuilderFactory for LocalTableBuilderFactory {
396    type Filter = Xor16FilterBuilder;
397    type Writer = BatchUploadWriter;
398
399    async fn open_builder(
400        &mut self,
401    ) -> HummockResult<SstableBuilder<BatchUploadWriter, Xor16FilterBuilder>> {
402        let id = self.next_id.fetch_add(1, SeqCst);
403        let tracker = self
404            .limiter
405            .require_memory(1)
406            .instrument_await("local_builder_require_memory".verbose())
407            .await;
408        let writer_options = SstableWriterOptions {
409            capacity_hint: Some(self.options.capacity),
410            tracker: Some(tracker),
411            policy: self.policy,
412        };
413        let writer = self
414            .sstable_store
415            .clone()
416            .create_sst_writer(id, writer_options);
417        let table_id_to_vnode = HashMap::from_iter(vec![(
418            TableId::default().as_raw_id(),
419            VirtualNode::COUNT_FOR_TEST,
420        )]);
421        let table_id_to_watermark_serde =
422            HashMap::from_iter(vec![(TableId::default().as_raw_id(), None)]);
423        let builder = SstableBuilder::for_test(
424            id,
425            writer,
426            self.options.clone(),
427            table_id_to_vnode,
428            table_id_to_watermark_serde,
429        );
430
431        Ok(builder)
432    }
433}
434
435#[cfg(test)]
436mod tests {
437    use risingwave_common::catalog::TableId;
438    use risingwave_common::util::epoch::{EpochExt, test_epoch};
439
440    use super::*;
441    use crate::compaction_catalog_manager::{
442        CompactionCatalogAgent, FilterKeyExtractorImpl, FullKeyFilterKeyExtractor,
443    };
444    use crate::hummock::DEFAULT_RESTART_INTERVAL;
445    use crate::hummock::iterator::test_utils::mock_sstable_store;
446    use crate::hummock::test_utils::{default_builder_opt_for_test, test_key_of, test_user_key_of};
447
448    #[tokio::test]
449    async fn test_empty() {
450        let block_size = 1 << 10;
451        let table_capacity = 4 * block_size;
452        let opts = SstableBuilderOptions {
453            capacity: table_capacity,
454            block_capacity: block_size,
455            restart_interval: DEFAULT_RESTART_INTERVAL,
456            bloom_false_positive: 0.1,
457            ..Default::default()
458        };
459        let builder_factory = LocalTableBuilderFactory::new(1001, mock_sstable_store().await, opts);
460        let compaction_catalog_agent_ref = Arc::new(CompactionCatalogAgent::dummy());
461        let builder =
462            CapacitySplitTableBuilder::for_test(builder_factory, compaction_catalog_agent_ref);
463        let results = builder.finish().await.unwrap();
464        assert!(results.is_empty());
465    }
466
467    #[tokio::test]
468    async fn test_lots_of_tables() {
469        let block_size = 1 << 10;
470        let table_capacity = 4 * block_size;
471        let opts = SstableBuilderOptions {
472            capacity: table_capacity,
473            block_capacity: block_size,
474            restart_interval: DEFAULT_RESTART_INTERVAL,
475            bloom_false_positive: 0.1,
476            ..Default::default()
477        };
478        let compaction_catalog_agent_ref = CompactionCatalogAgent::for_test(vec![0]);
479
480        let builder_factory = LocalTableBuilderFactory::new(1001, mock_sstable_store().await, opts);
481        let mut builder =
482            CapacitySplitTableBuilder::for_test(builder_factory, compaction_catalog_agent_ref);
483
484        for i in 0..table_capacity {
485            builder
486                .add_full_key_for_test(
487                    FullKey::from_user_key(
488                        test_user_key_of(i).as_ref(),
489                        test_epoch((table_capacity - i) as u64),
490                    ),
491                    HummockValue::put(b"value"),
492                    true,
493                )
494                .await
495                .unwrap();
496        }
497
498        let results = builder.finish().await.unwrap();
499        assert!(results.len() > 1);
500    }
501
502    #[tokio::test]
503    async fn test_table_seal() {
504        let opts = default_builder_opt_for_test();
505        let compaction_catalog_agent_ref = CompactionCatalogAgent::for_test(vec![0]);
506        let mut builder = CapacitySplitTableBuilder::for_test(
507            LocalTableBuilderFactory::new(1001, mock_sstable_store().await, opts),
508            compaction_catalog_agent_ref,
509        );
510        let mut epoch = test_epoch(100);
511
512        macro_rules! add {
513            () => {
514                epoch.dec_epoch();
515                builder
516                    .add_full_key_for_test(
517                        FullKey::from_user_key(test_user_key_of(1).as_ref(), epoch),
518                        HummockValue::put(b"v"),
519                        true,
520                    )
521                    .await
522                    .unwrap();
523            };
524        }
525
526        assert_eq!(builder.len(), 0);
527        builder.seal_current().await.unwrap();
528        assert_eq!(builder.len(), 0);
529        add!();
530        assert_eq!(builder.len(), 1);
531        add!();
532        assert_eq!(builder.len(), 1);
533        builder.seal_current().await.unwrap();
534        assert_eq!(builder.len(), 1);
535        add!();
536        assert_eq!(builder.len(), 2);
537        builder.seal_current().await.unwrap();
538        assert_eq!(builder.len(), 2);
539        builder.seal_current().await.unwrap();
540        assert_eq!(builder.len(), 2);
541
542        let results = builder.finish().await.unwrap();
543        assert_eq!(results.len(), 2);
544    }
545
546    #[tokio::test]
547    async fn test_initial_not_allowed_split() {
548        let opts = default_builder_opt_for_test();
549        let compaction_catalog_agent_ref = CompactionCatalogAgent::for_test(vec![0]);
550        let mut builder = CapacitySplitTableBuilder::for_test(
551            LocalTableBuilderFactory::new(1001, mock_sstable_store().await, opts),
552            compaction_catalog_agent_ref,
553        );
554        builder
555            .add_full_key_for_test(test_key_of(0).to_ref(), HummockValue::put(b"v"), false)
556            .await
557            .unwrap();
558    }
559
560    #[tokio::test]
561    async fn test_check_table_and_vnode_change() {
562        let block_size = 256;
563        let table_capacity = 2 * block_size;
564        let opts = SstableBuilderOptions {
565            capacity: table_capacity,
566            block_capacity: block_size,
567            restart_interval: DEFAULT_RESTART_INTERVAL,
568            bloom_false_positive: 0.1,
569            ..Default::default()
570        };
571
572        {
573            let table_partition_vnode = BTreeMap::from([
574                (1_u32.into(), 4_u32),
575                (2_u32.into(), 4_u32),
576                (3_u32.into(), 4_u32),
577            ]);
578
579            let compaction_catalog_agent_ref =
580                CompactionCatalogAgent::for_test(vec![0, 1, 2, 3, 4, 5]);
581            let mut builder = CapacitySplitTableBuilder::new(
582                LocalTableBuilderFactory::new(1001, mock_sstable_store().await, opts.clone()),
583                Arc::new(CompactorMetrics::unused()),
584                None,
585                table_partition_vnode,
586                None,
587                compaction_catalog_agent_ref,
588            );
589
590            let mut table_key = VirtualNode::from_index(0).to_be_bytes().to_vec();
591            table_key.extend_from_slice("a".as_bytes());
592
593            let switch_builder =
594                builder.check_switch_builder(&UserKey::for_test(TableId::from(1), &table_key));
595            assert!(switch_builder);
596
597            let mut table_key = VirtualNode::from_index(62).to_be_bytes().to_vec();
598            table_key.extend_from_slice("a".as_bytes());
599            let switch_builder =
600                builder.check_switch_builder(&UserKey::for_test(TableId::from(1), &table_key));
601            assert!(!switch_builder);
602
603            let mut table_key = VirtualNode::from_index(63).to_be_bytes().to_vec();
604            table_key.extend_from_slice("a".as_bytes());
605            let switch_builder =
606                builder.check_switch_builder(&UserKey::for_test(TableId::from(1), &table_key));
607            assert!(!switch_builder);
608
609            let mut table_key = VirtualNode::from_index(64).to_be_bytes().to_vec();
610            table_key.extend_from_slice("a".as_bytes());
611            let switch_builder =
612                builder.check_switch_builder(&UserKey::for_test(TableId::from(1), &table_key));
613            assert!(switch_builder);
614
615            let switch_builder =
616                builder.check_switch_builder(&UserKey::for_test(TableId::from(2), &table_key));
617            assert!(switch_builder);
618            let switch_builder =
619                builder.check_switch_builder(&UserKey::for_test(TableId::from(3), &table_key));
620            assert!(switch_builder);
621            let switch_builder =
622                builder.check_switch_builder(&UserKey::for_test(TableId::from(4), &table_key));
623            assert!(switch_builder);
624            let switch_builder =
625                builder.check_switch_builder(&UserKey::for_test(TableId::from(5), &table_key));
626            assert!(!switch_builder);
627        }
628
629        {
630            // Test different table vnode count
631            let table_partition_vnode = BTreeMap::from([
632                (1_u32.into(), 4_u32),
633                (2_u32.into(), 4_u32),
634                (3_u32.into(), 4_u32),
635            ]);
636
637            let table_id_to_vnode =
638                HashMap::from_iter(vec![(1.into(), 64), (2.into(), 128), (3.into(), 256)]);
639            let table_id_to_watermark_serde =
640                HashMap::from_iter(vec![(1.into(), None), (2.into(), None), (3.into(), None)]);
641            let compaction_catalog_agent_ref = Arc::new(CompactionCatalogAgent::new(
642                FilterKeyExtractorImpl::FullKey(FullKeyFilterKeyExtractor),
643                table_id_to_vnode,
644                table_id_to_watermark_serde,
645                HashMap::default(),
646            ));
647
648            let mut builder = CapacitySplitTableBuilder::new(
649                LocalTableBuilderFactory::new(1001, mock_sstable_store().await, opts),
650                Arc::new(CompactorMetrics::unused()),
651                None,
652                table_partition_vnode,
653                None,
654                compaction_catalog_agent_ref,
655            );
656
657            let mut table_key = VirtualNode::from_index(0).to_be_bytes().to_vec();
658            table_key.extend_from_slice("a".as_bytes());
659
660            let switch_builder =
661                builder.check_switch_builder(&UserKey::for_test(TableId::from(1), &table_key));
662            assert!(switch_builder);
663
664            let mut table_key = VirtualNode::from_index(15).to_be_bytes().to_vec();
665            table_key.extend_from_slice("a".as_bytes());
666            let switch_builder =
667                builder.check_switch_builder(&UserKey::for_test(TableId::from(1), &table_key));
668            assert!(!switch_builder);
669
670            let mut table_key = VirtualNode::from_index(16).to_be_bytes().to_vec();
671            table_key.extend_from_slice("a".as_bytes());
672            let switch_builder =
673                builder.check_switch_builder(&UserKey::for_test(TableId::from(1), &table_key));
674            assert!(switch_builder);
675
676            let mut table_key = VirtualNode::from_index(0).to_be_bytes().to_vec();
677            table_key.extend_from_slice("a".as_bytes());
678            let switch_builder =
679                builder.check_switch_builder(&UserKey::for_test(TableId::from(2), &table_key));
680            assert!(switch_builder);
681
682            let mut table_key = VirtualNode::from_index(16).to_be_bytes().to_vec();
683            table_key.extend_from_slice("a".as_bytes());
684            let switch_builder =
685                builder.check_switch_builder(&UserKey::for_test(TableId::from(2), &table_key));
686            assert!(!switch_builder);
687
688            let mut table_key = VirtualNode::from_index(31).to_be_bytes().to_vec();
689            table_key.extend_from_slice("a".as_bytes());
690            let switch_builder =
691                builder.check_switch_builder(&UserKey::for_test(TableId::from(2), &table_key));
692            assert!(!switch_builder);
693
694            let mut table_key = VirtualNode::from_index(32).to_be_bytes().to_vec();
695            table_key.extend_from_slice("a".as_bytes());
696            let switch_builder =
697                builder.check_switch_builder(&UserKey::for_test(TableId::from(2), &table_key));
698            assert!(switch_builder);
699
700            let mut table_key = VirtualNode::from_index(64).to_be_bytes().to_vec();
701            table_key.extend_from_slice("a".as_bytes());
702            let switch_builder =
703                builder.check_switch_builder(&UserKey::for_test(TableId::from(2), &table_key));
704            assert!(switch_builder);
705
706            let mut table_key = VirtualNode::from_index(0).to_be_bytes().to_vec();
707            table_key.extend_from_slice("a".as_bytes());
708            let switch_builder =
709                builder.check_switch_builder(&UserKey::for_test(TableId::from(3), &table_key));
710            assert!(switch_builder);
711
712            let mut table_key = VirtualNode::from_index(16).to_be_bytes().to_vec();
713            table_key.extend_from_slice("a".as_bytes());
714            let switch_builder =
715                builder.check_switch_builder(&UserKey::for_test(TableId::from(3), &table_key));
716            assert!(!switch_builder);
717
718            let mut table_key = VirtualNode::from_index(32).to_be_bytes().to_vec();
719            table_key.extend_from_slice("a".as_bytes());
720            let switch_builder =
721                builder.check_switch_builder(&UserKey::for_test(TableId::from(3), &table_key));
722            assert!(!switch_builder);
723
724            let mut table_key = VirtualNode::from_index(63).to_be_bytes().to_vec();
725            table_key.extend_from_slice("a".as_bytes());
726            let switch_builder =
727                builder.check_switch_builder(&UserKey::for_test(TableId::from(3), &table_key));
728            assert!(!switch_builder);
729
730            let mut table_key = VirtualNode::from_index(64).to_be_bytes().to_vec();
731            table_key.extend_from_slice("a".as_bytes());
732            let switch_builder =
733                builder.check_switch_builder(&UserKey::for_test(TableId::from(3), &table_key));
734            assert!(switch_builder);
735        }
736    }
737}