mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-09-09 06:52:19 +00:00
+1








c60311973d
* fix(mito2): fence checkpoints during region transitions (#8847) * fix: fence checkpoints during region transitions Signed-off-by: WenyXu <wenymedia@gmail.com> * test(datanode): fix transient downgrade setup Signed-off-by: WenyXu <wenymedia@gmail.com> * test(mito2): fix checkpoint lifecycle test setup Signed-off-by: WenyXu <wenymedia@gmail.com> * test(mito2): cover cancelled downgrade waiter retry Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(mito2): fence direct follower transitions Signed-off-by: WenyXu <wenymedia@gmail.com> * test: trim checkpoint transition coverage Signed-off-by: WenyXu <wenymedia@gmail.com> * refactor(mito2): clarify checkpoint task lifecycle Signed-off-by: WenyXu <wenymedia@gmail.com> --------- Signed-off-by: WenyXu <wenymedia@gmail.com> (cherry picked from commita502dfdefd) * fix(mito2): split SSTs at primary key series boundaries (#8888) * fix(mito2): split SSTs at series boundaries Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * test(mito2): cover SST splitting without primary key Also document the sortedness precondition and the series boundary split semantics on write_all_flat/write_all_flat_as_primary_key and the new split helpers. Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * test(mito2): avoid per-row Vec allocation for empty primary key Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> * Update src/mito2/src/sst/parquet/writer.rs Co-authored-by: Yingwen <realevenyag@gmail.com> --------- Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> Co-authored-by: Yingwen <realevenyag@gmail.com> (cherry picked from commit4ac3423261) * fix(meta): allow manual migration from offline datanodes (#8934) * fix(meta): allow migration from offline datanodes Signed-off-by: WenyXu <wenymedia@gmail.com> * test: fix offline migration event actor Signed-off-by: WenyXu <wenymedia@gmail.com> * test: read migration routes from metadata Signed-off-by: WenyXu <wenymedia@gmail.com> --------- Signed-off-by: WenyXu <wenymedia@gmail.com> (cherry picked from commit8a473c5bf0) * fix(flight): bound DoGet response wait (#8943) * fix(flight): defer datanode query initialization Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(client): retain Flight stream peer context Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(client): improve Flight stream diagnostics Signed-off-by: WenyXu <wenymedia@gmail.com> --------- Signed-off-by: WenyXu <wenymedia@gmail.com> (cherry picked from commit28398138ec) * fix(query): keep INSERT timestamp conversion out of the source query (#8911) * fix(query): keep INSERT timestamp conversion out of the source query Interpreting an INSERT's string timestamps used to work by pushing the conversion down into the source query, which changed what that query means. Two consequences: - Pushing through a UNION's DISTINCT moved the dedup key from the raw strings to parsed instants, so rows spelling the same instant differently collapsed into one. On an append-only table that is a silently dropped row. - A UNION branch that needed no conversion (a NULL, or an explicit cast) made the whole column give up, leaving sibling branches on UTC while the rest of the row used the session timezone. Convert at the assignment instead, by routing its cast through a timezone-carrying timestamp type and back. Arrow applies the timezone when a cast target carries one, and stripping it afterwards preserves the value. The source query is no longer touched, so both cases go away and the tree-walking rewrite (roughly 160 lines) is deleted. The rewrite reads source types, so it now runs TypeCoercion first: a UNION still carries its loose per-branch schema before coercion, and retargeting a cast whose input later becomes a timestamp would shift the value rather than reinterpret it. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> * fix(query): address review on INSERT assignment rewrite - Clone the input `Arc` instead of the whole subtree, and only rebuild it when a `Values` row actually changes. - Defer cloning the cast source until the literal-folding path has been ruled out. - Move the UTC check onto `Timezone::is_utc`, replacing a bare string compare. - Cover a prepared `INSERT ... VALUES (?)`: an untyped placeholder types as `Null`, so the assignment cast is left for parameter substitution. Signed-off-by: Dennis Zhuang <killme2008@gmail.com> --------- Signed-off-by: Dennis Zhuang <killme2008@gmail.com> (cherry picked from commit1851f6bf4d) * test: renew etcd TLS certificates (#8956) Signed-off-by: WenyXu <wenymedia@gmail.com> (cherry picked from commit144f83528d) * fix: update tokio-postgres and correct explain/fetch cursor output schema (#8955) * chore(deps): update tokio-postgres * fix: describing fetch cursor and analyze (cherry picked from commitb31f05eb59) Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(flow): avoid insert select HTTP/2 stalls Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> (cherry picked from commitd4a3d88cd7) * fix(query): share record batch forwarding for DML Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> (cherry picked from commitc34bec197c) * fix(mysql): interpret prepared statement datetime params in session timezone (#8923) * fix(mysql): interpret prepared statement datetime params in session timezone Binary DATETIME parameters of server-side prepared statements were converted as if UTC, ignoring the session timezone set via SET time_zone. Convert them with the session timezone and add an integration test covering prepared inserts and predicates under Asia/Shanghai. Signed-off-by: wy471x <wy471x@gmail.com> * refactor: share naive datetime timezone policy via common-time Address review feedback on the prepared-statement timezone fix: - Expose Timestamp::from_naive_datetime in common-time so the DST policy (gap -> error, ambiguous -> earlier instant) lives in one place, shared by the text protocol (Timestamp::from_str) and the MySQL binary protocol. - Route the MySQL prepared-statement datetime conversion through it. - Match the target type before converting datetime params so PreparedStmtTypeMismatch fails fast without wasted conversion. - Use the short Timezone import form for consistency with the rest of servers. Signed-off-by: wy471x <wy471x@gmail.com> --------- Signed-off-by: wy471x <wy471x@gmail.com> Co-authored-by: Ning Sun <sunng@protonmail.com> (cherry picked from commitaaa843104b) * fix(mito2): use target sequence for foreign SSTs (#8946) * fix(mito2): use target sequence for foreign SSTs Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * chore(mito2): address foreign SST review feedback Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> --------- Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> (cherry picked from commitbd7d2c1dfa) * fix: postgres describe for more statements (#8974) * fix: postgres describe for more statements Signed-off-by: Ning Sun <sunning@greptime.com> * fix: cover more show statements Signed-off-by: Ning Sun <sunning@greptime.com> * fix: address review comments - add missing `clippy::too_many_arguments` allow on `query_from_information_schema_dataframe` (CI clippy failure) - take `&ShowKind` in the information-schema dataframe helper so `kind` is no longer cloned at every call site; only the WHERE arm (which needs an owned expression for `sql_to_expr`) clones internally - document why re-applying TQL explain formats never overwrites an existing value (per-query context state) Signed-off-by: Ning Sun <sunning@greptime.com> * chore: trim comments to essentials Signed-off-by: Ning Sun <sunning@greptime.com> --------- Signed-off-by: Ning Sun <sunning@greptime.com> (cherry picked from commitd32cd77505) * fix(promql): resolve derived labels in aggregation arithmetic (#8994) Signed-off-by: shuiyisong <xixing.sys@gmail.com> (cherry picked from commitc4dafb5826) * fix(deps): narrow postgres lockfile update Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(deps): normalize remaining x509 lock reference Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(client): isolate query and control transports Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * docs(frontend): clarify adaptive window purpose Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(grpc): satisfy clippy in transport isolation coverage Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(pipeline): coalesce concurrent pipeline cache misses (#9022) Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * Revert "test(grpc): satisfy clippy in transport isolation coverage" This reverts commit888bcedc96. (cherry picked from commit e0216b4074485bcffaae53348a17091532cb3600) Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * Revert "docs(frontend): clarify adaptive window purpose" This reverts commit84cf69d099. (cherry picked from commit da30e605ad7ebfb2a3fa86ea44b67405e5afde98) Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * Revert "fix(client): isolate query and control transports" This reverts commita1d2dbb050. (cherry picked from commit 03f5c31f544d3d3852beddfa87cff34d04a646ae) Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * refactor(json2): add bounded auto-expansion to the JSON2 vector builder (#8909) * refactor(json2): add bounded auto-expansion to the JSON2 vector builder Signed-off-by: luofucong <luofc@foxmail.com> * resolve PR comments Signed-off-by: luofucong <luofc@foxmail.com> * fix ci Signed-off-by: luofucong <luofc@foxmail.com> --------- Signed-off-by: luofucong <luofc@foxmail.com> (cherry picked from commit76f08d2b3f) (cherry picked from commit 640fa63bd8ee098fac8e8d62d6fb7ca5af4ddcd0) * refactor(json2): optimize JSON2 building without auto-expanded paths (#8928) * refactor(json2): optimize JSON2 building without auto-expanded paths Signed-off-by: luofucong <luofc@foxmail.com> * resolve PR comments Signed-off-by: luofucong <luofc@foxmail.com> * avoid panicking memtable write Signed-off-by: luofucong <luofc@foxmail.com> --------- Signed-off-by: luofucong <luofc@foxmail.com> (cherry picked from commit7fd0a7bb98) (cherry picked from commit 63c68ee60101e2a09e14420051f8f889b41a2a3b) * refactor(json2): support querying v2 storage layout (#8940) * feat(json2): support querying v2 storage layout - route missing JSON2 paths to the v2 remainder field - reconstruct complete values from explicit fields and remainder data - preserve root JSON2 columns across projections and filters - support nested JSON values in json_get string results - add and reorganize JSON2 sqlness coverage Signed-off-by: luofucong <luofc@foxmail.com> * resolve PR comments Signed-off-by: luofucong <luofc@foxmail.com> --------- Signed-off-by: luofucong <luofc@foxmail.com> (cherry picked from commit932f87f7a8) (cherry picked from commit 5af1394a1f319bb99cafa9474b8a5a075be1e805) * feat(pipeline): support table-aware JSON2 transforms (#8964) * feat(pipeline): support table-aware JSON2 transforms Signed-off-by: shuiyisong <xixing.sys@gmail.com> * feat(pipeline): support JSON2 type hints in transforms Signed-off-by: shuiyisong <xixing.sys@gmail.com> * fix(pipeline): default failed JSON2 transforms to null Signed-off-by: shuiyisong <xixing.sys@gmail.com> * refactor(json2): distinguish invalid settings from layout errors Signed-off-by: shuiyisong <xixing.sys@gmail.com> --------- Signed-off-by: shuiyisong <xixing.sys@gmail.com> (cherry picked from commitfb86f6573e) (cherry picked from commit 18a21a9ce017b6887c857a3f3bbb9a83aa7f968b) * refactor: json2 v2 storage layout (#8979) * refactor: json2 v2 storage layout Signed-off-by: luofucong <luofc@foxmail.com> * resolve PR comments Signed-off-by: luofucong <luofc@foxmail.com> * fix ci Signed-off-by: luofucong <luofc@foxmail.com> * rethinking when "needs_remainder" Signed-off-by: luofucong <luofc@foxmail.com> * restore "ReadColumns" Signed-off-by: luofucong <luofc@foxmail.com> * resolve PR comments Signed-off-by: luofucong <luofc@foxmail.com> * fix ci Signed-off-by: luofucong <luofc@foxmail.com> --------- Signed-off-by: luofucong <luofc@foxmail.com> (cherry picked from commit529f046110) (cherry picked from commit 7628d795197c786872730c3a5ef36c5c662d4322) * feat(json2): support JSON2 paths in SQL functions (#9007) feat(query): support JSON2 paths in SQL functions Update the DataFusion fork to expose scalar function planning hooks. Infer JSON2 path output types from scalar, aggregate, and window function signatures, while preserving the default Utf8View behavior for functions that accept arbitrary inputs. Add unit and sqlness coverage for type conflicts, mixed typed and untyped JSON paths, filters, aggregates, and window functions. Signed-off-by: luofucong <luofc@foxmail.com> (cherry picked from commit15317a131b) (cherry picked from commit a945cbc7a4398e6bee74d4d2c67528772faf398d) * feat(json2): support empty and null JSON2 value (#9010) * feat(json2): support empty and null JSON2 value Signed-off-by: fys <fengys1996@gmail.com> * test(json2): cover explicit NULL and omitted-column inserts Signed-off-by: fys <fengys1996@gmail.com> * fix: cargo fmt Signed-off-by: fys <fengys1996@gmail.com> * fix: infer empty JSON object as object type Signed-off-by: fys <fengys1996@gmail.com> --------- Signed-off-by: fys <fengys1996@gmail.com> (cherry picked from commit05c65f54a8) (cherry picked from commit 05c27452ea5e958fab85e0ba4b82f5df205a5d08) * feat(json2): support list indexing for JSON2 columns (#9013) feat(query): support list indexing for JSON2 columns Extend JSON2 paths through DataFusion field-access planning, including nested list indexes and object fields following an index. Preserve Variant reads for bracket JSONPath expressions and normalize dot accesses after subscripts to work around the current DataFusion planner limitation. Add unit and sqlness coverage for nested indexes, type conflicts, missing paths, flushes, and compacted SSTs. Signed-off-by: luofucong <luofc@foxmail.com> (cherry picked from commitd62a5a990a) (cherry picked from commit c11d8647feb30ecdd5ba1bc8ed4df78999801672) * refactor(json2): concretize JSON2 schemas at merge scan boundaries (#9016) * refactor(json2): concretize JSON2 schemas at merge scan boundaries Infer concrete JSON2 output types from remote plans and expose them on MergeScanLogicalPlan before physical planning. Recompute affected local schemas and remove the JSON2-specific rewrite from MergeScanExec. Add SQLness coverage for whole JSON2 columns in windows and joins. Signed-off-by: luofucong <luofc@foxmail.com> * fix ci Signed-off-by: luofucong <luofc@foxmail.com> --------- Signed-off-by: luofucong <luofc@foxmail.com> (cherry picked from commit84bd993131) (cherry picked from commit 51c98a7e31a929fc33ff5133714bb47108f7539b) * fix(json2): keep empty structs in remainder (#9027) Signed-off-by: luofucong <luofc@foxmail.com> (cherry picked from commitd67d3501a9) (cherry picked from commit b3f2c1cc5bf0cfddb7c0670ac8f3522eb67c35a0) * fix(mito2): remove stale scan test imports Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> (cherry picked from commit d63bbe6aef298f5b8e960bb02eed08743a6684ee) * test(query): expect bounded merge scan partitions Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> (cherry picked from commit 7bcef46b1c010d2e73ec49be7581c216e6b5b1b6) * style(mito2): remove trailing whitespace Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(sqlness): refresh bounded partition plans Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(sqlness): normalize CPU-dependent partition plans Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> --------- Signed-off-by: WenyXu <wenymedia@gmail.com> Signed-off-by: Lei, HUANG <ratuthomm@gmail.com> Signed-off-by: Dennis Zhuang <killme2008@gmail.com> Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> Signed-off-by: wy471x <wy471x@gmail.com> Signed-off-by: Ning Sun <sunning@greptime.com> Signed-off-by: shuiyisong <xixing.sys@gmail.com> Signed-off-by: luofucong <luofc@foxmail.com> Signed-off-by: fys <fengys1996@gmail.com> Co-authored-by: WenyXu <wenymedia@gmail.com> Co-authored-by: Lei, HUANG <ratuthomm@gmail.com> Co-authored-by: Yingwen <realevenyag@gmail.com> Co-authored-by: dennis zhuang <killme2008@gmail.com> Co-authored-by: Ning Sun <sunng@protonmail.com> Co-authored-by: wy471x <wy471x@gmail.com> Co-authored-by: Ning Sun <sunning@greptime.com> Co-authored-by: shuiyisong <xixing.sys@gmail.com> Co-authored-by: luofucong <luofc@foxmail.com> Co-authored-by: fys <fengys1996@gmail.com>
1614 lines
55 KiB
Rust
1614 lines
55 KiB
Rust
// Copyright 2023 Greptime Team
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use client::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, Database, OutputData};
|
|
use common_catalog::consts::DEFAULT_PRIVATE_SCHEMA_NAME;
|
|
use common_event_recorder::event_table::{
|
|
ACTOR_COLUMN, REGION_ID_COLUMN, REGION_MIGRATION_DST_NODE_ID_COLUMN,
|
|
REGION_MIGRATION_SRC_NODE_ID_COLUMN, REGION_MIGRATION_TRIGGER_REASON_COLUMN,
|
|
};
|
|
use common_event_recorder::{
|
|
DEFAULT_EVENTS_TABLE_NAME, DEFAULT_FLUSH_INTERVAL_SECONDS, EVENTS_TABLE_TIMESTAMP_COLUMN_NAME,
|
|
EVENTS_TABLE_TYPE_COLUMN_NAME, PersistentEventContext, TriggerReason,
|
|
};
|
|
use common_meta::distributed_time_constants::default_distributed_time_constants;
|
|
use common_meta::key::{RegionDistribution, RegionRoleSet, TableMetadataManagerRef};
|
|
use common_meta::peer::Peer;
|
|
use common_meta::rpc::store::BatchDeleteRequest;
|
|
use common_procedure::ProcedureContext;
|
|
use common_procedure::event::{
|
|
EVENTS_TABLE_PROCEDURE_ID_COLUMN_NAME, EVENTS_TABLE_PROCEDURE_STATE_COLUMN_NAME,
|
|
};
|
|
use common_query::Output;
|
|
use common_recordbatch::RecordBatches;
|
|
use common_telemetry::info;
|
|
use common_test_util::recordbatch::check_output_stream;
|
|
use common_test_util::temp_dir::create_temp_dir;
|
|
use common_wal::config::kafka::common::{KafkaConnectionConfig, KafkaTopicConfig};
|
|
use common_wal::config::kafka::{DatanodeKafkaConfig, MetasrvKafkaConfig};
|
|
use common_wal::config::{DatanodeWalConfig, MetasrvWalConfig};
|
|
use datatypes::arrow::array::AsArray;
|
|
use datatypes::arrow::datatypes::UInt64Type;
|
|
use frontend::instance::Instance;
|
|
use futures::future::BoxFuture;
|
|
use meta_srv::error;
|
|
use meta_srv::error::Result as MetaResult;
|
|
use meta_srv::event::region_migration::REGION_MIGRATION_EVENT_TYPE;
|
|
use meta_srv::key::DatanodeLeaseKey;
|
|
use meta_srv::metasrv::SelectorContext;
|
|
use meta_srv::procedure::region_migration::{
|
|
RegionMigrationProcedureTask, RegionMigrationTriggerReason,
|
|
};
|
|
use meta_srv::selector::{Selector, SelectorOptions};
|
|
use sea_query::{Alias, Expr, Iden, Order, PostgresQueryBuilder, Query};
|
|
use servers::error::Result as ServerResult;
|
|
use servers::query_handler::sql::SqlQueryHandler;
|
|
use session::context::{QueryContext, QueryContextRef};
|
|
use store_api::storage::RegionId;
|
|
use table::metadata::TableId;
|
|
use tests_integration::cluster::{GreptimeDbCluster, GreptimeDbClusterBuilder};
|
|
use tests_integration::test_util::{
|
|
PEER_PLACEHOLDER_ADDR, StorageType, get_test_store_config, setup_authenticated_grpc_database,
|
|
};
|
|
use uuid::Uuid;
|
|
|
|
const TEST_TABLE_NAME: &str = "migration_target";
|
|
const PROCEDURE_ACTOR: &str = "procedure_actor";
|
|
const PROCEDURE_ACTOR_PASSWORD: &str = "procedure_actor_pwd";
|
|
|
|
#[macro_export]
|
|
macro_rules! region_migration_test {
|
|
($service:ident, $($(#[$meta:meta])* $test:ident),*,) => {
|
|
paste::item! {
|
|
mod [<integration_region_migration_ $service:lower _test>] {
|
|
$(
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
$(
|
|
#[$meta]
|
|
)*
|
|
async fn [< $test >]() {
|
|
let store_type = tests_integration::test_util::StorageType::$service;
|
|
if store_type.test_on() {
|
|
common_telemetry::init_default_ut_logging();
|
|
common_wal::maybe_skip_kafka_integration_test!();
|
|
let endpoints = common_wal::test_util::get_kafka_endpoints();
|
|
$crate::region_migration::$test(store_type, endpoints).await
|
|
}
|
|
|
|
}
|
|
)*
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! region_migration_tests {
|
|
($($service:ident),*) => {
|
|
$(
|
|
region_migration_test!(
|
|
$service,
|
|
|
|
test_region_migration,
|
|
test_region_migration_by_sql,
|
|
test_region_migration_with_offline_source_by_sql,
|
|
test_region_migration_multiple_regions,
|
|
test_region_migration_all_regions,
|
|
test_region_migration_incorrect_from_peer,
|
|
test_region_migration_incorrect_region_id,
|
|
test_metric_table_region_migration_by_sql,
|
|
);
|
|
)*
|
|
};
|
|
}
|
|
|
|
/// A naive region migration test.
|
|
pub async fn test_region_migration(store_type: StorageType, endpoints: Vec<String>) {
|
|
let cluster_name = "test_region_migration";
|
|
let peer_factory = |id| Peer {
|
|
id,
|
|
addr: PEER_PLACEHOLDER_ADDR.to_string(),
|
|
};
|
|
|
|
// Prepares test cluster.
|
|
let (store_config, _guard) = get_test_store_config(&store_type);
|
|
let home_dir = create_temp_dir("test_migration_data_home");
|
|
let datanodes = 5u64;
|
|
let builder = GreptimeDbClusterBuilder::new(cluster_name).await;
|
|
let const_selector = Arc::new(ConstNodeSelector::new(vec![
|
|
peer_factory(1),
|
|
peer_factory(2),
|
|
peer_factory(3),
|
|
]));
|
|
let cluster = builder
|
|
.with_datanodes(datanodes as u32)
|
|
.with_store_config(store_config)
|
|
.with_datanode_wal_config(DatanodeWalConfig::Kafka(DatanodeKafkaConfig {
|
|
connection: KafkaConnectionConfig {
|
|
broker_endpoints: endpoints.clone(),
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
}))
|
|
.with_metasrv_wal_config(MetasrvWalConfig::Kafka(MetasrvKafkaConfig {
|
|
connection: KafkaConnectionConfig {
|
|
broker_endpoints: endpoints,
|
|
..Default::default()
|
|
},
|
|
kafka_topic: KafkaTopicConfig {
|
|
num_topics: 3,
|
|
topic_name_prefix: Uuid::new_v4().to_string(),
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
}))
|
|
.with_shared_home_dir(Arc::new(home_dir))
|
|
.with_meta_selector(const_selector.clone())
|
|
.build(true)
|
|
.await;
|
|
let mut logical_timer = 1685508715000;
|
|
let table_metadata_manager = cluster.metasrv.table_metadata_manager().clone();
|
|
|
|
// Prepares test table.
|
|
let table_id = prepare_testing_table(&cluster).await;
|
|
|
|
// Inserts data
|
|
let results = insert_values(cluster.fe_instance(), logical_timer).await;
|
|
logical_timer += 1000;
|
|
for result in results {
|
|
assert!(matches!(result.unwrap().data, OutputData::AffectedRows(1)));
|
|
}
|
|
|
|
// The region distribution
|
|
let mut distribution = find_region_distribution(&table_metadata_manager, table_id).await;
|
|
|
|
// Selecting target of region migration.
|
|
let region_migration_manager = cluster.metasrv.region_migration_manager();
|
|
let (from_peer_id, from_regions) = distribution.pop_first().unwrap();
|
|
info!(
|
|
"Selecting from peer: {from_peer_id}, and regions: {:?}",
|
|
from_regions
|
|
);
|
|
let (to_peer_id, mut to_regions) = distribution.pop_first().unwrap();
|
|
info!(
|
|
"Selecting to peer: {to_peer_id}, and regions: {:?}",
|
|
to_regions
|
|
);
|
|
|
|
let region_id = RegionId::new(table_id, from_regions.leader_regions[0]);
|
|
// Trigger region migration.
|
|
let procedure = region_migration_manager
|
|
.submit_procedure(
|
|
ProcedureContext::from_event_context(PersistentEventContext::new(
|
|
TriggerReason::Manual,
|
|
)),
|
|
RegionMigrationProcedureTask::new(
|
|
region_id,
|
|
peer_factory(from_peer_id),
|
|
peer_factory(to_peer_id),
|
|
Duration::from_millis(1000),
|
|
RegionMigrationTriggerReason::Manual,
|
|
),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
info!("Started region procedure: {}!", procedure.unwrap());
|
|
|
|
// Prepares expected region distribution.
|
|
to_regions
|
|
.leader_regions
|
|
.extend(from_regions.leader_regions);
|
|
to_regions
|
|
.follower_regions
|
|
.extend(from_regions.follower_regions);
|
|
// Keeps asc order.
|
|
to_regions.sort();
|
|
distribution.insert(to_peer_id, to_regions);
|
|
|
|
// Waits condition
|
|
wait_condition(
|
|
Duration::from_secs(10),
|
|
Box::pin(async move {
|
|
loop {
|
|
let region_migration =
|
|
find_region_distribution(&table_metadata_manager, table_id).await;
|
|
if region_migration == distribution {
|
|
info!("Found new distribution: {region_migration:?}");
|
|
break;
|
|
} else {
|
|
info!("Found the unexpected distribution: {region_migration:?}, expected: {distribution:?}");
|
|
tokio::time::sleep(Duration::from_millis(200)).await;
|
|
}
|
|
}
|
|
}),
|
|
)
|
|
.await;
|
|
|
|
// Inserts more table.
|
|
let results = insert_values(cluster.fe_instance(), logical_timer).await;
|
|
for result in results {
|
|
assert!(matches!(result.unwrap().data, OutputData::AffectedRows(1)));
|
|
}
|
|
|
|
// Asserts the writes.
|
|
assert_values(cluster.fe_instance()).await;
|
|
|
|
// Triggers again.
|
|
let err = region_migration_manager
|
|
.submit_procedure(
|
|
ProcedureContext::from_event_context(PersistentEventContext::new(
|
|
TriggerReason::Manual,
|
|
)),
|
|
RegionMigrationProcedureTask::new(
|
|
region_id,
|
|
peer_factory(from_peer_id),
|
|
peer_factory(to_peer_id),
|
|
Duration::from_millis(1000),
|
|
RegionMigrationTriggerReason::Manual,
|
|
),
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
assert!(matches!(err, error::Error::RegionMigrated { .. }));
|
|
|
|
check_region_migration_events_system_table(
|
|
cluster.fe_instance(),
|
|
&procedure.unwrap().to_string(),
|
|
region_id.as_u64(),
|
|
from_peer_id,
|
|
to_peer_id,
|
|
None,
|
|
)
|
|
.await;
|
|
}
|
|
|
|
/// A naive metric table region migration test by SQL function
|
|
pub async fn test_metric_table_region_migration_by_sql(
|
|
store_type: StorageType,
|
|
endpoints: Vec<String>,
|
|
) {
|
|
let cluster_name = "test_region_migration";
|
|
let peer_factory = |id| Peer {
|
|
id,
|
|
addr: PEER_PLACEHOLDER_ADDR.to_string(),
|
|
};
|
|
|
|
// Prepares test cluster.
|
|
let (store_config, _guard) = get_test_store_config(&store_type);
|
|
let home_dir = create_temp_dir("test_migration_data_home");
|
|
let datanodes = 5u64;
|
|
let builder = GreptimeDbClusterBuilder::new(cluster_name).await;
|
|
let const_selector = Arc::new(ConstNodeSelector::new(vec![
|
|
peer_factory(1),
|
|
peer_factory(2),
|
|
peer_factory(3),
|
|
]));
|
|
let cluster = builder
|
|
.with_datanodes(datanodes as u32)
|
|
.with_store_config(store_config)
|
|
.with_datanode_wal_config(DatanodeWalConfig::Kafka(DatanodeKafkaConfig {
|
|
connection: KafkaConnectionConfig {
|
|
broker_endpoints: endpoints.clone(),
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
}))
|
|
.with_metasrv_wal_config(MetasrvWalConfig::Kafka(MetasrvKafkaConfig {
|
|
connection: KafkaConnectionConfig {
|
|
broker_endpoints: endpoints,
|
|
..Default::default()
|
|
},
|
|
kafka_topic: KafkaTopicConfig {
|
|
num_topics: 3,
|
|
topic_name_prefix: Uuid::new_v4().to_string(),
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
}))
|
|
.with_shared_home_dir(Arc::new(home_dir))
|
|
.with_meta_selector(const_selector.clone())
|
|
.build(true)
|
|
.await;
|
|
// Prepares test metric tables.
|
|
let table_id = prepare_testing_metric_table(&cluster).await;
|
|
let query_ctx = QueryContext::arc();
|
|
|
|
// Inserts values
|
|
run_sql(
|
|
cluster.fe_instance(),
|
|
r#"INSERT INTO t1 VALUES ('host1',0, 0), ('host2', 1, 1);"#,
|
|
query_ctx.clone(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
run_sql(
|
|
cluster.fe_instance(),
|
|
r#"INSERT INTO t2 VALUES ('job1', 0, 0), ('job2', 1, 1);"#,
|
|
query_ctx.clone(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// The region distribution
|
|
let mut distribution = find_region_distribution_by_sql(&cluster, "phy").await;
|
|
// Selecting target of region migration.
|
|
let (from_peer_id, from_regions) = distribution.pop_first().unwrap();
|
|
info!(
|
|
"Selecting from peer: {from_peer_id}, and regions: {:?}",
|
|
from_regions.leader_regions[0]
|
|
);
|
|
let to_peer_id = (from_peer_id + 1) % 3;
|
|
let region_id = RegionId::new(table_id, from_regions.leader_regions[0]);
|
|
// Trigger region migration.
|
|
let procedure_id =
|
|
trigger_migration_by_sql(&cluster, region_id.as_u64(), from_peer_id, to_peer_id).await;
|
|
|
|
info!("Started region procedure: {}!", procedure_id);
|
|
|
|
// Waits condition by checking procedure state
|
|
let frontend = cluster.fe_instance().clone();
|
|
let procedure_id_for_closure = procedure_id.clone();
|
|
wait_condition(
|
|
Duration::from_secs(10),
|
|
Box::pin(async move {
|
|
loop {
|
|
let state = query_procedure_by_sql(&frontend, &procedure_id_for_closure).await;
|
|
if state == "{\"status\":\"Done\"}" {
|
|
info!("Migration done: {state}");
|
|
break;
|
|
} else {
|
|
info!("Migration not finished: {state}");
|
|
tokio::time::sleep(Duration::from_millis(200)).await;
|
|
}
|
|
}
|
|
}),
|
|
)
|
|
.await;
|
|
|
|
let result = cluster
|
|
.frontend
|
|
.instance
|
|
.do_query("select * from t1 order by host desc", query_ctx.clone())
|
|
.await
|
|
.remove(0);
|
|
|
|
let expected = "\
|
|
+-------+-------------------------+-----+
|
|
| host | ts | val |
|
|
+-------+-------------------------+-----+
|
|
| host2 | 1970-01-01T00:00:00.001 | 1.0 |
|
|
| host1 | 1970-01-01T00:00:00 | 0.0 |
|
|
+-------+-------------------------+-----+";
|
|
check_output_stream(result.unwrap().data, expected).await;
|
|
|
|
let result = cluster
|
|
.frontend
|
|
.instance
|
|
.do_query("select * from t2 order by job desc", query_ctx)
|
|
.await
|
|
.remove(0);
|
|
|
|
let expected = "\
|
|
+------+-------------------------+-----+
|
|
| job | ts | val |
|
|
+------+-------------------------+-----+
|
|
| job2 | 1970-01-01T00:00:00.001 | 1.0 |
|
|
| job1 | 1970-01-01T00:00:00 | 0.0 |
|
|
+------+-------------------------+-----+";
|
|
check_output_stream(result.unwrap().data, expected).await;
|
|
|
|
check_region_migration_events_system_table(
|
|
cluster.fe_instance(),
|
|
&procedure_id,
|
|
region_id.as_u64(),
|
|
from_peer_id,
|
|
to_peer_id,
|
|
Some("greptime"),
|
|
)
|
|
.await;
|
|
}
|
|
|
|
/// A naive region migration test by SQL function.
|
|
pub async fn test_region_migration_by_sql(store_type: StorageType, endpoints: Vec<String>) {
|
|
test_region_migration_by_sql_inner(store_type, endpoints, false).await;
|
|
}
|
|
|
|
/// A region migration test by SQL function with an offline source datanode.
|
|
pub async fn test_region_migration_with_offline_source_by_sql(
|
|
store_type: StorageType,
|
|
endpoints: Vec<String>,
|
|
) {
|
|
test_region_migration_by_sql_inner(store_type, endpoints, true).await;
|
|
}
|
|
|
|
async fn test_region_migration_by_sql_inner(
|
|
store_type: StorageType,
|
|
endpoints: Vec<String>,
|
|
simulate_offline_source: bool,
|
|
) {
|
|
let cluster_name = "test_region_migration";
|
|
let peer_factory = |id| Peer {
|
|
id,
|
|
addr: PEER_PLACEHOLDER_ADDR.to_string(),
|
|
};
|
|
|
|
// Prepares test cluster.
|
|
let (store_config, _guard) = get_test_store_config(&store_type);
|
|
let home_dir = create_temp_dir("test_migration_data_home");
|
|
let datanodes = 5u64;
|
|
let builder = GreptimeDbClusterBuilder::new(cluster_name).await;
|
|
let const_selector = Arc::new(ConstNodeSelector::new(vec![
|
|
peer_factory(1),
|
|
peer_factory(2),
|
|
peer_factory(3),
|
|
]));
|
|
let mut cluster = builder
|
|
.with_datanodes(datanodes as u32)
|
|
.with_store_config(store_config)
|
|
.with_datanode_wal_config(DatanodeWalConfig::Kafka(DatanodeKafkaConfig {
|
|
connection: KafkaConnectionConfig {
|
|
broker_endpoints: endpoints.clone(),
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
}))
|
|
.with_metasrv_wal_config(MetasrvWalConfig::Kafka(MetasrvKafkaConfig {
|
|
connection: KafkaConnectionConfig {
|
|
broker_endpoints: endpoints,
|
|
..Default::default()
|
|
},
|
|
kafka_topic: KafkaTopicConfig {
|
|
num_topics: 3,
|
|
topic_name_prefix: Uuid::new_v4().to_string(),
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
}))
|
|
.with_shared_home_dir(Arc::new(home_dir))
|
|
.with_meta_selector(const_selector.clone())
|
|
.build(true)
|
|
.await;
|
|
let table_metadata_manager = cluster.metasrv.table_metadata_manager().clone();
|
|
let (actor_db, _actor_grpc_server) = setup_authenticated_grpc_database(
|
|
cluster.fe_instance().clone(),
|
|
PROCEDURE_ACTOR,
|
|
PROCEDURE_ACTOR_PASSWORD,
|
|
)
|
|
.await;
|
|
let mut logical_timer = 1685508715000;
|
|
|
|
// Prepares test table.
|
|
let table_id = prepare_testing_table(&cluster).await;
|
|
|
|
// Inserts data
|
|
let results = insert_values(cluster.fe_instance(), logical_timer).await;
|
|
logical_timer += 1000;
|
|
for result in results {
|
|
assert!(matches!(result.unwrap().data, OutputData::AffectedRows(1)));
|
|
}
|
|
|
|
// The region distribution
|
|
let mut distribution = find_region_distribution_by_sql(&cluster, TEST_TABLE_NAME).await;
|
|
|
|
let old_distribution = distribution.clone();
|
|
|
|
// Selecting target of region migration.
|
|
let region_migration_manager = cluster.metasrv.region_migration_manager().clone();
|
|
let (from_peer_id, from_regions) = distribution.pop_first().unwrap();
|
|
info!(
|
|
"Selecting from peer: {from_peer_id}, and regions: {:?}",
|
|
from_regions
|
|
);
|
|
let (to_peer_id, to_regions) = distribution.pop_first().unwrap();
|
|
info!(
|
|
"Selecting to peer: {to_peer_id}, and regions: {:?}",
|
|
to_regions
|
|
);
|
|
|
|
let region_id = RegionId::new(table_id, from_regions.leader_regions[0]);
|
|
// Trigger region migration.
|
|
let procedure_id =
|
|
trigger_migration_by_grpc(&actor_db, region_id.as_u64(), from_peer_id, to_peer_id).await;
|
|
|
|
info!("Started region procedure: {}!", procedure_id);
|
|
|
|
// Waits condition by checking procedure state
|
|
let frontend = cluster.fe_instance().clone();
|
|
let procedure_id_for_closure = procedure_id.clone();
|
|
wait_condition(
|
|
Duration::from_secs(10),
|
|
Box::pin(async move {
|
|
loop {
|
|
let state = query_procedure_by_sql(&frontend, &procedure_id_for_closure).await;
|
|
if state == "{\"status\":\"Done\"}" {
|
|
info!("Migration done: {state}");
|
|
break;
|
|
} else {
|
|
info!("Migration not finished: {state}");
|
|
tokio::time::sleep(Duration::from_millis(200)).await;
|
|
}
|
|
}
|
|
}),
|
|
)
|
|
.await;
|
|
|
|
check_region_migration_events_system_table(
|
|
cluster.fe_instance(),
|
|
&procedure_id,
|
|
region_id.as_u64(),
|
|
from_peer_id,
|
|
to_peer_id,
|
|
Some(PROCEDURE_ACTOR),
|
|
)
|
|
.await;
|
|
|
|
// Inserts more table.
|
|
let results = insert_values(cluster.fe_instance(), logical_timer).await;
|
|
for result in results {
|
|
assert!(matches!(result.unwrap().data, OutputData::AffectedRows(1)));
|
|
}
|
|
|
|
// Asserts the writes.
|
|
assert_values(cluster.fe_instance()).await;
|
|
|
|
if simulate_offline_source {
|
|
let mut expected_distribution =
|
|
find_region_distribution(&table_metadata_manager, table_id).await;
|
|
let (offline_from_peer_id, offline_region_number) = expected_distribution
|
|
.iter()
|
|
.find(|(peer_id, regions)| {
|
|
**peer_id != from_peer_id
|
|
&& **peer_id != to_peer_id
|
|
&& !regions.leader_regions.is_empty()
|
|
})
|
|
.map(|(peer_id, regions)| (*peer_id, regions.leader_regions[0]))
|
|
.unwrap();
|
|
let offline_region_id = RegionId::new(table_id, offline_region_number);
|
|
|
|
// Simulates scale-in: the source datanode stops and its lease disappears.
|
|
cluster
|
|
.datanode_instances
|
|
.get_mut(&offline_from_peer_id)
|
|
.unwrap()
|
|
.shutdown()
|
|
.await
|
|
.unwrap();
|
|
let source_lease_key: Vec<u8> = DatanodeLeaseKey {
|
|
node_id: offline_from_peer_id,
|
|
}
|
|
.try_into()
|
|
.unwrap();
|
|
cluster
|
|
.metasrv
|
|
.in_memory()
|
|
.batch_delete(BatchDeleteRequest {
|
|
keys: vec![source_lease_key],
|
|
prev_kv: false,
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
let offline_procedure_id = trigger_migration_by_sql(
|
|
&cluster,
|
|
offline_region_id.as_u64(),
|
|
offline_from_peer_id,
|
|
from_peer_id,
|
|
)
|
|
.await;
|
|
let frontend = cluster.fe_instance().clone();
|
|
let procedure_id_for_closure = offline_procedure_id.clone();
|
|
wait_condition(
|
|
default_distributed_time_constants().region_lease + Duration::from_secs(10),
|
|
Box::pin(async move {
|
|
loop {
|
|
let state = query_procedure_by_sql(&frontend, &procedure_id_for_closure).await;
|
|
if state == "{\"status\":\"Done\"}" {
|
|
info!("Offline-source migration done: {state}");
|
|
break;
|
|
}
|
|
info!("Offline-source migration not finished: {state}");
|
|
tokio::time::sleep(Duration::from_millis(200)).await;
|
|
}
|
|
}),
|
|
)
|
|
.await;
|
|
|
|
check_region_migration_events_system_table(
|
|
cluster.fe_instance(),
|
|
&offline_procedure_id,
|
|
offline_region_id.as_u64(),
|
|
offline_from_peer_id,
|
|
from_peer_id,
|
|
Some("greptime"),
|
|
)
|
|
.await;
|
|
|
|
let remove_offline_source = {
|
|
let source_regions = expected_distribution
|
|
.get_mut(&offline_from_peer_id)
|
|
.unwrap();
|
|
source_regions
|
|
.leader_regions
|
|
.retain(|region_number| *region_number != offline_region_number);
|
|
source_regions.leader_regions.is_empty() && source_regions.follower_regions.is_empty()
|
|
};
|
|
if remove_offline_source {
|
|
expected_distribution.remove(&offline_from_peer_id);
|
|
}
|
|
let target_regions = expected_distribution.entry(from_peer_id).or_default();
|
|
target_regions.add_leader_region(offline_region_number);
|
|
target_regions.sort();
|
|
|
|
let table_metadata_manager = table_metadata_manager.clone();
|
|
wait_condition(
|
|
Duration::from_secs(10),
|
|
Box::pin(async move {
|
|
loop {
|
|
let distribution =
|
|
find_region_distribution(&table_metadata_manager, table_id).await;
|
|
if distribution == expected_distribution {
|
|
break;
|
|
}
|
|
info!("Offline-source migration has unexpected distribution: {distribution:?}");
|
|
tokio::time::sleep(Duration::from_millis(200)).await;
|
|
}
|
|
}),
|
|
)
|
|
.await;
|
|
|
|
assert_values(cluster.fe_instance()).await;
|
|
}
|
|
|
|
// Triggers again.
|
|
let err = region_migration_manager
|
|
.submit_procedure(
|
|
ProcedureContext::from_event_context(PersistentEventContext::new(
|
|
TriggerReason::Manual,
|
|
)),
|
|
RegionMigrationProcedureTask::new(
|
|
region_id,
|
|
peer_factory(from_peer_id),
|
|
peer_factory(to_peer_id),
|
|
Duration::from_millis(1000),
|
|
RegionMigrationTriggerReason::Manual,
|
|
),
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
assert!(matches!(err, error::Error::RegionMigrated { .. }));
|
|
|
|
let new_distribution = find_region_distribution_by_sql(&cluster, TEST_TABLE_NAME).await;
|
|
|
|
assert_ne!(old_distribution, new_distribution);
|
|
}
|
|
|
|
/// A region migration test for a region server contains multiple regions of the table.
|
|
pub async fn test_region_migration_multiple_regions(
|
|
store_type: StorageType,
|
|
endpoints: Vec<String>,
|
|
) {
|
|
let cluster_name = "test_region_migration_multiple_regions";
|
|
let peer_factory = |id| Peer {
|
|
id,
|
|
addr: PEER_PLACEHOLDER_ADDR.to_string(),
|
|
};
|
|
|
|
// Prepares test cluster.
|
|
let (store_config, _guard) = get_test_store_config(&store_type);
|
|
let home_dir = create_temp_dir("test_region_migration_multiple_regions_data_home");
|
|
let datanodes = 5u64;
|
|
let builder = GreptimeDbClusterBuilder::new(cluster_name).await;
|
|
let const_selector = Arc::new(ConstNodeSelector::new(vec![
|
|
peer_factory(1),
|
|
peer_factory(2),
|
|
peer_factory(2),
|
|
]));
|
|
let cluster = builder
|
|
.with_datanodes(datanodes as u32)
|
|
.with_store_config(store_config)
|
|
.with_datanode_wal_config(DatanodeWalConfig::Kafka(DatanodeKafkaConfig {
|
|
connection: KafkaConnectionConfig {
|
|
broker_endpoints: endpoints.clone(),
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
}))
|
|
.with_metasrv_wal_config(MetasrvWalConfig::Kafka(MetasrvKafkaConfig {
|
|
connection: KafkaConnectionConfig {
|
|
broker_endpoints: endpoints,
|
|
..Default::default()
|
|
},
|
|
kafka_topic: KafkaTopicConfig {
|
|
num_topics: 3,
|
|
topic_name_prefix: Uuid::new_v4().to_string(),
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
}))
|
|
.with_shared_home_dir(Arc::new(home_dir))
|
|
.with_meta_selector(const_selector.clone())
|
|
.build(true)
|
|
.await;
|
|
let mut logical_timer = 1685508715000;
|
|
let table_metadata_manager = cluster.metasrv.table_metadata_manager().clone();
|
|
|
|
// Prepares test table.
|
|
let table_id = prepare_testing_table(&cluster).await;
|
|
|
|
// Inserts data
|
|
let results = insert_values(cluster.fe_instance(), logical_timer).await;
|
|
logical_timer += 1000;
|
|
for result in results {
|
|
assert!(matches!(result.unwrap().data, OutputData::AffectedRows(1)));
|
|
}
|
|
|
|
// The region distribution
|
|
let mut distribution = find_region_distribution(&table_metadata_manager, table_id).await;
|
|
assert_eq!(distribution.len(), 2);
|
|
|
|
// Selecting target of region migration.
|
|
let region_migration_manager = cluster.metasrv.region_migration_manager();
|
|
let (peer_1, peer_1_regions) = distribution.pop_first().unwrap();
|
|
let (peer_2, peer_2_regions) = distribution.pop_first().unwrap();
|
|
|
|
// Picks the peer only contains as from peer.
|
|
let ((from_peer_id, from_regions), (to_peer_id, mut to_regions)) =
|
|
if peer_1_regions.leader_regions.len() == 1 {
|
|
((peer_1, peer_1_regions), (peer_2, peer_2_regions))
|
|
} else {
|
|
((peer_2, peer_2_regions), (peer_1, peer_1_regions))
|
|
};
|
|
|
|
info!(
|
|
"Selecting from peer: {from_peer_id}, and regions: {:?}",
|
|
from_regions
|
|
);
|
|
info!(
|
|
"Selecting to peer: {to_peer_id}, and regions: {:?}",
|
|
to_regions
|
|
);
|
|
|
|
let region_id = RegionId::new(table_id, from_regions.leader_regions[0]);
|
|
// Trigger region migration.
|
|
let procedure = region_migration_manager
|
|
.submit_procedure(
|
|
ProcedureContext::from_event_context(PersistentEventContext::new(
|
|
TriggerReason::Manual,
|
|
)),
|
|
RegionMigrationProcedureTask::new(
|
|
region_id,
|
|
peer_factory(from_peer_id),
|
|
peer_factory(to_peer_id),
|
|
Duration::from_millis(1000),
|
|
RegionMigrationTriggerReason::Manual,
|
|
),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
info!("Started region procedure: {}!", procedure.unwrap());
|
|
|
|
// Prepares expected region distribution.
|
|
to_regions
|
|
.leader_regions
|
|
.extend(from_regions.leader_regions);
|
|
to_regions
|
|
.follower_regions
|
|
.extend(from_regions.follower_regions);
|
|
// Keeps asc order.
|
|
to_regions.sort();
|
|
distribution.insert(to_peer_id, to_regions);
|
|
|
|
// Waits condition
|
|
wait_condition(
|
|
Duration::from_secs(10),
|
|
Box::pin(async move {
|
|
loop {
|
|
let region_migration =
|
|
find_region_distribution(&table_metadata_manager, table_id).await;
|
|
if region_migration == distribution {
|
|
info!("Found new distribution: {region_migration:?}");
|
|
break;
|
|
} else {
|
|
info!("Found the unexpected distribution: {region_migration:?}, expected: {distribution:?}");
|
|
tokio::time::sleep(Duration::from_millis(200)).await;
|
|
}
|
|
}
|
|
}),
|
|
)
|
|
.await;
|
|
|
|
// Inserts more table.
|
|
let results = insert_values(cluster.fe_instance(), logical_timer).await;
|
|
for result in results {
|
|
assert!(matches!(result.unwrap().data, OutputData::AffectedRows(1)));
|
|
}
|
|
|
|
check_region_migration_events_system_table(
|
|
cluster.fe_instance(),
|
|
&procedure.unwrap().to_string(),
|
|
region_id.as_u64(),
|
|
from_peer_id,
|
|
to_peer_id,
|
|
None,
|
|
)
|
|
.await;
|
|
|
|
// Asserts the writes.
|
|
assert_values(cluster.fe_instance()).await;
|
|
|
|
// Triggers again.
|
|
let err = region_migration_manager
|
|
.submit_procedure(
|
|
ProcedureContext::from_event_context(PersistentEventContext::new(
|
|
TriggerReason::Manual,
|
|
)),
|
|
RegionMigrationProcedureTask::new(
|
|
region_id,
|
|
peer_factory(from_peer_id),
|
|
peer_factory(to_peer_id),
|
|
Duration::from_millis(1000),
|
|
RegionMigrationTriggerReason::Manual,
|
|
),
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
assert!(matches!(err, error::Error::RegionMigrated { .. }));
|
|
}
|
|
|
|
/// A region migration test for a region server contains all regions of the table.
|
|
pub async fn test_region_migration_all_regions(store_type: StorageType, endpoints: Vec<String>) {
|
|
let cluster_name = "test_region_migration_all_regions";
|
|
let peer_factory = |id| Peer {
|
|
id,
|
|
addr: PEER_PLACEHOLDER_ADDR.to_string(),
|
|
};
|
|
|
|
// Prepares test cluster.
|
|
let (store_config, _guard) = get_test_store_config(&store_type);
|
|
let home_dir = create_temp_dir("test_region_migration_all_regions_data_home");
|
|
let datanodes = 5u64;
|
|
let builder = GreptimeDbClusterBuilder::new(cluster_name).await;
|
|
let const_selector = Arc::new(ConstNodeSelector::new(vec![
|
|
peer_factory(2),
|
|
peer_factory(2),
|
|
peer_factory(2),
|
|
]));
|
|
let cluster = builder
|
|
.with_datanodes(datanodes as u32)
|
|
.with_store_config(store_config)
|
|
.with_datanode_wal_config(DatanodeWalConfig::Kafka(DatanodeKafkaConfig {
|
|
connection: KafkaConnectionConfig {
|
|
broker_endpoints: endpoints.clone(),
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
}))
|
|
.with_metasrv_wal_config(MetasrvWalConfig::Kafka(MetasrvKafkaConfig {
|
|
connection: KafkaConnectionConfig {
|
|
broker_endpoints: endpoints,
|
|
..Default::default()
|
|
},
|
|
kafka_topic: KafkaTopicConfig {
|
|
num_topics: 3,
|
|
topic_name_prefix: Uuid::new_v4().to_string(),
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
}))
|
|
.with_shared_home_dir(Arc::new(home_dir))
|
|
.with_meta_selector(const_selector.clone())
|
|
.build(true)
|
|
.await;
|
|
let mut logical_timer = 1685508715000;
|
|
let table_metadata_manager = cluster.metasrv.table_metadata_manager().clone();
|
|
|
|
// Prepares test table.
|
|
let table_id = prepare_testing_table(&cluster).await;
|
|
|
|
// Inserts data
|
|
let results = insert_values(cluster.fe_instance(), logical_timer).await;
|
|
logical_timer += 1000;
|
|
for result in results {
|
|
assert!(matches!(result.unwrap().data, OutputData::AffectedRows(1)));
|
|
}
|
|
|
|
// The region distribution
|
|
let mut distribution = find_region_distribution(&table_metadata_manager, table_id).await;
|
|
assert_eq!(distribution.len(), 1);
|
|
|
|
// Selecting target of region migration.
|
|
let region_migration_manager = cluster.metasrv.region_migration_manager();
|
|
let (from_peer_id, mut from_regions) = distribution.pop_first().unwrap();
|
|
let to_peer_id = 1;
|
|
let mut to_regions = RegionRoleSet::default();
|
|
info!(
|
|
"Selecting from peer: {from_peer_id}, and regions: {:?}",
|
|
from_regions
|
|
);
|
|
info!(
|
|
"Selecting to peer: {to_peer_id}, and regions: {:?}",
|
|
to_regions
|
|
);
|
|
|
|
let region_id = RegionId::new(table_id, from_regions.leader_regions[0]);
|
|
// Trigger region migration.
|
|
let procedure = region_migration_manager
|
|
.submit_procedure(
|
|
ProcedureContext::from_event_context(PersistentEventContext::new(
|
|
TriggerReason::Manual,
|
|
)),
|
|
RegionMigrationProcedureTask::new(
|
|
region_id,
|
|
peer_factory(from_peer_id),
|
|
peer_factory(to_peer_id),
|
|
Duration::from_millis(1000),
|
|
RegionMigrationTriggerReason::Manual,
|
|
),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
info!("Started region procedure: {}!", procedure.unwrap());
|
|
|
|
// Prepares expected region distribution.
|
|
to_regions
|
|
.leader_regions
|
|
.push(from_regions.leader_regions.remove(0));
|
|
// Keeps asc order.
|
|
to_regions.sort();
|
|
distribution.insert(to_peer_id, to_regions);
|
|
distribution.insert(from_peer_id, from_regions);
|
|
|
|
// Waits condition
|
|
wait_condition(
|
|
Duration::from_secs(10),
|
|
Box::pin(async move {
|
|
loop {
|
|
let region_migration =
|
|
find_region_distribution(&table_metadata_manager, table_id).await;
|
|
if region_migration == distribution {
|
|
info!("Found new distribution: {region_migration:?}");
|
|
break;
|
|
} else {
|
|
info!("Found the unexpected distribution: {region_migration:?}, expected: {distribution:?}");
|
|
tokio::time::sleep(Duration::from_millis(200)).await;
|
|
}
|
|
}
|
|
}),
|
|
)
|
|
.await;
|
|
|
|
check_region_migration_events_system_table(
|
|
cluster.fe_instance(),
|
|
&procedure.unwrap().to_string(),
|
|
region_id.as_u64(),
|
|
from_peer_id,
|
|
to_peer_id,
|
|
None,
|
|
)
|
|
.await;
|
|
|
|
// Inserts more table.
|
|
let results = insert_values(cluster.fe_instance(), logical_timer).await;
|
|
for result in results {
|
|
assert!(matches!(result.unwrap().data, OutputData::AffectedRows(1)));
|
|
}
|
|
|
|
// Asserts the writes.
|
|
assert_values(cluster.fe_instance()).await;
|
|
|
|
// Triggers again.
|
|
let err = region_migration_manager
|
|
.submit_procedure(
|
|
ProcedureContext::from_event_context(PersistentEventContext::new(
|
|
TriggerReason::Manual,
|
|
)),
|
|
RegionMigrationProcedureTask::new(
|
|
region_id,
|
|
peer_factory(from_peer_id),
|
|
peer_factory(to_peer_id),
|
|
Duration::from_millis(1000),
|
|
RegionMigrationTriggerReason::Manual,
|
|
),
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
assert!(matches!(err, error::Error::RegionMigrated { .. }));
|
|
}
|
|
|
|
pub async fn test_region_migration_incorrect_from_peer(
|
|
store_type: StorageType,
|
|
endpoints: Vec<String>,
|
|
) {
|
|
let cluster_name = "test_region_migration_incorrect_from_peer";
|
|
let peer_factory = |id| Peer {
|
|
id,
|
|
addr: PEER_PLACEHOLDER_ADDR.to_string(),
|
|
};
|
|
|
|
// Prepares test cluster.
|
|
let (store_config, _guard) = get_test_store_config(&store_type);
|
|
let home_dir = create_temp_dir("test_region_migration_incorrect_from_peer_data_home");
|
|
let datanodes = 5u64;
|
|
let builder = GreptimeDbClusterBuilder::new(cluster_name).await;
|
|
let const_selector = Arc::new(ConstNodeSelector::new(vec![
|
|
peer_factory(1),
|
|
peer_factory(2),
|
|
peer_factory(3),
|
|
]));
|
|
let cluster = builder
|
|
.with_datanodes(datanodes as u32)
|
|
.with_store_config(store_config)
|
|
.with_datanode_wal_config(DatanodeWalConfig::Kafka(DatanodeKafkaConfig {
|
|
connection: KafkaConnectionConfig {
|
|
broker_endpoints: endpoints.clone(),
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
}))
|
|
.with_metasrv_wal_config(MetasrvWalConfig::Kafka(MetasrvKafkaConfig {
|
|
connection: KafkaConnectionConfig {
|
|
broker_endpoints: endpoints,
|
|
..Default::default()
|
|
},
|
|
kafka_topic: KafkaTopicConfig {
|
|
num_topics: 3,
|
|
topic_name_prefix: Uuid::new_v4().to_string(),
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
}))
|
|
.with_shared_home_dir(Arc::new(home_dir))
|
|
.with_meta_selector(const_selector.clone())
|
|
.build(true)
|
|
.await;
|
|
let logical_timer = 1685508715000;
|
|
let table_metadata_manager = cluster.metasrv.table_metadata_manager().clone();
|
|
|
|
// Prepares test table.
|
|
let table_id = prepare_testing_table(&cluster).await;
|
|
|
|
// Inserts data
|
|
let results = insert_values(cluster.fe_instance(), logical_timer).await;
|
|
for result in results {
|
|
assert!(matches!(result.unwrap().data, OutputData::AffectedRows(1)));
|
|
}
|
|
|
|
// The region distribution
|
|
let distribution = find_region_distribution(&table_metadata_manager, table_id).await;
|
|
assert_eq!(distribution.len(), 3);
|
|
let region_migration_manager = cluster.metasrv.region_migration_manager();
|
|
|
|
let region_id = RegionId::new(table_id, 1);
|
|
|
|
// Trigger region migration.
|
|
let err = region_migration_manager
|
|
.submit_procedure(
|
|
ProcedureContext::from_event_context(PersistentEventContext::new(
|
|
TriggerReason::Manual,
|
|
)),
|
|
RegionMigrationProcedureTask::new(
|
|
region_id,
|
|
peer_factory(5),
|
|
peer_factory(1),
|
|
Duration::from_millis(1000),
|
|
RegionMigrationTriggerReason::Manual,
|
|
),
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
|
|
assert!(matches!(
|
|
err,
|
|
meta_srv::error::Error::LeaderPeerChanged { .. }
|
|
));
|
|
}
|
|
|
|
pub async fn test_region_migration_incorrect_region_id(
|
|
store_type: StorageType,
|
|
endpoints: Vec<String>,
|
|
) {
|
|
let cluster_name = "test_region_migration_incorrect_region_id";
|
|
let peer_factory = |id| Peer {
|
|
id,
|
|
addr: PEER_PLACEHOLDER_ADDR.to_string(),
|
|
};
|
|
|
|
// Prepares test cluster.
|
|
let (store_config, _guard) = get_test_store_config(&store_type);
|
|
let home_dir = create_temp_dir("test_region_migration_incorrect_region_id_data_home");
|
|
let datanodes = 5u64;
|
|
let builder = GreptimeDbClusterBuilder::new(cluster_name).await;
|
|
let const_selector = Arc::new(ConstNodeSelector::new(vec![
|
|
peer_factory(1),
|
|
peer_factory(2),
|
|
peer_factory(3),
|
|
]));
|
|
let cluster = builder
|
|
.with_datanodes(datanodes as u32)
|
|
.with_store_config(store_config)
|
|
.with_datanode_wal_config(DatanodeWalConfig::Kafka(DatanodeKafkaConfig {
|
|
connection: KafkaConnectionConfig {
|
|
broker_endpoints: endpoints.clone(),
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
}))
|
|
.with_metasrv_wal_config(MetasrvWalConfig::Kafka(MetasrvKafkaConfig {
|
|
connection: KafkaConnectionConfig {
|
|
broker_endpoints: endpoints,
|
|
..Default::default()
|
|
},
|
|
kafka_topic: KafkaTopicConfig {
|
|
num_topics: 3,
|
|
topic_name_prefix: Uuid::new_v4().to_string(),
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
}))
|
|
.with_shared_home_dir(Arc::new(home_dir))
|
|
.with_meta_selector(const_selector.clone())
|
|
.build(true)
|
|
.await;
|
|
let logical_timer = 1685508715000;
|
|
let table_metadata_manager = cluster.metasrv.table_metadata_manager().clone();
|
|
|
|
// Prepares test table.
|
|
let table_id = prepare_testing_table(&cluster).await;
|
|
|
|
// Inserts data
|
|
let results = insert_values(cluster.fe_instance(), logical_timer).await;
|
|
for result in results {
|
|
assert!(matches!(result.unwrap().data, OutputData::AffectedRows(1)));
|
|
}
|
|
|
|
// The region distribution
|
|
let distribution = find_region_distribution(&table_metadata_manager, table_id).await;
|
|
assert_eq!(distribution.len(), 3);
|
|
let region_migration_manager = cluster.metasrv.region_migration_manager();
|
|
|
|
let region_id = RegionId::new(table_id, 5);
|
|
|
|
// Trigger region migration.
|
|
let err = region_migration_manager
|
|
.submit_procedure(
|
|
ProcedureContext::from_event_context(PersistentEventContext::new(
|
|
TriggerReason::Manual,
|
|
)),
|
|
RegionMigrationProcedureTask::new(
|
|
region_id,
|
|
peer_factory(2),
|
|
peer_factory(1),
|
|
Duration::from_millis(1000),
|
|
RegionMigrationTriggerReason::Manual,
|
|
),
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
|
|
assert!(matches!(
|
|
err,
|
|
meta_srv::error::Error::RegionRouteNotFound { .. }
|
|
));
|
|
}
|
|
|
|
struct ConstNodeSelector {
|
|
peers: Vec<Peer>,
|
|
}
|
|
|
|
impl ConstNodeSelector {
|
|
fn new(peers: Vec<Peer>) -> Self {
|
|
Self { peers }
|
|
}
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl Selector for ConstNodeSelector {
|
|
type Context = SelectorContext;
|
|
type Output = Vec<Peer>;
|
|
|
|
async fn select(
|
|
&self,
|
|
_ctx: &Self::Context,
|
|
_opts: SelectorOptions,
|
|
) -> MetaResult<Self::Output> {
|
|
Ok(self.peers.clone())
|
|
}
|
|
}
|
|
|
|
async fn wait_condition(timeout: Duration, condition: BoxFuture<'static, ()>) {
|
|
tokio::time::timeout(timeout, condition).await.unwrap();
|
|
}
|
|
|
|
async fn assert_values(instance: &Arc<Instance>) {
|
|
let query_ctx = QueryContext::arc();
|
|
|
|
let result = instance
|
|
.do_query(
|
|
&format!("select * from {TEST_TABLE_NAME} order by i, ts"),
|
|
query_ctx,
|
|
)
|
|
.await
|
|
.remove(0);
|
|
|
|
let expected = "\
|
|
+----+---------------------+
|
|
| i | ts |
|
|
+----+---------------------+
|
|
| 5 | 2023-05-31T04:51:55 |
|
|
| 5 | 2023-05-31T04:51:56 |
|
|
| 15 | 2023-05-31T04:51:55 |
|
|
| 15 | 2023-05-31T04:51:56 |
|
|
| 55 | 2023-05-31T04:51:55 |
|
|
| 55 | 2023-05-31T04:51:56 |
|
|
+----+---------------------+";
|
|
check_output_stream(result.unwrap().data, expected).await;
|
|
}
|
|
|
|
async fn prepare_testing_metric_table(cluster: &GreptimeDbCluster) -> TableId {
|
|
let sql = r#"CREATE TABLE phy (ts timestamp time index, val double) engine=metric with ("physical_metric_table" = "");"#;
|
|
let mut result = cluster
|
|
.frontend
|
|
.instance
|
|
.do_query(sql, QueryContext::arc())
|
|
.await;
|
|
let output = result.remove(0).unwrap();
|
|
assert!(matches!(output.data, OutputData::AffectedRows(0)));
|
|
|
|
let sql = r#"CREATE TABLE t1 (ts timestamp time index, val double, host string primary key) engine = metric with ("on_physical_table" = "phy");"#;
|
|
let mut result = cluster
|
|
.frontend
|
|
.instance
|
|
.do_query(sql, QueryContext::arc())
|
|
.await;
|
|
let output = result.remove(0).unwrap();
|
|
assert!(matches!(output.data, OutputData::AffectedRows(0)));
|
|
|
|
let sql = r#"CREATE TABLE t2 (ts timestamp time index, job string primary key, val double) engine = metric with ("on_physical_table" = "phy");"#;
|
|
let mut result = cluster
|
|
.frontend
|
|
.instance
|
|
.do_query(sql, QueryContext::arc())
|
|
.await;
|
|
let output = result.remove(0).unwrap();
|
|
assert!(matches!(output.data, OutputData::AffectedRows(0)));
|
|
|
|
let table = cluster
|
|
.frontend
|
|
.instance
|
|
.catalog_manager()
|
|
.table(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, "phy", None)
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
table.table_info().table_id()
|
|
}
|
|
|
|
async fn prepare_testing_table(cluster: &GreptimeDbCluster) -> TableId {
|
|
let sql = format!(
|
|
r"
|
|
CREATE TABLE {TEST_TABLE_NAME} (
|
|
i INT PRIMARY KEY,
|
|
ts TIMESTAMP TIME INDEX,
|
|
) PARTITION ON COLUMNS (i) (
|
|
i <= 10,
|
|
i > 10 AND i <= 50,
|
|
i > 50
|
|
)"
|
|
);
|
|
let mut result = cluster
|
|
.frontend
|
|
.instance
|
|
.do_query(&sql, QueryContext::arc())
|
|
.await;
|
|
let output = result.remove(0).unwrap();
|
|
assert!(matches!(output.data, OutputData::AffectedRows(0)));
|
|
|
|
let table = cluster
|
|
.frontend
|
|
.instance
|
|
.catalog_manager()
|
|
.table(
|
|
DEFAULT_CATALOG_NAME,
|
|
DEFAULT_SCHEMA_NAME,
|
|
TEST_TABLE_NAME,
|
|
None,
|
|
)
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
table.table_info().table_id()
|
|
}
|
|
|
|
async fn find_region_distribution(
|
|
table_metadata_manager: &TableMetadataManagerRef,
|
|
table_id: TableId,
|
|
) -> RegionDistribution {
|
|
table_metadata_manager
|
|
.table_route_manager()
|
|
.get_region_distribution(table_id)
|
|
.await
|
|
.unwrap()
|
|
.unwrap()
|
|
}
|
|
|
|
/// Find region distribution by SQL query
|
|
async fn find_region_distribution_by_sql(
|
|
cluster: &GreptimeDbCluster,
|
|
table: &str,
|
|
) -> RegionDistribution {
|
|
let query_ctx = QueryContext::arc();
|
|
|
|
let OutputData::Stream(stream) = run_sql(
|
|
cluster.fe_instance(),
|
|
&format!(
|
|
r#"select b.peer_id as datanode_id,
|
|
a.greptime_partition_id as region_id
|
|
from information_schema.partitions a left join information_schema.region_peers b
|
|
on a.greptime_partition_id = b.region_id
|
|
where a.table_name='{table}' order by datanode_id asc"#
|
|
),
|
|
query_ctx.clone(),
|
|
)
|
|
.await
|
|
.unwrap()
|
|
.data
|
|
else {
|
|
unreachable!();
|
|
};
|
|
|
|
let recordbatches = RecordBatches::try_collect(stream).await.unwrap();
|
|
|
|
info!("SQL result:\n {}", recordbatches.pretty_print().unwrap());
|
|
|
|
let mut distribution = RegionDistribution::new();
|
|
|
|
for batch in recordbatches.take() {
|
|
let column = batch.column_by_name("datanode_id").unwrap();
|
|
let datanode_ids = column.as_primitive::<UInt64Type>();
|
|
let column = batch.column_by_name("region_id").unwrap();
|
|
let region_ids = column.as_primitive::<UInt64Type>();
|
|
|
|
for (datanode_id, region_id) in datanode_ids.iter().zip(region_ids.iter()) {
|
|
let (Some(datanode_id), Some(region_id)) = (datanode_id, region_id) else {
|
|
unreachable!();
|
|
};
|
|
|
|
let region_id = RegionId::from_u64(region_id);
|
|
distribution
|
|
.entry(datanode_id)
|
|
.or_default()
|
|
.add_leader_region(region_id.region_number());
|
|
}
|
|
}
|
|
|
|
distribution
|
|
}
|
|
|
|
/// Trigger the region migration by SQL, returns the procedure id if success.
|
|
async fn trigger_migration_by_sql(
|
|
cluster: &GreptimeDbCluster,
|
|
region_id: u64,
|
|
from_peer_id: u64,
|
|
to_peer_id: u64,
|
|
) -> String {
|
|
let OutputData::RecordBatches(recordbatches) = run_sql(
|
|
cluster.fe_instance(),
|
|
&format!("admin migrate_region({region_id}, {from_peer_id}, {to_peer_id})"),
|
|
QueryContext::arc(),
|
|
)
|
|
.await
|
|
.unwrap()
|
|
.data
|
|
else {
|
|
unreachable!();
|
|
};
|
|
|
|
info!("SQL result:\n {}", recordbatches.pretty_print().unwrap());
|
|
|
|
let record_batch = &recordbatches.take()[0];
|
|
let column = record_batch.column(0);
|
|
let column = column.as_string::<i32>();
|
|
column.value(0).to_string()
|
|
}
|
|
|
|
/// Triggers region migration through the authenticated public gRPC SQL endpoint.
|
|
async fn trigger_migration_by_grpc(
|
|
database: &Database,
|
|
region_id: u64,
|
|
from_peer_id: u64,
|
|
to_peer_id: u64,
|
|
) -> String {
|
|
let output = database
|
|
.sql(format!(
|
|
"admin migrate_region({region_id}, {from_peer_id}, {to_peer_id})"
|
|
))
|
|
.await
|
|
.unwrap();
|
|
let recordbatches = match output.data {
|
|
OutputData::RecordBatches(recordbatches) => recordbatches,
|
|
OutputData::Stream(stream) => RecordBatches::try_collect(stream).await.unwrap(),
|
|
OutputData::AffectedRows(_) => unreachable!(),
|
|
};
|
|
|
|
let record_batch = &recordbatches.take()[0];
|
|
record_batch
|
|
.column(0)
|
|
.as_string::<i32>()
|
|
.value(0)
|
|
.to_string()
|
|
}
|
|
|
|
/// Query procedure state by SQL.
|
|
async fn query_procedure_by_sql(instance: &Arc<Instance>, pid: &str) -> String {
|
|
let OutputData::RecordBatches(recordbatches) = run_sql(
|
|
instance,
|
|
&format!("admin procedure_state('{pid}')"),
|
|
QueryContext::arc(),
|
|
)
|
|
.await
|
|
.unwrap()
|
|
.data
|
|
else {
|
|
unreachable!();
|
|
};
|
|
|
|
info!("SQL result:\n {}", recordbatches.pretty_print().unwrap());
|
|
|
|
let record_batch = &recordbatches.take()[0];
|
|
let column = record_batch.column(0);
|
|
let column = column.as_string::<i32>();
|
|
column.value(0).to_string()
|
|
}
|
|
|
|
async fn insert_values(instance: &Arc<Instance>, ts: u64) -> Vec<ServerResult<Output>> {
|
|
let query_ctx = QueryContext::arc();
|
|
|
|
let mut results = Vec::new();
|
|
for range in [5, 15, 55] {
|
|
let result = run_sql(
|
|
instance,
|
|
&format!("INSERT INTO {TEST_TABLE_NAME} VALUES ({},{})", range, ts),
|
|
query_ctx.clone(),
|
|
)
|
|
.await;
|
|
results.push(result);
|
|
}
|
|
|
|
results
|
|
}
|
|
|
|
async fn run_sql(
|
|
instance: &Arc<Instance>,
|
|
sql: &str,
|
|
query_ctx: QueryContextRef,
|
|
) -> ServerResult<Output> {
|
|
info!("Run SQL: {sql}");
|
|
instance.do_query(sql, query_ctx).await.remove(0)
|
|
}
|
|
|
|
enum RegionMigrationEvents {
|
|
Actor,
|
|
ProcedureId,
|
|
Timestamp,
|
|
ProcedureState,
|
|
Schema,
|
|
Table,
|
|
EventType,
|
|
RegionMigrationTriggerReason,
|
|
RegionId,
|
|
SrcNodeId,
|
|
DstNodeId,
|
|
}
|
|
|
|
impl Iden for RegionMigrationEvents {
|
|
fn unquoted(&self, s: &mut dyn std::fmt::Write) {
|
|
write!(
|
|
s,
|
|
"{}",
|
|
match self {
|
|
Self::Actor => ACTOR_COLUMN.name(),
|
|
Self::ProcedureId => EVENTS_TABLE_PROCEDURE_ID_COLUMN_NAME,
|
|
Self::Timestamp => EVENTS_TABLE_TIMESTAMP_COLUMN_NAME,
|
|
Self::ProcedureState => EVENTS_TABLE_PROCEDURE_STATE_COLUMN_NAME,
|
|
Self::Schema => DEFAULT_PRIVATE_SCHEMA_NAME,
|
|
Self::Table => DEFAULT_EVENTS_TABLE_NAME,
|
|
Self::EventType => EVENTS_TABLE_TYPE_COLUMN_NAME,
|
|
Self::RegionMigrationTriggerReason => REGION_MIGRATION_TRIGGER_REASON_COLUMN.name(),
|
|
Self::RegionId => REGION_ID_COLUMN.name(),
|
|
Self::SrcNodeId => REGION_MIGRATION_SRC_NODE_ID_COLUMN.name(),
|
|
Self::DstNodeId => REGION_MIGRATION_DST_NODE_ID_COLUMN.name(),
|
|
}
|
|
)
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
async fn check_region_migration_events_system_table(
|
|
fe_instance: &Arc<Instance>,
|
|
procedure_id: &str,
|
|
region_id: u64,
|
|
from_peer_id: u64,
|
|
to_peer_id: u64,
|
|
actor: Option<&str>,
|
|
) {
|
|
// Sleep enough time to ensure the event is recorded.
|
|
tokio::time::sleep(DEFAULT_FLUSH_INTERVAL_SECONDS * 2).await;
|
|
|
|
// The query is equivalent to the following SQL:
|
|
// SELECT region_migration_trigger_reason, procedure_state,
|
|
// json_get_string(procedure_trigger, 'type') AS procedure_trigger
|
|
// FROM greptime_private.events WHERE
|
|
// type = 'region_migration' AND
|
|
// procedure_id = '${procedure_id}' AND
|
|
// table_id = ${table_id} AND
|
|
// region_id = ${region_id} AND
|
|
// region_migration_src_node_id = ${from_peer_id} AND
|
|
// region_migration_dst_node_id = ${to_peer_id}
|
|
// ORDER BY timestamp ASC
|
|
let mut query = Query::select();
|
|
query
|
|
.column(RegionMigrationEvents::RegionMigrationTriggerReason)
|
|
.column(RegionMigrationEvents::ProcedureState)
|
|
.expr_as(
|
|
Expr::cust("json_get_string(procedure_trigger, 'type')"),
|
|
Alias::new("procedure_trigger"),
|
|
)
|
|
.expr_as(
|
|
Expr::cust("json_to_string(event_context)"),
|
|
Alias::new("event_context"),
|
|
)
|
|
.from((RegionMigrationEvents::Schema, RegionMigrationEvents::Table))
|
|
.and_where(Expr::col(RegionMigrationEvents::EventType).eq(REGION_MIGRATION_EVENT_TYPE))
|
|
.and_where(Expr::col(RegionMigrationEvents::ProcedureId).eq(procedure_id))
|
|
.and_where(Expr::col(RegionMigrationEvents::RegionId).eq(region_id))
|
|
.and_where(Expr::col(RegionMigrationEvents::SrcNodeId).eq(from_peer_id))
|
|
.and_where(Expr::col(RegionMigrationEvents::DstNodeId).eq(to_peer_id));
|
|
if let Some(actor) = actor {
|
|
query.and_where(Expr::col(RegionMigrationEvents::Actor).eq(actor));
|
|
} else {
|
|
query.and_where(Expr::col(RegionMigrationEvents::Actor).is_null());
|
|
}
|
|
let query = query
|
|
.order_by(RegionMigrationEvents::Timestamp, Order::Asc)
|
|
.to_string(PostgresQueryBuilder);
|
|
|
|
let result = fe_instance
|
|
.do_query(&query, QueryContext::arc())
|
|
.await
|
|
.remove(0);
|
|
|
|
let expected = if actor == Some(PROCEDURE_ACTOR) {
|
|
"\
|
|
+---------------------------------+-----------------+-------------------+---------------------------------------+
|
|
| region_migration_trigger_reason | procedure_state | procedure_trigger | event_context |
|
|
+---------------------------------+-----------------+-------------------+---------------------------------------+
|
|
| Manual | Running | Submitted | {\"protocol\":\"grpc\",\"reason\":\"manual\"} |
|
|
| Manual | Done | Succeeded | |
|
|
+---------------------------------+-----------------+-------------------+---------------------------------------+"
|
|
} else {
|
|
"\
|
|
+---------------------------------+-----------------+-------------------+---------------------+
|
|
| region_migration_trigger_reason | procedure_state | procedure_trigger | event_context |
|
|
+---------------------------------+-----------------+-------------------+---------------------+
|
|
| Manual | Running | Submitted | {\"reason\":\"manual\"} |
|
|
| Manual | Done | Succeeded | |
|
|
+---------------------------------+-----------------+-------------------+---------------------+"
|
|
};
|
|
check_output_stream(result.unwrap().data, expected).await;
|
|
}
|