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(
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 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 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 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 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 switch_builder = true;
290
291 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 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 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 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
369pub 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 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}