Skip to main content

risingwave_ctl/
lib.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
15#![warn(clippy::large_futures, clippy::large_stack_frames)]
16#![allow(unfulfilled_lint_expectations)]
17#![recursion_limit = "256"]
18
19use anyhow::Result;
20use clap::{ArgGroup, Args, Parser, Subcommand};
21use cmd_impl::bench::BenchCommands;
22use cmd_impl::hummock::SstDumpArgs;
23use itertools::Itertools;
24use risingwave_common::util::tokio_util::sync::CancellationToken;
25use risingwave_hummock_sdk::{HummockEpoch, HummockVersionId};
26use risingwave_meta::backup_restore::RestoreOpts;
27use risingwave_pb::hummock::rise_ctl_update_compaction_config_request::{
28    CompressionAlgorithm, SstableFilterLayout, SstableFilterType,
29};
30use risingwave_pb::id::{CompactionGroupId, FragmentId, HummockSstableId, JobId, TableId};
31use thiserror_ext::AsReport;
32
33use crate::cmd_impl::hummock::{
34    build_compaction_config_vec, list_pinned_versions, migrate_legacy_object,
35};
36use crate::cmd_impl::profile::ProfileWorkerType;
37use crate::cmd_impl::scale::set_cdc_table_backfill_parallelism;
38use crate::cmd_impl::throttle::apply_throttle;
39use crate::common::CtlContext;
40
41pub mod cmd_impl;
42pub mod common;
43
44/// risectl provides internal access to the RisingWave cluster. Generally, you will need
45/// to provide the meta address and the state store URL to enable risectl to access the cluster. You
46/// must start RisingWave in full cluster mode (e.g. enable MinIO and compactor in risedev.yml)
47/// instead of playground mode to use this tool. risectl will read environment variables
48/// `RW_META_ADDR` and `RW_HUMMOCK_URL` to configure itself.
49#[derive(Parser)]
50#[clap(version, about = "The DevOps tool that provides internal access to the RisingWave cluster", long_about = None)]
51#[clap(propagate_version = true)]
52#[clap(infer_subcommands = true)]
53pub struct CliOpts {
54    #[clap(subcommand)]
55    command: Commands,
56}
57
58#[derive(Subcommand)]
59#[clap(infer_subcommands = true)]
60enum Commands {
61    /// Commands for Compute
62    #[clap(subcommand)]
63    Compute(ComputeCommands),
64    /// Commands for Hummock
65    #[clap(subcommand)]
66    Hummock(HummockCommands),
67    /// Commands for Tables
68    #[clap(subcommand)]
69    Table(TableCommands),
70    /// Commands for Meta
71    #[clap(subcommand)]
72    Meta(MetaCommands),
73    /// Commands for Benchmarks
74    #[clap(subcommand)]
75    Bench(BenchCommands),
76    /// Commands for await-tree, such as dumping, analyzing and transcribing
77    #[clap(subcommand)]
78    #[clap(visible_alias("trace"))]
79    AwaitTree(AwaitTreeCommands),
80    /// Commands for profiling nodes
81    #[clap(subcommand)]
82    Profile(ProfileCommands),
83    #[clap(subcommand)]
84    Throttle(ThrottleCommands),
85    /// Commands for Self-testing
86    #[clap(subcommand, hide = true)]
87    Test(TestCommands),
88}
89
90#[derive(Subcommand)]
91enum ComputeCommands {
92    /// Show all the configuration parameters on compute node
93    ShowConfig { host: String },
94}
95
96#[expect(clippy::large_enum_variant)]
97#[derive(Subcommand)]
98enum HummockCommands {
99    /// list latest Hummock version on meta node
100    ListVersion {
101        #[clap(short, long = "verbose", default_value_t = false)]
102        verbose: bool,
103
104        #[clap(long = "verbose_key_range", default_value_t = false)]
105        verbose_key_range: bool,
106    },
107
108    /// list hummock version deltas in the meta store
109    ListVersionDeltas {
110        #[clap(short, long = "start-version-delta-id", default_value_t = HummockVersionId::new(0))]
111        start_id: HummockVersionId,
112
113        #[clap(short, long = "num-epochs", default_value_t = 100)]
114        num_epochs: u32,
115    },
116    /// Forbid hummock commit new epochs, which is a prerequisite for compaction deterministic test
117    DisableCommitEpoch,
118    /// list all Hummock key-value pairs
119    ListKv {
120        #[clap(short, long = "epoch", default_value_t = HummockEpoch::MAX)]
121        epoch: u64,
122
123        #[clap(short, long = "table-id")]
124        table_id: TableId,
125
126        // data directory for hummock state store. None: use default
127        data_dir: Option<String>,
128
129        #[clap(short, long = "use-new-object-prefix-strategy", default_value = "true")]
130        use_new_object_prefix_strategy: bool,
131    },
132    SstDump(SstDumpArgs),
133    /// trigger a targeted compaction through `compaction_group_id`
134    TriggerManualCompaction {
135        #[clap(short, long = "compaction-group-id", default_value_t = CompactionGroupId::new(2))]
136        compaction_group_id: CompactionGroupId,
137
138        #[clap(short, long = "table-id", default_value_t = 0)]
139        table_id: u32,
140
141        #[clap(short, long = "level", value_delimiter = ',', default_values_t = vec![1u32])]
142        levels: Vec<u32>,
143
144        #[clap(long = "target-level")]
145        target_level: Option<u32>,
146
147        #[clap(short, long = "sst-ids", value_delimiter = ',')]
148        sst_ids: Vec<HummockSstableId>,
149
150        #[clap(long = "exclusive", default_value_t = false)]
151        exclusive: bool,
152
153        #[clap(long = "retry-interval-ms", default_value_t = 1000)]
154        retry_interval_ms: u64,
155    },
156    /// Trigger a full GC for SSTs that is not pinned, with timestamp <= now -
157    /// `sst_retention_time_sec`, and with `prefix` in path.
158    TriggerFullGc {
159        #[clap(short, long = "sst_retention_time_sec", default_value_t = 259200)]
160        sst_retention_time_sec: u64,
161        #[clap(short, long = "prefix", required = false)]
162        prefix: Option<String>,
163    },
164    /// List pinned versions of each worker.
165    ListPinnedVersions {},
166    /// List all compaction groups.
167    ListCompactionGroup,
168    /// Update compaction config for compaction groups.
169    UpdateCompactionConfig {
170        #[clap(long, value_delimiter = ',')]
171        compaction_group_ids: Vec<CompactionGroupId>,
172        #[clap(long)]
173        max_bytes_for_level_base: Option<u64>,
174        #[clap(long)]
175        max_bytes_for_level_multiplier: Option<u64>,
176        #[clap(long)]
177        max_compaction_bytes: Option<u64>,
178        #[clap(long)]
179        sub_level_max_compaction_bytes: Option<u64>,
180        #[clap(long)]
181        level0_tier_compact_file_number: Option<u64>,
182        #[clap(long)]
183        target_file_size_base: Option<u64>,
184        #[clap(long)]
185        compaction_filter_mask: Option<u32>,
186        #[clap(long)]
187        max_sub_compaction: Option<u32>,
188        #[clap(long)]
189        level0_stop_write_threshold_sub_level_number: Option<u64>,
190        #[clap(long)]
191        level0_sub_level_compact_level_count: Option<u32>,
192        #[clap(long)]
193        max_space_reclaim_bytes: Option<u64>,
194        #[clap(long)]
195        level0_max_compact_file_number: Option<u64>,
196        #[clap(long)]
197        level0_overlapping_sub_level_compact_level_count: Option<u32>,
198        #[clap(long)]
199        enable_emergency_picker: Option<bool>,
200        #[clap(long)]
201        tombstone_reclaim_ratio: Option<u32>,
202        #[clap(long)]
203        compression_level: Option<u32>,
204        #[clap(long)]
205        compression_algorithm: Option<String>,
206        /// LSM level index to update, e.g. 0 for L0, 6 for L6.
207        #[clap(long, requires = "sstable_filter_type")]
208        sstable_filter_type_level: Option<u32>,
209        /// SST filter type to use for this level. Supported values: "none", "xor16", "xor8".
210        #[clap(long, requires = "sstable_filter_type_level")]
211        sstable_filter_type: Option<String>,
212        /// LSM level index to update, e.g. 0 for L0, 6 for L6.
213        #[clap(long, requires = "sstable_filter_layout")]
214        sstable_filter_layout_level: Option<u32>,
215        /// Filter layout for this level.
216        ///
217        /// Supported values:
218        /// - "auto": decide by heuristics (currently by kv-count threshold)
219        /// - "plain": always use a single non-blocked filter, ignoring kv-count threshold
220        /// - "blocked": always use block-based filters, ignoring kv-count threshold
221        #[clap(long, requires = "sstable_filter_layout_level")]
222        sstable_filter_layout: Option<String>,
223        #[clap(long)]
224        max_l0_compact_level: Option<u32>,
225        #[clap(long)]
226        sst_allowed_trivial_move_min_size: Option<u64>,
227        #[clap(long)]
228        disable_auto_group_scheduling: Option<bool>,
229        #[clap(long)]
230        max_overlapping_level_size: Option<u64>,
231        #[clap(long)]
232        sst_allowed_trivial_move_max_count: Option<u32>,
233        #[clap(long)]
234        emergency_level0_sst_file_count: Option<u32>,
235        #[clap(long)]
236        emergency_level0_sub_level_partition: Option<u32>,
237        #[clap(long)]
238        level0_stop_write_threshold_max_sst_count: Option<u32>,
239        #[clap(long)]
240        level0_stop_write_threshold_max_size: Option<u64>,
241        #[clap(long)]
242        enable_optimize_l0_interval_selection: Option<bool>,
243        /// KV-count threshold for using blocked xor filters when output layout is "auto".
244        ///
245        /// Note: shared-buffer flush does not read compaction group config, so this setting only
246        /// applies to compaction tasks.
247        #[clap(long)]
248        blocked_xor_filter_kv_count_threshold: Option<u64>,
249        #[clap(long)]
250        max_vnode_key_range_bytes: Option<u64>,
251    },
252    /// Split given compaction group into two. Moves the given tables to the new group.
253    SplitCompactionGroup {
254        #[clap(long)]
255        compaction_group_id: CompactionGroupId,
256        #[clap(long, value_delimiter = ',')]
257        table_ids: Vec<TableId>,
258        #[clap(long, default_value_t = 0)]
259        partition_vnode_count: u32,
260    },
261    /// Pause version checkpoint, which subsequently pauses GC of delta log and SST object.
262    PauseVersionCheckpoint,
263    /// Resume version checkpoint, which subsequently resumes GC of delta log and SST object.
264    ResumeVersionCheckpoint,
265    /// Replay version from the checkpoint one to the latest one.
266    ReplayVersion,
267    /// List compaction status
268    ListCompactionStatus {
269        #[clap(short, long = "verbose", default_value_t = false)]
270        verbose: bool,
271    },
272    GetCompactionScore {
273        #[clap(long)]
274        compaction_group_id: CompactionGroupId,
275    },
276    /// Validate the current `HummockVersion`.
277    ValidateVersion,
278    /// Rebuild table stats
279    RebuildTableStats,
280    CancelCompactTask {
281        #[clap(short, long)]
282        task_id: u64,
283    },
284    PrintUserKeyInArchive {
285        /// The ident of the archive file in object store. It's also the first Hummock version id of this archive.
286        #[clap(long, value_delimiter = ',')]
287        archive_ids: Vec<u64>,
288        /// The data directory of Hummock storage, where `SSTable` objects can be found.
289        #[clap(long)]
290        data_dir: String,
291        /// KVs that are matched with the user key are printed.
292        #[clap(long)]
293        user_key: String,
294        #[clap(short, long = "use-new-object-prefix-strategy", default_value = "true")]
295        use_new_object_prefix_strategy: bool,
296    },
297    PrintVersionDeltaInArchive {
298        /// The ident of the archive file in object store. It's also the first Hummock version id of this archive.
299        #[clap(long, value_delimiter = ',')]
300        archive_ids: Vec<u64>,
301        /// The data directory of Hummock storage, where `SSTable` objects can be found.
302        #[clap(long)]
303        data_dir: String,
304        /// Version deltas that are related to the SST id are printed.
305        #[clap(long)]
306        sst_id: HummockSstableId,
307        #[clap(short, long = "use-new-object-prefix-strategy", default_value = "true")]
308        use_new_object_prefix_strategy: bool,
309    },
310    TieredCacheTracing {
311        #[clap(long)]
312        enable: bool,
313        #[clap(long)]
314        record_hybrid_insert_threshold_ms: Option<u32>,
315        #[clap(long)]
316        record_hybrid_get_threshold_ms: Option<u32>,
317        #[clap(long)]
318        record_hybrid_obtain_threshold_ms: Option<u32>,
319        #[clap(long)]
320        record_hybrid_remove_threshold_ms: Option<u32>,
321        #[clap(long)]
322        record_hybrid_fetch_threshold_ms: Option<u32>,
323    },
324    MergeCompactionGroup {
325        #[clap(long)]
326        left_group_id: CompactionGroupId,
327        #[clap(long)]
328        right_group_id: CompactionGroupId,
329    },
330    MigrateLegacyObject {
331        url: String,
332        source_dir: String,
333        target_dir: String,
334        #[clap(long, default_value = "100")]
335        concurrency: u32,
336    },
337    ResizeCache {
338        #[clap(long)]
339        meta_cache_capacity_mb: Option<u64>,
340        #[clap(long)]
341        data_cache_capacity_mb: Option<u64>,
342        #[clap(
343            long,
344            help = "Clear the local file-backed meta cache on all compute nodes (best effort)"
345        )]
346        clear_meta_cache: bool,
347        #[clap(
348            long,
349            help = "Clear the local file-backed data cache on all compute nodes (best effort)"
350        )]
351        clear_data_cache: bool,
352    },
353    /// Table cache refill tools.
354    #[clap(subcommand)]
355    Refill(RefillCommands),
356}
357
358#[derive(Subcommand)]
359enum RefillCommands {
360    /// Collect table cache refill stats from compute nodes.
361    Stats,
362    /// Warm up a Hummock-backed table in the streaming compute node caches.
363    WarmUp {
364        #[clap(long)]
365        table_id: TableId,
366        /// Maximum number of vnodes scanned concurrently on each compute node.
367        #[clap(long, default_value_t = 16)]
368        concurrency: u32,
369    },
370}
371
372#[derive(Subcommand)]
373enum TableCommands {
374    /// scan a state table with MV name
375    Scan {
376        /// name of the materialized view to operate on
377        mv_name: String,
378        // data directory for hummock state store. None: use default
379        data_dir: Option<String>,
380
381        #[clap(short, long = "use-new-object-prefix-strategy", default_value = "true")]
382        use_new_object_prefix_strategy: bool,
383    },
384    /// scan a state table using Id
385    ScanById {
386        /// id of the state table to operate on
387        table_id: TableId,
388        // data directory for hummock state store. None: use default
389        data_dir: Option<String>,
390        #[clap(short, long = "use-new-object-prefix-strategy", default_value = "true")]
391        use_new_object_prefix_strategy: bool,
392    },
393    /// list all state tables
394    List,
395}
396
397#[derive(Subcommand)]
398#[expect(clippy::large_enum_variant)]
399enum MetaCommands {
400    /// pause the stream graph
401    Pause,
402    /// resume the stream graph
403    Resume,
404    /// force resume backfill for troubleshooting
405    #[clap(
406        group(
407            ArgGroup::new("resume_backfill_target")
408                .required(true)
409                .args(&["job_id", "fragment_id"])
410        )
411    )]
412    ResumeBackfill {
413        #[clap(long)]
414        job_id: Option<JobId>,
415        #[clap(long)]
416        fragment_id: Option<FragmentId>,
417    },
418    /// get cluster info
419    ClusterInfo,
420    /// get source split info
421    SourceSplitInfo {
422        #[clap(long)]
423        ignore_id: bool,
424    },
425    /// Reschedule the actors in the stream graph
426    ///
427    /// The format is `fragment_id-[worker_id:count]+[worker_id:count]`
428    /// You can provide either decreased `worker_ids` only or increased only, but decreased should be preceded by
429    /// `added` when both are provided.
430    ///
431    /// For example, for plan `100-[1:1]+[4:1]` the follow request will be generated:
432    /// ```text
433    /// {
434    ///     100: WorkerReschedule {
435    ///         increased_actor_count: { 1: 1 },
436    ///         decreased_actor_count: { 4: 1 },
437    ///     }
438    /// }
439    /// ```
440    /// Use ; to separate multiple fragment
441    #[clap(verbatim_doc_comment)]
442    #[clap(group(clap::ArgGroup::new("input_group").required(true).args(&["plan", "from"])))]
443    Reschedule {
444        /// Plan of reschedule, needs to be used with `revision`
445        #[clap(long, requires = "revision")]
446        plan: Option<String>,
447        /// Revision of the plan
448        #[clap(long)]
449        revision: Option<u64>,
450        /// Reschedule from a specific file
451        #[clap(long, conflicts_with = "revision", value_hint = clap::ValueHint::AnyPath)]
452        from: Option<String>,
453        /// Show the plan only, no actual operation
454        #[clap(long, default_value = "false")]
455        dry_run: bool,
456        /// Resolve `NO_SHUFFLE` upstream
457        #[clap(long, default_value = "false")]
458        resolve_no_shuffle: bool,
459    },
460    /// backup meta by taking a meta snapshot
461    BackupMeta {
462        #[clap(long)]
463        remarks: Option<String>,
464    },
465    /// restore meta by recovering from a meta snapshot
466    RestoreMeta {
467        #[command(flatten)]
468        opts: RestoreOpts,
469    },
470    /// delete meta snapshots
471    DeleteMetaSnapshots {
472        #[clap(long, value_delimiter = ',')]
473        snapshot_ids: Vec<u64>,
474    },
475
476    /// List all existing connections in the catalog
477    ListConnections,
478
479    /// List fragment mapping for serving
480    ListServingFragmentMapping,
481
482    /// Unregister workers from the cluster
483    UnregisterWorkers {
484        /// The workers that needs to be unregistered, `worker_id` and `worker_host:worker_port` are both supported
485        #[clap(
486            long,
487            required = true,
488            value_delimiter = ',',
489            value_name = "worker_id or worker_host:worker_port, ..."
490        )]
491        workers: Vec<String>,
492
493        /// Automatic yes to prompts
494        #[clap(short = 'y', long, default_value_t = false)]
495        yes: bool,
496
497        /// The worker not found will be ignored
498        #[clap(long, default_value_t = false)]
499        ignore_not_found: bool,
500
501        /// Checking whether the fragment is occupied by workers
502        #[clap(long, default_value_t = false)]
503        check_fragment_occupied: bool,
504    },
505
506    /// Validate source interface for the cloud team
507    ValidateSource {
508        /// With properties in json format
509        /// If privatelink is used, specify `connection.id` instead of `connection.name`
510        #[clap(long)]
511        props: String,
512    },
513
514    SetCdcTableBackfillParallelism {
515        #[clap(long, required = true)]
516        table_id: u32,
517        #[clap(long, required = true)]
518        parallelism: u32,
519    },
520
521    /// Alter source connector properties with pause/resume orchestration (UNSAFE)
522    /// This operation pauses the source, updates properties, and resumes.
523    AlterSourcePropertiesSafe {
524        /// Source ID to update
525        #[clap(long)]
526        source_id: u32,
527        /// Properties to change in JSON format, e.g. '{"properties.bootstrap.server": "new-broker:9092"}'
528        #[clap(long)]
529        props: String,
530        /// Reset split assignments after property change (for major upstream changes)
531        #[clap(long, default_value_t = false)]
532        reset_splits: bool,
533    },
534
535    /// Reset source split assignments (UNSAFE - admin only)
536    /// Clears cached split state and triggers re-discovery from upstream.
537    ResetSourceSplits {
538        /// Source ID to reset
539        #[clap(long)]
540        source_id: u32,
541    },
542
543    /// Inject specific offsets into source splits (UNSAFE - admin only)
544    /// WARNING: This can cause data duplication or loss!
545    InjectSourceOffsets {
546        /// Source ID to inject offsets for
547        #[clap(long)]
548        source_id: u32,
549        /// Split offsets in JSON format, e.g. '{"split-0": "100", "split-1": "200"}'
550        #[clap(long)]
551        offsets: String,
552    },
553
554    /// Apply all schema changes under `src/meta/model/migration` to the meta
555    /// store without starting a meta node. Mirrors `SqlMetaStore::up`.
556    CreateMetaStoreSchema {
557        #[command(flatten)]
558        opts: cmd_impl::meta::CreateMetaStoreSchemaOpts,
559    },
560}
561
562#[derive(Subcommand, Clone, Debug)]
563pub enum AwaitTreeCommands {
564    /// Dump Await Tree
565    Dump {
566        /// The format of actor traces in the diagnose file. Allowed values: `json`, `text`. `json` by default.
567        #[clap(short, long = "actor-traces-format")]
568        actor_traces_format: Option<String>,
569    },
570    /// Analyze Await Tree
571    Analyze {
572        /// The path to the diagnose file, if None, ctl will first pull one from the cluster
573        /// The actor traces format can be either `json` or `text`. The analyze command will
574        /// automatically detect the format.
575        #[clap(long = "path")]
576        path: Option<String>,
577    },
578    /// Transcribe Await Tree From JSON to Text format
579    Transcribe {
580        /// The path to the await tree file to be transcribed
581        #[clap(long = "path")]
582        path: String,
583    },
584}
585
586#[derive(Subcommand, Clone, Debug)]
587enum TestCommands {
588    /// Test if JVM and Java libraries are working
589    Jvm,
590}
591
592#[derive(Subcommand, Clone, Debug)]
593enum ThrottleCommands {
594    Source(ThrottleCommandArgs),
595    Mv(ThrottleCommandArgs),
596    Sink(ThrottleCommandArgs),
597}
598
599#[derive(Clone, Debug, clap::ValueEnum)]
600pub enum ThrottleTypeArg {
601    Dml,
602    Backfill,
603    Source,
604    Sink,
605}
606
607#[derive(Clone, Debug, Args)]
608pub struct ThrottleCommandArgs {
609    /// The ID of the object to throttle
610    #[clap(long, required = true)]
611    id: u32,
612    /// The rate limit to apply
613    #[clap(long)]
614    rate: Option<u32>,
615    /// The type of throttle to apply
616    #[clap(long, value_enum, required = true)]
617    throttle_type: ThrottleTypeArg,
618}
619
620#[derive(Subcommand, Clone, Debug)]
621pub enum ProfileCommands {
622    /// CPU profile
623    Cpu {
624        /// The time to active profiling for (in seconds)
625        #[clap(short, long = "sleep")]
626        sleep: u64,
627        /// Target worker types. Repeatable. Defaults to frontend, compute-node, and compactor.
628        #[clap(long = "worker-type", value_name = "TYPE")]
629        worker_types: Vec<ProfileWorkerType>,
630    },
631    /// Heap profile
632    Heap {
633        /// The output directory of the dumped file
634        #[clap(long = "dir")]
635        dir: Option<String>,
636        /// Target worker types. Repeatable. Defaults to frontend, compute-node, and compactor.
637        #[clap(long = "worker-type", value_name = "TYPE")]
638        worker_types: Vec<ProfileWorkerType>,
639    },
640}
641
642/// Start `risectl` with the given options.
643/// Cancel the operation when the given `shutdown` token triggers.
644/// Log and abort the process if any error occurs.
645///
646/// Note: use [`start_fallible`] if you want to call functionalities of `risectl`
647/// in an embedded manner.
648pub async fn start(opts: CliOpts, shutdown: CancellationToken) {
649    let context = CtlContext::default();
650
651    tokio::select! {
652        _ = shutdown.cancelled() => {
653            // Shutdown requested, clean up the context and return.
654            context.try_close().await;
655        }
656
657        result = start_fallible(opts, &context) => {
658            if let Err(e) = result {
659                eprintln!("Error: {:#?}", e.as_report()); // pretty with backtrace
660                std::process::exit(1);
661            }
662        }
663    }
664}
665
666/// Start `risectl` with the given options.
667/// Return `Err` if any error occurs.
668pub async fn start_fallible(opts: CliOpts, context: &CtlContext) -> Result<()> {
669    let result = start_impl(opts, context).await;
670    context.try_close().await;
671    result
672}
673
674async fn start_impl(opts: CliOpts, context: &CtlContext) -> Result<()> {
675    match opts.command {
676        Commands::Compute(ComputeCommands::ShowConfig { host }) => {
677            cmd_impl::compute::show_config(&host).await?
678        }
679        Commands::Hummock(HummockCommands::DisableCommitEpoch) => {
680            cmd_impl::hummock::disable_commit_epoch(context).await?
681        }
682        Commands::Hummock(HummockCommands::ListVersion {
683            verbose,
684            verbose_key_range,
685        }) => {
686            cmd_impl::hummock::list_version(context, verbose, verbose_key_range).await?;
687        }
688        Commands::Hummock(HummockCommands::ListVersionDeltas {
689            start_id,
690            num_epochs,
691        }) => {
692            cmd_impl::hummock::list_version_deltas(context, start_id, num_epochs).await?;
693        }
694        Commands::Hummock(HummockCommands::ListKv {
695            epoch,
696            table_id,
697            data_dir,
698            use_new_object_prefix_strategy,
699        }) => {
700            cmd_impl::hummock::list_kv(
701                context,
702                epoch,
703                table_id,
704                data_dir,
705                use_new_object_prefix_strategy,
706            )
707            .await?;
708        }
709        Commands::Hummock(HummockCommands::SstDump(args)) => {
710            cmd_impl::hummock::sst_dump(context, args).await.unwrap()
711        }
712        Commands::Hummock(HummockCommands::TriggerManualCompaction {
713            compaction_group_id,
714            table_id,
715            levels,
716            target_level,
717            sst_ids,
718            exclusive,
719            retry_interval_ms,
720        }) => {
721            cmd_impl::hummock::trigger_manual_compaction(
722                context,
723                compaction_group_id,
724                table_id.into(),
725                levels,
726                target_level,
727                sst_ids,
728                exclusive,
729                retry_interval_ms,
730            )
731            .await?
732        }
733        Commands::Hummock(HummockCommands::TriggerFullGc {
734            sst_retention_time_sec,
735            prefix,
736        }) => cmd_impl::hummock::trigger_full_gc(context, sst_retention_time_sec, prefix).await?,
737        Commands::Hummock(HummockCommands::ListPinnedVersions {}) => {
738            list_pinned_versions(context).await?
739        }
740        Commands::Hummock(HummockCommands::ListCompactionGroup) => {
741            cmd_impl::hummock::list_compaction_group(context).await?
742        }
743        Commands::Hummock(HummockCommands::UpdateCompactionConfig {
744            compaction_group_ids,
745            max_bytes_for_level_base,
746            max_bytes_for_level_multiplier,
747            max_compaction_bytes,
748            sub_level_max_compaction_bytes,
749            level0_tier_compact_file_number,
750            target_file_size_base,
751            compaction_filter_mask,
752            max_sub_compaction,
753            level0_stop_write_threshold_sub_level_number,
754            level0_sub_level_compact_level_count,
755            max_space_reclaim_bytes,
756            level0_max_compact_file_number,
757            level0_overlapping_sub_level_compact_level_count,
758            enable_emergency_picker,
759            tombstone_reclaim_ratio,
760            compression_level,
761            compression_algorithm,
762            sstable_filter_type_level,
763            sstable_filter_type,
764            sstable_filter_layout_level,
765            sstable_filter_layout,
766            max_l0_compact_level,
767            sst_allowed_trivial_move_min_size,
768            disable_auto_group_scheduling,
769            max_overlapping_level_size,
770            sst_allowed_trivial_move_max_count,
771            emergency_level0_sst_file_count,
772            emergency_level0_sub_level_partition,
773            level0_stop_write_threshold_max_sst_count,
774            level0_stop_write_threshold_max_size,
775            enable_optimize_l0_interval_selection,
776            blocked_xor_filter_kv_count_threshold,
777            max_vnode_key_range_bytes,
778        }) => {
779            cmd_impl::hummock::update_compaction_config(
780                context,
781                compaction_group_ids,
782                build_compaction_config_vec(
783                    max_bytes_for_level_base,
784                    max_bytes_for_level_multiplier,
785                    max_compaction_bytes,
786                    sub_level_max_compaction_bytes,
787                    level0_tier_compact_file_number,
788                    target_file_size_base,
789                    compaction_filter_mask,
790                    max_sub_compaction,
791                    level0_stop_write_threshold_sub_level_number,
792                    level0_sub_level_compact_level_count,
793                    max_space_reclaim_bytes,
794                    level0_max_compact_file_number,
795                    level0_overlapping_sub_level_compact_level_count,
796                    enable_emergency_picker,
797                    tombstone_reclaim_ratio,
798                    if let Some(level) = compression_level {
799                        assert!(compression_algorithm.is_some());
800                        Some(CompressionAlgorithm {
801                            level,
802                            compression_algorithm: compression_algorithm.unwrap(),
803                        })
804                    } else {
805                        None
806                    },
807                    if let (Some(level), Some(filter_type)) =
808                        (sstable_filter_type_level, sstable_filter_type)
809                    {
810                        Some(SstableFilterType { level, filter_type })
811                    } else {
812                        None
813                    },
814                    if let (Some(level), Some(layout)) =
815                        (sstable_filter_layout_level, sstable_filter_layout)
816                    {
817                        Some(SstableFilterLayout { level, layout })
818                    } else {
819                        None
820                    },
821                    max_l0_compact_level,
822                    sst_allowed_trivial_move_min_size,
823                    disable_auto_group_scheduling,
824                    max_overlapping_level_size,
825                    sst_allowed_trivial_move_max_count,
826                    emergency_level0_sst_file_count,
827                    emergency_level0_sub_level_partition,
828                    level0_stop_write_threshold_max_sst_count,
829                    level0_stop_write_threshold_max_size,
830                    enable_optimize_l0_interval_selection,
831                    blocked_xor_filter_kv_count_threshold,
832                    max_vnode_key_range_bytes,
833                ),
834            )
835            .await?
836        }
837        Commands::Hummock(HummockCommands::SplitCompactionGroup {
838            compaction_group_id,
839            table_ids,
840            partition_vnode_count,
841        }) => {
842            cmd_impl::hummock::split_compaction_group(
843                context,
844                compaction_group_id,
845                &table_ids.into_iter().map_into().collect_vec(),
846                partition_vnode_count,
847            )
848            .await?;
849        }
850        Commands::Hummock(HummockCommands::PauseVersionCheckpoint) => {
851            cmd_impl::hummock::pause_version_checkpoint(context).await?;
852        }
853        Commands::Hummock(HummockCommands::ResumeVersionCheckpoint) => {
854            cmd_impl::hummock::resume_version_checkpoint(context).await?;
855        }
856        Commands::Hummock(HummockCommands::ReplayVersion) => {
857            cmd_impl::hummock::replay_version(context).await?;
858        }
859        Commands::Hummock(HummockCommands::ListCompactionStatus { verbose }) => {
860            cmd_impl::hummock::list_compaction_status(context, verbose).await?;
861        }
862        Commands::Hummock(HummockCommands::GetCompactionScore {
863            compaction_group_id,
864        }) => {
865            cmd_impl::hummock::get_compaction_score(context, compaction_group_id).await?;
866        }
867        Commands::Hummock(HummockCommands::ValidateVersion) => {
868            cmd_impl::hummock::validate_version(context).await?;
869        }
870        Commands::Hummock(HummockCommands::RebuildTableStats) => {
871            cmd_impl::hummock::rebuild_table_stats(context).await?;
872        }
873        Commands::Hummock(HummockCommands::CancelCompactTask { task_id }) => {
874            cmd_impl::hummock::cancel_compact_task(context, task_id).await?;
875        }
876        Commands::Hummock(HummockCommands::PrintVersionDeltaInArchive {
877            archive_ids,
878            data_dir,
879            sst_id,
880            use_new_object_prefix_strategy,
881        }) => {
882            cmd_impl::hummock::print_version_delta_in_archive(
883                context,
884                archive_ids.into_iter().map(HummockVersionId::new),
885                data_dir,
886                sst_id,
887                use_new_object_prefix_strategy,
888            )
889            .await?;
890        }
891        Commands::Hummock(HummockCommands::PrintUserKeyInArchive {
892            archive_ids,
893            data_dir,
894            user_key,
895            use_new_object_prefix_strategy,
896        }) => {
897            cmd_impl::hummock::print_user_key_in_archive(
898                context,
899                archive_ids.into_iter().map(HummockVersionId::new),
900                data_dir,
901                user_key,
902                use_new_object_prefix_strategy,
903            )
904            .await?;
905        }
906        Commands::Hummock(HummockCommands::TieredCacheTracing {
907            enable,
908            record_hybrid_insert_threshold_ms,
909            record_hybrid_get_threshold_ms,
910            record_hybrid_obtain_threshold_ms,
911            record_hybrid_remove_threshold_ms,
912            record_hybrid_fetch_threshold_ms,
913        }) => {
914            cmd_impl::hummock::tiered_cache_tracing(
915                context,
916                enable,
917                record_hybrid_insert_threshold_ms,
918                record_hybrid_get_threshold_ms,
919                record_hybrid_obtain_threshold_ms,
920                record_hybrid_remove_threshold_ms,
921                record_hybrid_fetch_threshold_ms,
922            )
923            .await?
924        }
925        Commands::Hummock(HummockCommands::MergeCompactionGroup {
926            left_group_id,
927            right_group_id,
928        }) => {
929            cmd_impl::hummock::merge_compaction_group(context, left_group_id, right_group_id)
930                .await?
931        }
932
933        Commands::Hummock(HummockCommands::MigrateLegacyObject {
934            url,
935            source_dir,
936            target_dir,
937            concurrency,
938        }) => {
939            migrate_legacy_object(url, source_dir, target_dir, concurrency).await?;
940        }
941        Commands::Hummock(HummockCommands::ResizeCache {
942            meta_cache_capacity_mb,
943            data_cache_capacity_mb,
944            clear_meta_cache,
945            clear_data_cache,
946        }) => {
947            const MIB: u64 = 1024 * 1024;
948            cmd_impl::hummock::resize_cache(
949                context,
950                meta_cache_capacity_mb.map(|v| v * MIB),
951                data_cache_capacity_mb.map(|v| v * MIB),
952                clear_meta_cache,
953                clear_data_cache,
954            )
955            .await?
956        }
957        Commands::Hummock(HummockCommands::Refill(RefillCommands::Stats)) => {
958            cmd_impl::hummock::refill_stats(context).await?
959        }
960        Commands::Hummock(HummockCommands::Refill(RefillCommands::WarmUp {
961            table_id,
962            concurrency,
963        })) => cmd_impl::hummock::warm_up_table_cache(context, table_id, concurrency).await?,
964        Commands::Table(TableCommands::Scan {
965            mv_name,
966            data_dir,
967            use_new_object_prefix_strategy,
968        }) => {
969            cmd_impl::table::scan(context, mv_name, data_dir, use_new_object_prefix_strategy)
970                .await?
971        }
972        Commands::Table(TableCommands::ScanById {
973            table_id,
974            data_dir,
975            use_new_object_prefix_strategy,
976        }) => {
977            cmd_impl::table::scan_id(context, table_id, data_dir, use_new_object_prefix_strategy)
978                .await?
979        }
980        Commands::Table(TableCommands::List) => cmd_impl::table::list(context).await?,
981        Commands::Bench(cmd) => cmd_impl::bench::do_bench(context, cmd).await?,
982        Commands::Meta(MetaCommands::Pause) => cmd_impl::meta::pause(context).await?,
983        Commands::Meta(MetaCommands::Resume) => cmd_impl::meta::resume(context).await?,
984        Commands::Meta(MetaCommands::ResumeBackfill {
985            job_id,
986            fragment_id,
987        }) => cmd_impl::meta::resume_backfill(context, job_id, fragment_id).await?,
988        Commands::Meta(MetaCommands::ClusterInfo) => cmd_impl::meta::cluster_info(context).await?,
989        Commands::Meta(MetaCommands::SourceSplitInfo { ignore_id }) => {
990            cmd_impl::meta::source_split_info(context, ignore_id).await?
991        }
992        Commands::Meta(MetaCommands::Reschedule {
993            from,
994            dry_run,
995            plan,
996            revision,
997            resolve_no_shuffle,
998        }) => {
999            cmd_impl::meta::reschedule(context, plan, revision, from, dry_run, resolve_no_shuffle)
1000                .await?
1001        }
1002        Commands::Meta(MetaCommands::BackupMeta { remarks }) => {
1003            cmd_impl::meta::backup_meta(context, remarks).await?
1004        }
1005        Commands::Meta(MetaCommands::RestoreMeta { opts }) => {
1006            risingwave_meta::backup_restore::restore(opts).await?
1007        }
1008        Commands::Meta(MetaCommands::DeleteMetaSnapshots { snapshot_ids }) => {
1009            cmd_impl::meta::delete_meta_snapshots(context, &snapshot_ids).await?
1010        }
1011        Commands::Meta(MetaCommands::ListConnections) => {
1012            cmd_impl::meta::list_connections(context).await?
1013        }
1014        Commands::Meta(MetaCommands::ListServingFragmentMapping) => {
1015            cmd_impl::meta::list_serving_fragment_mappings(context).await?
1016        }
1017        Commands::Meta(MetaCommands::UnregisterWorkers {
1018            workers,
1019            yes,
1020            ignore_not_found,
1021            check_fragment_occupied,
1022        }) => {
1023            cmd_impl::meta::unregister_workers(
1024                context,
1025                workers,
1026                yes,
1027                ignore_not_found,
1028                check_fragment_occupied,
1029            )
1030            .await?
1031        }
1032        Commands::Meta(MetaCommands::ValidateSource { props }) => {
1033            cmd_impl::meta::validate_source(context, props).await?
1034        }
1035        Commands::AwaitTree(AwaitTreeCommands::Dump {
1036            actor_traces_format,
1037        }) => cmd_impl::await_tree::dump(context, actor_traces_format).await?,
1038        Commands::AwaitTree(AwaitTreeCommands::Analyze { path }) => {
1039            cmd_impl::await_tree::bottleneck_detect(context, path).await?
1040        }
1041        Commands::AwaitTree(AwaitTreeCommands::Transcribe { path }) => {
1042            rw_diagnose_tools::await_tree::transcribe(path)?
1043        }
1044        Commands::Profile(ProfileCommands::Cpu {
1045            sleep,
1046            worker_types,
1047        }) => cmd_impl::profile::cpu_profile(context, sleep, worker_types).await?,
1048        Commands::Profile(ProfileCommands::Heap { dir, worker_types }) => {
1049            cmd_impl::profile::heap_profile(context, dir, worker_types).await?
1050        }
1051        Commands::Throttle(ThrottleCommands::Source(args)) => {
1052            apply_throttle(context, risingwave_pb::meta::PbThrottleTarget::Source, args).await?
1053        }
1054        Commands::Throttle(ThrottleCommands::Mv(args)) => {
1055            apply_throttle(context, risingwave_pb::meta::PbThrottleTarget::Mv, args).await?;
1056        }
1057        Commands::Throttle(ThrottleCommands::Sink(args)) => {
1058            apply_throttle(context, risingwave_pb::meta::PbThrottleTarget::Sink, args).await?;
1059        }
1060        Commands::Meta(MetaCommands::SetCdcTableBackfillParallelism {
1061            table_id,
1062            parallelism,
1063        }) => {
1064            set_cdc_table_backfill_parallelism(context, table_id, parallelism).await?;
1065        }
1066        Commands::Meta(MetaCommands::AlterSourcePropertiesSafe {
1067            source_id,
1068            props,
1069            reset_splits,
1070        }) => {
1071            cmd_impl::meta::alter_source_properties_safe(context, source_id, props, reset_splits)
1072                .await?;
1073        }
1074        Commands::Meta(MetaCommands::ResetSourceSplits { source_id }) => {
1075            cmd_impl::meta::reset_source_splits(context, source_id).await?;
1076        }
1077        Commands::Meta(MetaCommands::InjectSourceOffsets { source_id, offsets }) => {
1078            cmd_impl::meta::inject_source_offsets(context, source_id, offsets).await?;
1079        }
1080        Commands::Meta(MetaCommands::CreateMetaStoreSchema { opts }) => {
1081            cmd_impl::meta::create_meta_store_schema(opts).await?;
1082        }
1083        Commands::Test(TestCommands::Jvm) => cmd_impl::test::test_jvm()?,
1084    }
1085    Ok(())
1086}