Files
greptimedb/tests-integration/tests/repartition_event.rs
dennis zhuang f3eb8e6c72 test: cut integration test time and make the storage matrix meaningful (#9308)
* test: cut integration test time and make the storage matrix meaningful

tests-integration is ~85% of workspace test CPU, and 81% of that is the
S3/S3WithCache variants of the HTTP and gRPC suites. Those suites do not
touch the object store: of the 70 matrix HTTP tests only one flushed and
read back an SST, so the matrix was paying real AWS round trips to
re-prove protocol parsing.

- Point the PR CI object-store matrix at the MinIO already started by
  tests-integration/fixtures. Three GT_S3_* consumers did not read
  GT_S3_ENDPOINT_URL and would have hit real AWS with MinIO credentials;
  they now do.
- Add a nightly Linux job against real AWS S3, and pass GT_S3_* into the
  release integration-test container. The release previously ran every
  remote-backend case as a skip and only exercised the file backend.
- Give each S3WithCache test its own read cache directory. They shared
  /tmp/greptimedb_cache, which the datanode wipes on startup, so a
  starting test deleted the read cache of a running one.
- Add flush -> read-back assertions to the tests whose columns have a
  non-trivial SST representation: JSON/JSON2 columns, native histograms,
  metric-engine logical tables, and tables carrying fulltext or skipping
  indexes whose puffin files only exist after a flush.
- Move eight tests that create no table out of the storage matrix.
- Make the event recorder flush interval a constructor parameter and
  shorten it in the event tests, which otherwise wait a 5s window per DDL
  they assert on. It is skipped by serde and never reaches config files.
- Drop duplicates: test_grpc_zstd_compression was a verbatim copy of
  test_grpc_message_size_ok and is now rewritten to assert the negotiated
  grpc-encoding; test_execute_copy_to_{s3,oss,gcs,azblob} were strict
  prefixes of their copy_from siblings; two standalone/distributed event
  test pairs shared one assertion body.
- Fix and un-ignore stddev_by_label. stddev_pop merges partial aggregates
  in a parallelism-dependent order, so its last digits are unstable; the
  test now compares values with a tolerance.
- Rebase the jaeger v1 fixture on the current instant. It carries
  ttl=7d with 2025 timestamps, so its rows were only readable as long as
  they stayed in the memtable.

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* test: address review — wire nightly real-S3 job into check-status, keep the short event interval

The nightly `check-status` job did not depend on the new real-S3 job, so a
failure there would not have reached the status or Slack notification.

In database_ddl_event the short interval was set by a first
`with_event_recorder_options` call and then overwritten by the pre-existing
one, which carries `..Default::default()`. Merged into a single call.

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

---------

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
2026-09-23 01:52:52 +00:00

296 lines
14 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::Database;
use common_test_util::temp_dir::create_temp_dir;
use meta_srv::gc::GcSchedulerOptions;
use mito2::gc::GcConfig;
use tests_integration::cluster::GreptimeDbClusterBuilder;
use tests_integration::test_util::{
StorageType, get_test_store_config, setup_authenticated_grpc_database,
test_event_recorder_options,
};
use crate::event_recorder_test_util::{
assert_eventually_eq, assert_procedure_actor, find_eventually_string,
};
const TABLE_NAME: &str = "repartition_events";
const PROCEDURE_ACTOR: &str = "procedure_actor";
const PROCEDURE_ACTOR_PASSWORD: &str = "procedure_actor_pwd";
#[tokio::test(flavor = "multi_thread")]
async fn test_repartition_event() {
let store_type = StorageType::File;
if !store_type.test_on() {
return;
}
common_telemetry::init_default_ut_logging();
let (store_config, _guard) = get_test_store_config(&store_type);
let home_dir = create_temp_dir("test_repartition_event_data_home");
let cluster = GreptimeDbClusterBuilder::new("test_repartition_event")
.await
.with_event_recorder_options(test_event_recorder_options())
.with_datanodes(1)
.with_store_config(store_config)
.with_shared_home_dir(Arc::new(home_dir))
.with_metasrv_gc_config(GcSchedulerOptions {
enable: true,
gc_cooldown_period: Duration::from_nanos(1),
..Default::default()
})
.with_datanode_gc_config(GcConfig {
enable: true,
lingering_time: Some(Duration::ZERO),
unknown_file_lingering_time: Duration::ZERO,
..Default::default()
})
.build(true)
.await;
let instance = cluster.fe_instance();
let (database, _grpc_server) = setup_authenticated_grpc_database(
instance.clone(),
PROCEDURE_ACTOR,
PROCEDURE_ACTOR_PASSWORD,
)
.await;
execute_partition(&database).await;
let partition_procedure_id = assert_repartition_event(instance).await;
assert_repartition_group_event(instance, &partition_procedure_id).await;
execute_merge(&database).await;
let merge_procedure_id = find_submitted_procedure_id(
instance,
"repartition",
&format!(
"table_name = '{TABLE_NAME}' \
AND json_path_match(payload, '$.source_type == \"partitioned\"')"
),
)
.await;
assert_procedure_actor(instance, &merge_procedure_id, Some(PROCEDURE_ACTOR)).await;
assert_merge_repartition_group_event(instance, &merge_procedure_id).await;
}
async fn execute_partition(database: &Database) {
for sql in [
format!(
"CREATE TABLE {TABLE_NAME} (host INT, ts TIMESTAMP TIME INDEX, PRIMARY KEY(host)) ENGINE = mito"
),
format!(
"INSERT INTO {TABLE_NAME} VALUES (1, '2024-01-01 00:00:00'), (10, '2024-01-01 00:00:00')"
),
format!("ALTER TABLE {TABLE_NAME} PARTITION ON COLUMNS (host) (host < 10, host >= 10)"),
] {
database.sql(sql).await.unwrap();
}
}
async fn execute_merge(database: &Database) {
let sql = format!("ALTER TABLE {TABLE_NAME} MERGE PARTITION (host < 10, host >= 10)");
database.sql(sql).await.unwrap();
}
async fn assert_repartition_event(instance: &Arc<frontend::instance::Instance>) -> String {
let procedure_id = find_submitted_procedure_id(
instance,
"repartition",
&format!(
"table_name = '{TABLE_NAME}' \
AND json_path_match(payload, '$.source_type == \"unpartitioned\"')"
),
)
.await;
assert_procedure_actor(instance, &procedure_id, Some(PROCEDURE_ACTOR)).await;
let query = format!(
r#"SELECT type, catalog_name, schema_name, table_name, table_id,
json_path_match(payload, '$.version == 2') AS payload_version,
json_path_match(payload, '$.source_type == "unpartitioned"') AS unpartitioned_source,
json_path_match(payload, '$.target_partition_columns[0] == "host"') AS target_column
FROM greptime_private.events
WHERE type = 'repartition'
AND procedure_id = '{procedure_id}'
AND json_path_match(procedure_trigger, '$.type == "Submitted"')
AND table_name = '{TABLE_NAME}'"#
);
let expected = "\
+-------------+--------------+-------------+--------------------+----------+-----------------+----------------------+---------------+
| type | catalog_name | schema_name | table_name | table_id | payload_version | unpartitioned_source | target_column |
+-------------+--------------+-------------+--------------------+----------+-----------------+----------------------+---------------+
| repartition | greptime | public | repartition_events | 1024 | true | true | true |
+-------------+--------------+-------------+--------------------+----------+-----------------+----------------------+---------------+";
assert_eventually_eq(instance, &query, expected).await;
assert_repartition_lifecycle_event(instance, &procedure_id).await;
procedure_id
}
async fn assert_repartition_lifecycle_event(
instance: &Arc<frontend::instance::Instance>,
procedure_id: &str,
) {
let query = format!(
r#"SELECT type, catalog_name, schema_name, table_name, table_id
FROM greptime_private.events
WHERE type = 'repartition'
AND procedure_id = '{procedure_id}'
AND json_path_match(procedure_trigger, '$.type == "Succeeded"')
AND json_is_null(payload)"#
);
let expected = "\
+-------------+--------------+-------------+--------------------+----------+
| type | catalog_name | schema_name | table_name | table_id |
+-------------+--------------+-------------+--------------------+----------+
| repartition | greptime | public | repartition_events | 1024 |
+-------------+--------------+-------------+--------------------+----------+";
assert_eventually_eq(instance, &query, expected).await;
}
async fn assert_repartition_group_event(
instance: &Arc<frontend::instance::Instance>,
parent_procedure_id: &str,
) {
let procedure_id = find_submitted_procedure_id(
instance,
"repartition_group",
&format!(
"table_name = '{TABLE_NAME}' \
AND source_partition_expr IS NULL"
),
)
.await;
assert_procedure_actor(instance, &procedure_id, Some(PROCEDURE_ACTOR)).await;
let query = format!(
r#"SELECT type, catalog_name, schema_name, table_name, table_id,
parent_procedure_id = '{parent_procedure_id}' AS matches_parent_procedure_id,
repartition_group_id IS NOT NULL AS has_group_id,
source_region_id,
source_region_number,
source_partition_expr IS NULL AS default_source,
target_region_id,
target_region_number,
target_partition_expr
FROM greptime_private.events
WHERE type = 'repartition_group'
AND procedure_id = '{procedure_id}'
AND json_path_match(procedure_trigger, '$.type == "Submitted"')
AND table_name = '{TABLE_NAME}'
ORDER BY target_region_id"#
);
let expected = "\
+-------------------+--------------+-------------+--------------------+----------+-----------------------------+--------------+------------------+----------------------+----------------+------------------+----------------------+-----------------------+
| type | catalog_name | schema_name | table_name | table_id | matches_parent_procedure_id | has_group_id | source_region_id | source_region_number | default_source | target_region_id | target_region_number | target_partition_expr |
+-------------------+--------------+-------------+--------------------+----------+-----------------------------+--------------+------------------+----------------------+----------------+------------------+----------------------+-----------------------+
| repartition_group | greptime | public | repartition_events | 1024 | true | true | 4398046511104 | 0 | true | 4398046511104 | 0 | host < 10 |
| repartition_group | greptime | public | repartition_events | 1024 | true | true | 4398046511104 | 0 | true | 4398046511105 | 1 | host >= 10 |
+-------------------+--------------+-------------+--------------------+----------+-----------------------------+--------------+------------------+----------------------+----------------+------------------+----------------------+-----------------------+";
assert_eventually_eq(instance, &query, expected).await;
assert_repartition_group_lifecycle_event(instance, &procedure_id, parent_procedure_id).await;
}
async fn assert_repartition_group_lifecycle_event(
instance: &Arc<frontend::instance::Instance>,
procedure_id: &str,
parent_procedure_id: &str,
) {
let query = format!(
r#"SELECT type, catalog_name, schema_name, table_name, table_id,
parent_procedure_id = '{parent_procedure_id}' AS matches_parent_procedure_id,
repartition_group_id IS NOT NULL AS has_group_id
FROM greptime_private.events
WHERE type = 'repartition_group'
AND procedure_id = '{procedure_id}'
AND json_path_match(procedure_trigger, '$.type == "Succeeded"')
AND json_is_null(payload)"#
);
let expected = "\
+-------------------+--------------+-------------+--------------------+----------+-----------------------------+--------------+
| type | catalog_name | schema_name | table_name | table_id | matches_parent_procedure_id | has_group_id |
+-------------------+--------------+-------------+--------------------+----------+-----------------------------+--------------+
| repartition_group | greptime | public | repartition_events | 1024 | true | true |
+-------------------+--------------+-------------+--------------------+----------+-----------------------------+--------------+";
assert_eventually_eq(instance, &query, expected).await;
}
async fn assert_merge_repartition_group_event(
instance: &Arc<frontend::instance::Instance>,
parent_procedure_id: &str,
) {
let procedure_id = find_submitted_procedure_id(
instance,
"repartition_group",
&format!(
"table_name = '{TABLE_NAME}' \
AND source_partition_expr IS NOT NULL"
),
)
.await;
assert_procedure_actor(instance, &procedure_id, Some(PROCEDURE_ACTOR)).await;
let query = format!(
r#"SELECT type, catalog_name, schema_name, table_name, table_id,
parent_procedure_id = '{parent_procedure_id}' AS matches_parent_procedure_id,
repartition_group_id IS NOT NULL AS has_group_id,
source_region_id,
source_region_number,
source_partition_expr IS NOT NULL AS partitioned_source,
target_region_id,
target_region_number,
target_partition_expr IS NOT NULL AS target_expr
FROM greptime_private.events
WHERE type = 'repartition_group'
AND procedure_id = '{procedure_id}'
AND json_path_match(procedure_trigger, '$.type == "Submitted"')
AND table_name = '{TABLE_NAME}'
AND source_partition_expr IS NOT NULL
ORDER BY source_region_id"#
);
let expected = "\
+-------------------+--------------+-------------+--------------------+----------+-----------------------------+--------------+------------------+----------------------+--------------------+------------------+----------------------+-------------+
| type | catalog_name | schema_name | table_name | table_id | matches_parent_procedure_id | has_group_id | source_region_id | source_region_number | partitioned_source | target_region_id | target_region_number | target_expr |
+-------------------+--------------+-------------+--------------------+----------+-----------------------------+--------------+------------------+----------------------+--------------------+------------------+----------------------+-------------+
| repartition_group | greptime | public | repartition_events | 1024 | true | true | 4398046511104 | 0 | true | 4398046511104 | 0 | true |
| repartition_group | greptime | public | repartition_events | 1024 | true | true | 4398046511105 | 1 | true | 4398046511104 | 0 | true |
+-------------------+--------------+-------------+--------------------+----------+-----------------------------+--------------+------------------+----------------------+--------------------+------------------+----------------------+-------------+";
assert_eventually_eq(instance, &query, expected).await;
}
async fn find_submitted_procedure_id(
instance: &Arc<frontend::instance::Instance>,
event_type: &str,
predicate: &str,
) -> String {
find_eventually_string(
instance,
&format!(
r#"SELECT procedure_id
FROM greptime_private.events
WHERE type = '{event_type}'
AND json_path_match(procedure_trigger, '$.type == "Submitted"')
AND {predicate}
ORDER BY timestamp DESC
LIMIT 1"#
),
"procedure_id",
)
.await
}