1use 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
51pub struct CapacitySplitTableBuilder<F>
56where
57 F: TableBuilderFactory,
58{
59 builder_factory: F,
61
62 sst_outputs: Vec<LocalSstableInfo>,
63
64 current_builder: Option<SstableBuilder<F::Writer, F::Filter>>,
65
66 pub compactor_metrics: Arc<CompactorMetrics>,
68
69 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 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 #[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 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 pub fn len(&self) -> usize {
145 self.sst_outputs.len() + self.current_builder.is_some() as usize
146 }
147
148 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 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 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 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 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 switch_builder = true;
289
290 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 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 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 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
368pub 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 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}