From d1c85f68e8983a516690dffcfe5820d9dfa2b8a4 Mon Sep 17 00:00:00 2001 From: JackyWoo Date: Mon, 26 Sep 2022 18:29:15 +0800 Subject: [PATCH 01/59] manually snapshot creation for keeper --- docs/en/operations/clickhouse-keeper.md | 8 +++++++- src/Coordination/FourLetterCommand.cpp | 8 ++++++++ src/Coordination/FourLetterCommand.h | 13 +++++++++++++ src/Coordination/KeeperDispatcher.h | 6 ++++++ src/Coordination/KeeperServer.cpp | 5 +++++ src/Coordination/KeeperServer.h | 2 ++ .../test_keeper_four_word_command/test.py | 15 +++++++++++++++ 7 files changed, 56 insertions(+), 1 deletion(-) diff --git a/docs/en/operations/clickhouse-keeper.md b/docs/en/operations/clickhouse-keeper.md index 8bf64bca28f..6597e4e5be0 100644 --- a/docs/en/operations/clickhouse-keeper.md +++ b/docs/en/operations/clickhouse-keeper.md @@ -123,7 +123,7 @@ clickhouse keeper --config /etc/your_path_to_config/config.xml ClickHouse Keeper also provides 4lw commands which are almost the same with Zookeeper. Each command is composed of four letters such as `mntr`, `stat` etc. There are some more interesting commands: `stat` gives some general information about the server and connected clients, while `srvr` and `cons` give extended details on server and connections respectively. -The 4lw commands has a white list configuration `four_letter_word_white_list` which has default value `conf,cons,crst,envi,ruok,srst,srvr,stat,wchc,wchs,dirs,mntr,isro`. +The 4lw commands has a white list configuration `four_letter_word_white_list` which has default value `conf,cons,crst,envi,ruok,srst,srvr,stat,wchc,wchs,dirs,mntr,isro,csnp`. You can issue the commands to ClickHouse Keeper via telnet or nc, at the client port. @@ -306,6 +306,12 @@ Sessions with Ephemerals (1): /clickhouse/task_queue/ddl ``` +- `csnp`: Schedule a snapshot creation task. Return `"Snapshot creation scheduled."` if successfully scheduled or Fail to scheduled snapshot creation.` if failed. + +``` +Snapshot creation scheduled. +``` + ## [experimental] Migration from ZooKeeper {#migration-from-zookeeper} Seamlessly migration from ZooKeeper to ClickHouse Keeper is impossible you have to stop your ZooKeeper cluster, convert data and start ClickHouse Keeper. `clickhouse-keeper-converter` tool allows converting ZooKeeper logs and snapshots to ClickHouse Keeper snapshot. It works only with ZooKeeper > 3.4. Steps for migration: diff --git a/src/Coordination/FourLetterCommand.cpp b/src/Coordination/FourLetterCommand.cpp index c33630a913b..70009703c5a 100644 --- a/src/Coordination/FourLetterCommand.cpp +++ b/src/Coordination/FourLetterCommand.cpp @@ -136,6 +136,9 @@ void FourLetterCommandFactory::registerCommands(KeeperDispatcher & keeper_dispat FourLetterCommandPtr api_version_command = std::make_shared(keeper_dispatcher); factory.registerCommand(api_version_command); + FourLetterCommandPtr create_snapshot_command = std::make_shared(keeper_dispatcher); + factory.registerCommand(create_snapshot_command); + factory.initializeAllowList(keeper_dispatcher); factory.setInitialize(true); } @@ -472,4 +475,9 @@ String ApiVersionCommand::run() return toString(static_cast(Coordination::current_keeper_api_version)); } +String CreateSnapshotCommand::run() +{ + return keeper_dispatcher.createSnapshot() ? "Snapshot creation scheduled." : "Fail to scheduled snapshot creation."; +} + } diff --git a/src/Coordination/FourLetterCommand.h b/src/Coordination/FourLetterCommand.h index 8a98b94b33a..25cc281d5e1 100644 --- a/src/Coordination/FourLetterCommand.h +++ b/src/Coordination/FourLetterCommand.h @@ -327,4 +327,17 @@ struct ApiVersionCommand : public IFourLetterCommand String run() override; ~ApiVersionCommand() override = default; }; + +/// Create snapshot manually +struct CreateSnapshotCommand : public IFourLetterCommand +{ + explicit CreateSnapshotCommand(KeeperDispatcher & keeper_dispatcher_) + : IFourLetterCommand(keeper_dispatcher_) + { + } + + String name() override { return "csnp"; } + String run() override; + ~CreateSnapshotCommand() override = default; +}; } diff --git a/src/Coordination/KeeperDispatcher.h b/src/Coordination/KeeperDispatcher.h index 5e2701299f4..9b52721b951 100644 --- a/src/Coordination/KeeperDispatcher.h +++ b/src/Coordination/KeeperDispatcher.h @@ -201,6 +201,12 @@ public: { keeper_stats.reset(); } + + /// Create snapshot manually + bool createSnapshot() + { + return server->createSnapshot(); + } }; } diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 8186ddd0c00..f03f453aada 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -903,4 +903,9 @@ Keeper4LWInfo KeeperServer::getPartiallyFilled4LWInfo() const return result; } +bool KeeperServer::createSnapshot() +{ + return raft_instance->create_snapshot(); +} + } diff --git a/src/Coordination/KeeperServer.h b/src/Coordination/KeeperServer.h index 6873ef2a01e..f969e9ee063 100644 --- a/src/Coordination/KeeperServer.h +++ b/src/Coordination/KeeperServer.h @@ -130,6 +130,8 @@ public: /// Wait configuration update for action. Used by followers. /// Return true if update was successfully received. bool waitConfigurationUpdate(const ConfigUpdateAction & task); + + bool createSnapshot(); }; } diff --git a/tests/integration/test_keeper_four_word_command/test.py b/tests/integration/test_keeper_four_word_command/test.py index e8136d322d3..0995adb199c 100644 --- a/tests/integration/test_keeper_four_word_command/test.py +++ b/tests/integration/test_keeper_four_word_command/test.py @@ -634,3 +634,18 @@ def test_cmd_wchp(started_cluster): assert "/test_4lw_normal_node_1" in list_data finally: destroy_zk_client(zk) + + +def test_cmd_csnp(started_cluster): + zk = None + try: + wait_nodes() + clear_znodes() + reset_node_stats() + + zk = get_fake_zk(node1.name, timeout=30.0) + + data = send_4lw_cmd(cmd="csnp") + assert data == "Snapshot creation scheduled." + finally: + destroy_zk_client(zk) From dfb2be3a6732aed0890a82b9d79d12dc1b8ea841 Mon Sep 17 00:00:00 2001 From: JackyWoo Date: Mon, 26 Sep 2022 18:34:53 +0800 Subject: [PATCH 02/59] fix docs --- docs/en/operations/clickhouse-keeper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/operations/clickhouse-keeper.md b/docs/en/operations/clickhouse-keeper.md index 6597e4e5be0..1b8b1e02aa8 100644 --- a/docs/en/operations/clickhouse-keeper.md +++ b/docs/en/operations/clickhouse-keeper.md @@ -123,7 +123,7 @@ clickhouse keeper --config /etc/your_path_to_config/config.xml ClickHouse Keeper also provides 4lw commands which are almost the same with Zookeeper. Each command is composed of four letters such as `mntr`, `stat` etc. There are some more interesting commands: `stat` gives some general information about the server and connected clients, while `srvr` and `cons` give extended details on server and connections respectively. -The 4lw commands has a white list configuration `four_letter_word_white_list` which has default value `conf,cons,crst,envi,ruok,srst,srvr,stat,wchc,wchs,dirs,mntr,isro,csnp`. +The 4lw commands has a white list configuration `four_letter_word_white_list` which has default value `conf,cons,crst,envi,ruok,srst,srvr,stat,wchc,wchs,dirs,mntr,isro`. You can issue the commands to ClickHouse Keeper via telnet or nc, at the client port. From c813bbbddc5caffc486d014e333156dbc34a767a Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 1 Oct 2022 23:00:23 +0200 Subject: [PATCH 03/59] Add a test --- .../0_stateless/02456_progress_tty.expect | 45 +++++++++++++++++++ .../0_stateless/02456_progress_tty.reference | 0 2 files changed, 45 insertions(+) create mode 100755 tests/queries/0_stateless/02456_progress_tty.expect create mode 100644 tests/queries/0_stateless/02456_progress_tty.reference diff --git a/tests/queries/0_stateless/02456_progress_tty.expect b/tests/queries/0_stateless/02456_progress_tty.expect new file mode 100755 index 00000000000..32ed8fd9094 --- /dev/null +++ b/tests/queries/0_stateless/02456_progress_tty.expect @@ -0,0 +1,45 @@ +#!/usr/bin/expect -f + +set basedir [file dirname $argv0] +set basename [file tail $argv0] +#exp_internal -f $env(CLICKHOUSE_TMP)/$basename.debuglog 0 + +log_user 0 +set timeout 60 +match_max 100000 +set stty_init "rows 25 cols 80" + +expect_after { + # Do not ignore eof from expect + eof { exp_continue } + # A default timeout action is to do nothing, change it to fail + timeout { exit 1 } +} + +# Progress is displayed by default +spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null" +expect "Progress: " +expect "█" + +# It is true even if we redirect both stdout and stderr to /dev/null +spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null 2>&1" +expect "Progress: " +expect "█" + +# The option --progress has implicit value of true +spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --progress --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null 2>&1" +expect "Progress: " +expect "█" + +# But we can set it to false +spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --progress false --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' 2>/dev/null" +expect -exact "3\n" + +# As well as to 0 for the same effect +spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --progress 0 --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' 2>/dev/null" +expect -exact "3\n" + +# If we set it to 1, the progress will be displayed as well +spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --progress 1 --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null 2>&1" +expect "Progress: " +expect "█" diff --git a/tests/queries/0_stateless/02456_progress_tty.reference b/tests/queries/0_stateless/02456_progress_tty.reference new file mode 100644 index 00000000000..e69de29bb2d From 39633120ab969ce2e710dd8d530e472325e0e5a1 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 1 Oct 2022 23:17:49 +0200 Subject: [PATCH 04/59] Add a test --- .../0_stateless/02456_progress_tty.expect | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/tests/queries/0_stateless/02456_progress_tty.expect b/tests/queries/0_stateless/02456_progress_tty.expect index 32ed8fd9094..6a96c88c05c 100755 --- a/tests/queries/0_stateless/02456_progress_tty.expect +++ b/tests/queries/0_stateless/02456_progress_tty.expect @@ -4,42 +4,52 @@ set basedir [file dirname $argv0] set basename [file tail $argv0] #exp_internal -f $env(CLICKHOUSE_TMP)/$basename.debuglog 0 -log_user 0 +log_user 1 set timeout 60 match_max 100000 -set stty_init "rows 25 cols 80" +set stty_init "rows 25 cols 120" expect_after { - # Do not ignore eof from expect eof { exp_continue } - # A default timeout action is to do nothing, change it to fail timeout { exit 1 } } +spawn bash +send "source $basedir/../shell_config.sh\r" + # Progress is displayed by default -spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null" +send "\$CLICKHOUSE_LOCAL --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null\r" expect "Progress: " expect "█" +send "\3" # It is true even if we redirect both stdout and stderr to /dev/null -spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null 2>&1" +send "\$CLICKHOUSE_LOCAL --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null 2>&1\r" expect "Progress: " expect "█" +send "\3" # The option --progress has implicit value of true -spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --progress --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null 2>&1" +send "\$CLICKHOUSE_LOCAL --progress --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null 2>&1\r" expect "Progress: " expect "█" +send "\3" # But we can set it to false -spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --progress false --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' 2>/dev/null" -expect -exact "3\n" +send "\$CLICKHOUSE_LOCAL --progress false --query 'SELECT sleep(1), \$\$Hello\$\$ FROM numbers(3) SETTINGS max_block_size = 1' 2>/dev/null\r" +expect -exact "0\tHello\r\n" +send "\3" # As well as to 0 for the same effect -spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --progress 0 --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' 2>/dev/null" -expect -exact "3\n" +send "\$CLICKHOUSE_LOCAL --progress 0 --query 'SELECT sleep(1), \$\$Hello\$\$ FROM numbers(3) SETTINGS max_block_size = 1' 2>/dev/null\r" +expect -exact "0\tHello\r\n" +send "\3" # If we set it to 1, the progress will be displayed as well -spawn bash -c "source $basedir/../shell_config.sh ; \$CLICKHOUSE_LOCAL --progress 1 --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null 2>&1" +send "\$CLICKHOUSE_LOCAL --progress 1 --query 'SELECT sum(sleep(1) = 0) FROM numbers(3) SETTINGS max_block_size = 1' >/dev/null 2>&1\r" expect "Progress: " expect "█" +send "\3" + +send "exit\r" +expect eof From 9a54af7df936a6f50c1458f878a228c050625ded Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 1 Oct 2022 23:18:54 +0200 Subject: [PATCH 05/59] Add a test --- tests/queries/0_stateless/02456_progress_tty.expect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02456_progress_tty.expect b/tests/queries/0_stateless/02456_progress_tty.expect index 6a96c88c05c..90be3b1076e 100755 --- a/tests/queries/0_stateless/02456_progress_tty.expect +++ b/tests/queries/0_stateless/02456_progress_tty.expect @@ -4,7 +4,7 @@ set basedir [file dirname $argv0] set basename [file tail $argv0] #exp_internal -f $env(CLICKHOUSE_TMP)/$basename.debuglog 0 -log_user 1 +log_user 0 set timeout 60 match_max 100000 set stty_init "rows 25 cols 120" From b65f674dd488cd4da91e0a50b20744c0b74cbbdf Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 1 Oct 2022 23:19:02 +0200 Subject: [PATCH 06/59] Add a test --- tests/queries/0_stateless/02456_progress_tty.expect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queries/0_stateless/02456_progress_tty.expect b/tests/queries/0_stateless/02456_progress_tty.expect index 90be3b1076e..9daa6caa3fa 100755 --- a/tests/queries/0_stateless/02456_progress_tty.expect +++ b/tests/queries/0_stateless/02456_progress_tty.expect @@ -2,7 +2,7 @@ set basedir [file dirname $argv0] set basename [file tail $argv0] -#exp_internal -f $env(CLICKHOUSE_TMP)/$basename.debuglog 0 +exp_internal -f $env(CLICKHOUSE_TMP)/$basename.debuglog 0 log_user 0 set timeout 60 From 5d710e21f108b736028afd3341d9d8029bb7c024 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sat, 1 Oct 2022 23:19:36 +0200 Subject: [PATCH 07/59] Render progress directly in tty --- programs/client/Client.cpp | 2 +- programs/local/LocalServer.cpp | 2 +- src/Client/ClientBase.cpp | 66 ++++++++++++++++++++++--------- src/Client/ClientBase.h | 6 ++- src/Common/ProgressIndication.cpp | 32 +++++++-------- src/Common/ProgressIndication.h | 14 ++++--- 6 files changed, 76 insertions(+), 46 deletions(-) diff --git a/programs/client/Client.cpp b/programs/client/Client.cpp index 303c8c2ce4f..cc3bbbb63ca 100644 --- a/programs/client/Client.cpp +++ b/programs/client/Client.cpp @@ -1025,7 +1025,7 @@ void Client::processConfig() } else { - need_render_progress = config().getBool("progress", false); + need_render_progress = config().getBool("progress", true); echo_queries = config().getBool("echo", false); ignore_error = config().getBool("ignore-error", false); diff --git a/programs/local/LocalServer.cpp b/programs/local/LocalServer.cpp index 2b9d819f5eb..26d5acafbec 100644 --- a/programs/local/LocalServer.cpp +++ b/programs/local/LocalServer.cpp @@ -488,7 +488,7 @@ void LocalServer::processConfig() } else { - need_render_progress = config().getBool("progress", false); + need_render_progress = config().getBool("progress", true); echo_queries = config().hasOption("echo") || config().hasOption("verbose"); ignore_error = config().getBool("ignore-error", false); is_multiquery = true; diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index f407fab68f1..566ee49fba3 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -415,7 +415,7 @@ void ClientBase::onData(Block & block, ASTPtr parsed_query) /// If results are written INTO OUTFILE, we can avoid clearing progress to avoid flicker. if (need_render_progress && (stdout_is_a_tty || is_interactive) && (!select_into_file || select_into_file_and_stdout)) - progress_indication.clearProgressOutput(); + progress_indication.clearProgressOutput(*tty_buf); try { @@ -436,7 +436,7 @@ void ClientBase::onData(Block & block, ASTPtr parsed_query) { if (select_into_file && !select_into_file_and_stdout) std::cerr << "\r"; - progress_indication.writeProgress(); + progress_indication.writeProgress(*tty_buf); } } @@ -444,7 +444,8 @@ void ClientBase::onData(Block & block, ASTPtr parsed_query) void ClientBase::onLogData(Block & block) { initLogsOutputStream(); - progress_indication.clearProgressOutput(); + if (need_render_progress) + progress_indication.clearProgressOutput(*tty_buf); logs_out_stream->writeLogs(block); logs_out_stream->flush(); } @@ -640,6 +641,33 @@ void ClientBase::initLogsOutputStream() } } +void ClientBase::initTtyBuffer() +{ + if (!tty_buf) + { + static constexpr auto tty_file_name = "/dev/tty"; + + /// Output all progress bar commands to stderr at once to avoid flicker. + /// This size is usually greater than the window size. + static constexpr size_t buf_size = 1024; + + std::error_code ec; + std::filesystem::file_status tty = std::filesystem::status(tty_file_name, ec); + + if (!ec && exists(tty) && is_character_file(tty) + && (tty.permissions() & std::filesystem::perms::others_write) != std::filesystem::perms::none) + { + tty_buf = std::make_unique(tty_file_name, buf_size); + } + else if (stderr_is_a_tty) + { + tty_buf = std::make_unique(STDERR_FILENO, buf_size); + } + else + need_render_progress = false; + } +} + void ClientBase::updateSuggest(const ASTPtr & ast) { std::vector new_words; @@ -939,13 +967,14 @@ void ClientBase::onProgress(const Progress & value) output_format->onProgress(value); if (need_render_progress) - progress_indication.writeProgress(); + progress_indication.writeProgress(*tty_buf); } void ClientBase::onEndOfStream() { - progress_indication.clearProgressOutput(); + if (need_render_progress) + progress_indication.clearProgressOutput(*tty_buf); if (output_format) output_format->finalize(); @@ -953,10 +982,7 @@ void ClientBase::onEndOfStream() resetOutput(); if (is_interactive && !written_first_block) - { - progress_indication.clearProgressOutput(); std::cout << "Ok." << std::endl; - } } @@ -1000,14 +1026,15 @@ void ClientBase::onProfileEvents(Block & block) progress_indication.updateThreadEventData(thread_times); if (need_render_progress) - progress_indication.writeProgress(); + progress_indication.writeProgress(*tty_buf); if (profile_events.print) { if (profile_events.watch.elapsedMilliseconds() >= profile_events.delay_ms) { initLogsOutputStream(); - progress_indication.clearProgressOutput(); + if (need_render_progress) + progress_indication.clearProgressOutput(*tty_buf); logs_out_stream->writeProfileEvents(block); logs_out_stream->flush(); @@ -1181,7 +1208,7 @@ void ClientBase::sendData(Block & sample, const ColumnsDescription & columns_des progress_indication.updateProgress(Progress(file_progress)); /// Set callback to be called on file progress. - progress_indication.setFileProgressCallback(global_context, true); + progress_indication.setFileProgressCallback(global_context, *tty_buf); } /// If data fetched from file (maybe compressed file) @@ -1433,12 +1460,10 @@ bool ClientBase::receiveEndOfQuery() void ClientBase::cancelQuery() { connection->sendCancel(); - if (is_interactive) - { - progress_indication.clearProgressOutput(); - std::cout << "Cancelling query." << std::endl; + if (need_render_progress) + progress_indication.clearProgressOutput(*tty_buf); - } + std::cout << "Cancelling query." << std::endl; cancelled = true; } @@ -1556,7 +1581,8 @@ void ClientBase::processParsedSingleQuery(const String & full_query, const Strin if (profile_events.last_block) { initLogsOutputStream(); - progress_indication.clearProgressOutput(); + if (need_render_progress) + progress_indication.clearProgressOutput(*tty_buf); logs_out_stream->writeProfileEvents(profile_events.last_block); logs_out_stream->flush(); @@ -2216,6 +2242,8 @@ void ClientBase::init(int argc, char ** argv) stdout_is_a_tty = isatty(STDOUT_FILENO); stderr_is_a_tty = isatty(STDERR_FILENO); terminal_width = getTerminalWidth(); + if (need_render_progress) + initTtyBuffer(); Arguments common_arguments{""}; /// 0th argument is ignored. std::vector external_tables_arguments; @@ -2243,7 +2271,7 @@ void ClientBase::init(int argc, char ** argv) ("stage", po::value()->default_value("complete"), "Request query processing up to specified stage: complete,fetch_columns,with_mergeable_state,with_mergeable_state_after_aggregation,with_mergeable_state_after_aggregation_and_limit") ("query_kind", po::value()->default_value("initial_query"), "One of initial_query/secondary_query/no_query") ("query_id", po::value(), "query_id") - ("progress", "print progress of queries execution") + ("progress", po::value()->implicit_value(true), "print progress of queries execution") ("disable_suggestion,A", "Disable loading suggestion data. Note that suggestion data is loaded asynchronously through a second connection to ClickHouse server. Also it is reasonable to disable suggestion if you want to paste a query with TAB characters. Shorthand option -A is for those who get used to mysql client.") ("time,t", "print query execution time to stderr in non-interactive mode (for benchmarks)") @@ -2348,7 +2376,7 @@ void ClientBase::init(int argc, char ** argv) if (options.count("profile-events-delay-ms")) config().setInt("profile-events-delay-ms", options["profile-events-delay-ms"].as()); if (options.count("progress")) - config().setBool("progress", true); + config().setBool("progress", options["progress"].as()); if (options.count("echo")) config().setBool("echo", true); if (options.count("disable_suggestion")) diff --git a/src/Client/ClientBase.h b/src/Client/ClientBase.h index 3a6e623dc3f..86471405d00 100644 --- a/src/Client/ClientBase.h +++ b/src/Client/ClientBase.h @@ -143,11 +143,11 @@ private: void initOutputFormat(const Block & block, ASTPtr parsed_query); void initLogsOutputStream(); + void initTtyBuffer(); String prompt() const; void resetOutput(); - void outputQueryInfo(bool echo_query_); void parseAndCheckOptions(OptionsDescription & options_description, po::variables_map & options, Arguments & arguments); void updateSuggest(const ASTPtr & ast); @@ -219,6 +219,10 @@ protected: String server_logs_file; std::unique_ptr logs_out_stream; + /// /dev/tty if accessible or std::cerr - for progress bar. + /// We prefer to output progress bar directly to tty to allow user to redirect stdout and stderr and still get the progress indication. + std::unique_ptr tty_buf; + String home_path; String history_file; /// Path to a file containing command history. diff --git a/src/Common/ProgressIndication.cpp b/src/Common/ProgressIndication.cpp index ab4ecf5c25f..7828dc18413 100644 --- a/src/Common/ProgressIndication.cpp +++ b/src/Common/ProgressIndication.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -44,15 +45,6 @@ bool ProgressIndication::updateProgress(const Progress & value) return progress.incrementPiecewiseAtomically(value); } -void ProgressIndication::clearProgressOutput() -{ - if (written_progress_chars) - { - written_progress_chars = 0; - std::cerr << "\r" CLEAR_TO_END_OF_LINE; - } -} - void ProgressIndication::resetProgress() { watch.restart(); @@ -67,15 +59,12 @@ void ProgressIndication::resetProgress() } } -void ProgressIndication::setFileProgressCallback(ContextMutablePtr context, bool write_progress_on_update_) +void ProgressIndication::setFileProgressCallback(ContextMutablePtr context, WriteBuffer & message) { - write_progress_on_update = write_progress_on_update_; context->setFileProgressCallback([&](const FileProgress & file_progress) { progress.incrementPiecewiseAtomically(Progress(file_progress)); - - if (write_progress_on_update) - writeProgress(); + writeProgress(message); }); } @@ -153,13 +142,10 @@ void ProgressIndication::writeFinalProgress() std::cout << ". "; } -void ProgressIndication::writeProgress() +void ProgressIndication::writeProgress(WriteBuffer & message) { std::lock_guard lock(progress_mutex); - /// Output all progress bar commands to stderr at once to avoid flicker. - WriteBufferFromFileDescriptor message(STDERR_FILENO, 1024); - static size_t increment = 0; static const char * indicators[8] = { "\033[1;30m→\033[0m", @@ -318,4 +304,14 @@ void ProgressIndication::writeProgress() message.next(); } +void ProgressIndication::clearProgressOutput(WriteBuffer & message) +{ + if (written_progress_chars) + { + written_progress_chars = 0; + message << "\r" CLEAR_TO_END_OF_LINE; + message.next(); + } +} + } diff --git a/src/Common/ProgressIndication.h b/src/Common/ProgressIndication.h index 4f05f41b9d0..1262a4dc968 100644 --- a/src/Common/ProgressIndication.h +++ b/src/Common/ProgressIndication.h @@ -12,9 +12,12 @@ /// http://en.wikipedia.org/wiki/ANSI_escape_code #define CLEAR_TO_END_OF_LINE "\033[K" + namespace DB { +class WriteBuffer; + struct ThreadEventData { UInt64 time() const noexcept { return user_ms + system_ms; } @@ -30,14 +33,13 @@ using HostToThreadTimesMap = std::unordered_map; class ProgressIndication { public: - /// Write progress to stderr. - void writeProgress(); + /// Write progress bar. + void writeProgress(WriteBuffer & message); + void clearProgressOutput(WriteBuffer & message); + /// Write summary. void writeFinalProgress(); - /// Clear stderr output. - void clearProgressOutput(); - /// Reset progress values. void resetProgress(); @@ -52,7 +54,7 @@ public: /// In some cases there is a need to update progress value, when there is no access to progress_inidcation object. /// In this case it is added via context. /// `write_progress_on_update` is needed to write progress for loading files data via pipe in non-interactive mode. - void setFileProgressCallback(ContextMutablePtr context, bool write_progress_on_update = false); + void setFileProgressCallback(ContextMutablePtr context, WriteBuffer & message); /// How much seconds passed since query execution start. double elapsedSeconds() const { return getElapsedNanoseconds() / 1e9; } From 8b84788d089cbbf46e0a14f69db1f31b9e79e21d Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 2 Oct 2022 00:38:41 +0200 Subject: [PATCH 08/59] Fix error --- src/Client/ClientBase.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index 566ee49fba3..936456c4c34 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -104,6 +104,7 @@ namespace ErrorCodes extern const int CANNOT_SET_SIGNAL_HANDLER; extern const int UNRECOGNIZED_ARGUMENTS; extern const int LOGICAL_ERROR; + extern const int CANNOT_OPEN_FILE; } } @@ -657,9 +658,22 @@ void ClientBase::initTtyBuffer() if (!ec && exists(tty) && is_character_file(tty) && (tty.permissions() & std::filesystem::perms::others_write) != std::filesystem::perms::none) { - tty_buf = std::make_unique(tty_file_name, buf_size); + try + { + tty_buf = std::make_unique(tty_file_name, buf_size); + return; + } + catch (const Exception & e) + { + if (e.code() != ErrorCodes::CANNOT_OPEN_FILE) + throw; + + /// It is normal if file exists, indicated as writeable but still cannot be opened. + /// Fallback to other options. + } } - else if (stderr_is_a_tty) + + if (stderr_is_a_tty) { tty_buf = std::make_unique(STDERR_FILENO, buf_size); } From 5be163b6467ea482dbfd1f1cfff40b1bae646220 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 2 Oct 2022 01:24:19 +0200 Subject: [PATCH 09/59] Fix comment --- src/Client/ClientBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index 936456c4c34..320afb5e0f8 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -648,7 +648,7 @@ void ClientBase::initTtyBuffer() { static constexpr auto tty_file_name = "/dev/tty"; - /// Output all progress bar commands to stderr at once to avoid flicker. + /// Output all progress bar commands to terminal at once to avoid flicker. /// This size is usually greater than the window size. static constexpr size_t buf_size = 1024; From bc473f4ec0d57ce779bfaf669d57e6376225be28 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 2 Oct 2022 16:08:33 +0200 Subject: [PATCH 10/59] Something --- src/Client/ClientBase.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index 320afb5e0f8..3385a865550 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -661,10 +661,19 @@ void ClientBase::initTtyBuffer() try { tty_buf = std::make_unique(tty_file_name, buf_size); + + /// It is possible that the terminal file has writeable permissions + /// but we cannot write anything there. Check it with invisible character. + tty_buf->write('\0'); + tty_buf->next(); + return; } catch (const Exception & e) { + if (tty_buf) + tty_buf.reset(); + if (e.code() != ErrorCodes::CANNOT_OPEN_FILE) throw; From eef40ce88407e3315c1de0a1348927be9918f621 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Wed, 5 Oct 2022 02:29:52 +0200 Subject: [PATCH 11/59] Some attempt --- src/Client/ClientBase.cpp | 3 +-- src/Client/ClientBase.h | 4 +++- src/Common/ProgressIndication.cpp | 11 +++++++---- src/Common/ProgressIndication.h | 11 ++++------- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index 3385a865550..52c2294eb7c 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -1,13 +1,11 @@ #include #include -#include #include #include #include #include -#include #include #include #include @@ -66,6 +64,7 @@ #include #include #include +#include #include #include #include diff --git a/src/Client/ClientBase.h b/src/Client/ClientBase.h index 86471405d00..9cc318c2f2b 100644 --- a/src/Client/ClientBase.h +++ b/src/Client/ClientBase.h @@ -15,6 +15,7 @@ #include #include + namespace po = boost::program_options; @@ -38,6 +39,7 @@ enum MultiQueryProcessingStage void interruptSignalHandler(int signum); class InternalTextLogs; +class WriteBufferFromFileDescriptor; class ClientBase : public Poco::Util::Application, public IHints<2, ClientBase> { @@ -221,7 +223,7 @@ protected: /// /dev/tty if accessible or std::cerr - for progress bar. /// We prefer to output progress bar directly to tty to allow user to redirect stdout and stderr and still get the progress indication. - std::unique_ptr tty_buf; + std::unique_ptr tty_buf; String home_path; String history_file; /// Path to a file containing command history. diff --git a/src/Common/ProgressIndication.cpp b/src/Common/ProgressIndication.cpp index 7828dc18413..30604d7808a 100644 --- a/src/Common/ProgressIndication.cpp +++ b/src/Common/ProgressIndication.cpp @@ -12,6 +12,9 @@ #include "IO/WriteBufferFromString.h" #include +/// http://en.wikipedia.org/wiki/ANSI_escape_code +#define CLEAR_TO_END_OF_LINE "\033[K" + namespace { @@ -59,7 +62,7 @@ void ProgressIndication::resetProgress() } } -void ProgressIndication::setFileProgressCallback(ContextMutablePtr context, WriteBuffer & message) +void ProgressIndication::setFileProgressCallback(ContextMutablePtr context, WriteBufferFromFileDescriptor & message) { context->setFileProgressCallback([&](const FileProgress & file_progress) { @@ -142,7 +145,7 @@ void ProgressIndication::writeFinalProgress() std::cout << ". "; } -void ProgressIndication::writeProgress(WriteBuffer & message) +void ProgressIndication::writeProgress(WriteBufferFromFileDescriptor & message) { std::lock_guard lock(progress_mutex); @@ -160,7 +163,7 @@ void ProgressIndication::writeProgress(WriteBuffer & message) const char * indicator = indicators[increment % 8]; - size_t terminal_width = getTerminalWidth(); + size_t terminal_width = getTerminalWidth(message.getFD()); if (!written_progress_chars) { @@ -304,7 +307,7 @@ void ProgressIndication::writeProgress(WriteBuffer & message) message.next(); } -void ProgressIndication::clearProgressOutput(WriteBuffer & message) +void ProgressIndication::clearProgressOutput(WriteBufferFromFileDescriptor & message) { if (written_progress_chars) { diff --git a/src/Common/ProgressIndication.h b/src/Common/ProgressIndication.h index 1262a4dc968..33c77f4fc34 100644 --- a/src/Common/ProgressIndication.h +++ b/src/Common/ProgressIndication.h @@ -9,14 +9,11 @@ #include #include -/// http://en.wikipedia.org/wiki/ANSI_escape_code -#define CLEAR_TO_END_OF_LINE "\033[K" - namespace DB { -class WriteBuffer; +class WriteBufferFromFileDescriptor; struct ThreadEventData { @@ -34,8 +31,8 @@ class ProgressIndication { public: /// Write progress bar. - void writeProgress(WriteBuffer & message); - void clearProgressOutput(WriteBuffer & message); + void writeProgress(WriteBufferFromFileDescriptor & message); + void clearProgressOutput(WriteBufferFromFileDescriptor & message); /// Write summary. void writeFinalProgress(); @@ -54,7 +51,7 @@ public: /// In some cases there is a need to update progress value, when there is no access to progress_inidcation object. /// In this case it is added via context. /// `write_progress_on_update` is needed to write progress for loading files data via pipe in non-interactive mode. - void setFileProgressCallback(ContextMutablePtr context, WriteBuffer & message); + void setFileProgressCallback(ContextMutablePtr context, WriteBufferFromFileDescriptor & message); /// How much seconds passed since query execution start. double elapsedSeconds() const { return getElapsedNanoseconds() / 1e9; } From 52ca29140e739f1a67caccb450ccda7c97f349b6 Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Fri, 21 Oct 2022 12:27:20 -0400 Subject: [PATCH 12/59] WIP: add Superset deploy instructions --- .../example-datasets/cell-towers.md | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/en/getting-started/example-datasets/cell-towers.md b/docs/en/getting-started/example-datasets/cell-towers.md index 3d993c3e224..989ae6f0a7e 100644 --- a/docs/en/getting-started/example-datasets/cell-towers.md +++ b/docs/en/getting-started/example-datasets/cell-towers.md @@ -10,6 +10,7 @@ import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; import ActionsMenu from '@site/docs/en/_snippets/_service_actions_menu.md'; import SQLConsoleDetail from '@site/docs/en/_snippets/_launch_sql_console.md'; +import SupersetDocker from '@site/docs/en/_snippets/_add_superset_detail.md'; This dataset is from [OpenCellid](https://www.opencellid.org/) - The world's largest Open Database of Cell Towers. @@ -224,6 +225,29 @@ WHERE pointInPolygon((lon, lat), (SELECT * FROM moscow)) 1 rows in set. Elapsed: 0.067 sec. Processed 43.28 million rows, 692.42 MB (645.83 million rows/s., 10.33 GB/s.) ``` +## Review of the schema + +This dataset primarily provides the location (Longitude and Latitude) and radio types at mobile cellular towers worldwide. The column descriptions can be found in the [community forum](https://community.opencellid.org/t/documenting-the-columns-in-the-downloadable-cells-database-csv/186). The columns used in the visualizations that will be built are described below + +Here is a description of the columns taken from the OpenCellID forum: + +| Column | Description | +|--------------|--------------------------------------------------------| +| radio | Technology generation: CDMA, GSM, UMTS, 5G NR | +| mcc | Mobile Country Code: `204` is The Netherlands | +| lon | Longitude: With Latitude, approximate tower location | +| lat | Latitude: With Longitude, approximate tower location | + +:::tip mcc +To find your MCC check [Mobile network codes](https://en.wikipedia.org/wiki/Mobile_country_code), and use the first three digits in the `MCC / MNC` column. +::: + +## Build visualizations with Apache Superset + +Superset is easy to run from Docker. If you already have Superset running, all you need to do is add ClickHouse Connect with `pip install clickhouse-connect`. If you need to install Superset open the **Launch Apache Superset in Docker** directly below. + + + The data is also available for interactive queries in the [Playground](https://play.clickhouse.com/play?user=play), [example](https://play.clickhouse.com/play?user=play#U0VMRUNUIG1jYywgY291bnQoKSBGUk9NIGNlbGxfdG93ZXJzIEdST1VQIEJZIG1jYyBPUkRFUiBCWSBjb3VudCgpIERFU0M=). Although you cannot create temporary tables there. From 39c88c74e84b1109015ad5b80e164bf57799e3ba Mon Sep 17 00:00:00 2001 From: JackyWoo Date: Sat, 22 Oct 2022 22:31:17 +0800 Subject: [PATCH 13/59] check whether last manually created snapshot is done --- contrib/NuRaft | 2 +- docs/en/operations/clickhouse-keeper.md | 8 +++++++- src/Coordination/FourLetterCommand.cpp | 7 ++++++- src/Coordination/FourLetterCommand.h | 14 ++++++++++++++ src/Coordination/KeeperDispatcher.h | 6 ++++++ src/Coordination/KeeperServer.cpp | 16 +++++++++++++++- src/Coordination/KeeperServer.h | 6 ++++++ .../test_keeper_four_word_command/test.py | 9 ++++++--- 8 files changed, 61 insertions(+), 7 deletions(-) diff --git a/contrib/NuRaft b/contrib/NuRaft index 1be805e7cb2..e4e746a24eb 160000 --- a/contrib/NuRaft +++ b/contrib/NuRaft @@ -1 +1 @@ -Subproject commit 1be805e7cb2494aa8170015493474379b0362dfc +Subproject commit e4e746a24eb56861a86f3672771e3308d8c40722 diff --git a/docs/en/operations/clickhouse-keeper.md b/docs/en/operations/clickhouse-keeper.md index 03eddd4f6ed..66b4685bff5 100644 --- a/docs/en/operations/clickhouse-keeper.md +++ b/docs/en/operations/clickhouse-keeper.md @@ -309,12 +309,18 @@ Sessions with Ephemerals (1): /clickhouse/task_queue/ddl ``` -- `csnp`: Schedule a snapshot creation task. Return `"Snapshot creation scheduled."` if successfully scheduled or Fail to scheduled snapshot creation.` if failed. +- `csnp`: Schedule a snapshot creation task. Return `Snapshot creation scheduled.` if successfully scheduled or `Fail to scheduled snapshot creation.` if failed. ``` Snapshot creation scheduled. ``` +- `snpd`: Whether the last successfully scheduled snapshot creation is done. Return `Yes` if true or `No` if false. + +``` +Yes +``` + ## [experimental] Migration from ZooKeeper {#migration-from-zookeeper} Seamlessly migration from ZooKeeper to ClickHouse Keeper is impossible you have to stop your ZooKeeper cluster, convert data and start ClickHouse Keeper. `clickhouse-keeper-converter` tool allows converting ZooKeeper logs and snapshots to ClickHouse Keeper snapshot. It works only with ZooKeeper > 3.4. Steps for migration: diff --git a/src/Coordination/FourLetterCommand.cpp b/src/Coordination/FourLetterCommand.cpp index 70009703c5a..3d1077ea84c 100644 --- a/src/Coordination/FourLetterCommand.cpp +++ b/src/Coordination/FourLetterCommand.cpp @@ -477,7 +477,12 @@ String ApiVersionCommand::run() String CreateSnapshotCommand::run() { - return keeper_dispatcher.createSnapshot() ? "Snapshot creation scheduled." : "Fail to scheduled snapshot creation."; + return keeper_dispatcher.createSnapshot() ? "Snapshot creation scheduled." : "Fail to scheduled snapshot creation task."; +} + +String CheckSnapshotDoneCommand::run() +{ + return keeper_dispatcher.snapshotDone() ? "Snapshot creation done." : "Fail to scheduled snapshot creation task."; } } diff --git a/src/Coordination/FourLetterCommand.h b/src/Coordination/FourLetterCommand.h index 5001a750d66..28f1d7f153f 100644 --- a/src/Coordination/FourLetterCommand.h +++ b/src/Coordination/FourLetterCommand.h @@ -340,4 +340,18 @@ struct CreateSnapshotCommand : public IFourLetterCommand String run() override; ~CreateSnapshotCommand() override = default; }; + +/// Check whether last manual snapshot done +struct CheckSnapshotDoneCommand : public IFourLetterCommand +{ + explicit CheckSnapshotDoneCommand(KeeperDispatcher & keeper_dispatcher_) + : IFourLetterCommand(keeper_dispatcher_) + { + } + + String name() override { return "snpd"; } + String run() override; + ~CheckSnapshotDoneCommand() override = default; +}; + } diff --git a/src/Coordination/KeeperDispatcher.h b/src/Coordination/KeeperDispatcher.h index 79212ea3040..48681957c13 100644 --- a/src/Coordination/KeeperDispatcher.h +++ b/src/Coordination/KeeperDispatcher.h @@ -209,6 +209,12 @@ public: { return server->createSnapshot(); } + + /// Whether the last manually created snapshot is done + bool snapshotDone() + { + return server->snapshotDone(); + } }; } diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index e0186927b54..87ebea0b4ab 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -114,6 +114,7 @@ KeeperServer::KeeperServer( , is_recovering(config.getBool("keeper_server.force_recovery", false)) , keeper_context{std::make_shared()} , create_snapshot_on_exit(config.getBool("keeper_server.create_snapshot_on_exit", true)) + , last_manual_snapshot_log_idx(0) { if (coordination_settings->quorum_reads) LOG_WARNING(log, "Quorum reads enabled, Keeper will work slower."); @@ -908,7 +909,20 @@ Keeper4LWInfo KeeperServer::getPartiallyFilled4LWInfo() const bool KeeperServer::createSnapshot() { - return raft_instance->create_snapshot(); + std::lock_guard lock(snapshot_mutex); + if (raft_instance->create_snapshot()) + { + last_manual_snapshot_log_idx = raft_instance->get_last_snapshot_idx(); + LOG_INFO(log, "Successfully schedule a keeper snapshot creation task at log index {}", last_manual_snapshot_log_idx); + return true; + } + return false; +} + +bool KeeperServer::snapshotDone() +{ + std::lock_guard lock(snapshot_mutex); + return last_manual_snapshot_log_idx != 0 && last_manual_snapshot_log_idx == raft_instance->get_last_snapshot_idx(); } } diff --git a/src/Coordination/KeeperServer.h b/src/Coordination/KeeperServer.h index ec832199387..11e3b75d127 100644 --- a/src/Coordination/KeeperServer.h +++ b/src/Coordination/KeeperServer.h @@ -66,6 +66,10 @@ private: const bool create_snapshot_on_exit; + /// Used to check whether the previous manually created snapshot complete. + uint64_t last_manual_snapshot_log_idx; + std::mutex snapshot_mutex; + public: KeeperServer( const KeeperConfigurationAndSettingsPtr & settings_, @@ -133,6 +137,8 @@ public: bool waitConfigurationUpdate(const ConfigUpdateAction & task); bool createSnapshot(); + + bool snapshotDone(); }; } diff --git a/tests/integration/test_keeper_four_word_command/test.py b/tests/integration/test_keeper_four_word_command/test.py index 2b2343757bb..bfe0b2a96e4 100644 --- a/tests/integration/test_keeper_four_word_command/test.py +++ b/tests/integration/test_keeper_four_word_command/test.py @@ -598,7 +598,7 @@ def test_cmd_wchp(started_cluster): destroy_zk_client(zk) -def test_cmd_csnp(started_cluster): +def test_cmd_snapshot(started_cluster): zk = None try: wait_nodes() @@ -607,7 +607,10 @@ def test_cmd_csnp(started_cluster): zk = get_fake_zk(node1.name, timeout=30.0) - data = send_4lw_cmd(cmd="csnp") - assert data == "Snapshot creation scheduled." + create = send_4lw_cmd(cmd="csnp") + assert create == "Snapshot creation scheduled." + + check = send_4lw_cmd(cmd="snpd") + assert (check == "Yes" or check == "No") finally: destroy_zk_client(zk) From 42e391a0191ef046a499fab5c245e5714475cbe7 Mon Sep 17 00:00:00 2001 From: JackyWoo Date: Sat, 22 Oct 2022 22:47:03 +0800 Subject: [PATCH 14/59] fix test --- tests/integration/test_keeper_four_word_command/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_keeper_four_word_command/test.py b/tests/integration/test_keeper_four_word_command/test.py index bfe0b2a96e4..4949d6f70de 100644 --- a/tests/integration/test_keeper_four_word_command/test.py +++ b/tests/integration/test_keeper_four_word_command/test.py @@ -611,6 +611,6 @@ def test_cmd_snapshot(started_cluster): assert create == "Snapshot creation scheduled." check = send_4lw_cmd(cmd="snpd") - assert (check == "Yes" or check == "No") + assert check == "Yes" or check == "No" finally: destroy_zk_client(zk) From 409fb28d0cd9cfcad87d8eb1e073d77f892260bf Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 23 Oct 2022 01:52:30 +0200 Subject: [PATCH 15/59] Fix build --- src/Common/ProgressIndication.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/ProgressIndication.cpp b/src/Common/ProgressIndication.cpp index f1abd20a6c8..b049edcdcf7 100644 --- a/src/Common/ProgressIndication.cpp +++ b/src/Common/ProgressIndication.cpp @@ -152,7 +152,7 @@ void ProgressIndication::writeProgress(WriteBufferFromFileDescriptor & message) const char * indicator = indicators[increment % 8]; - size_t terminal_width = getTerminalWidth(message.getFD()); + size_t terminal_width = getTerminalWidth(); if (!written_progress_chars) { From c386898268f580336aa6710e91dc43cbf5607383 Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 23 Oct 2022 04:46:07 +0200 Subject: [PATCH 16/59] Fix error --- src/Client/ClientBase.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index fd8aec6973f..1c0251e7671 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -415,7 +415,7 @@ void ClientBase::onData(Block & block, ASTPtr parsed_query) return; /// If results are written INTO OUTFILE, we can avoid clearing progress to avoid flicker. - if (need_render_progress && (stdout_is_a_tty || is_interactive) && (!select_into_file || select_into_file_and_stdout)) + if (need_render_progress && tty_buf && (!select_into_file || select_into_file_and_stdout)) progress_indication.clearProgressOutput(*tty_buf); try @@ -433,7 +433,7 @@ void ClientBase::onData(Block & block, ASTPtr parsed_query) output_format->flush(); /// Restore progress bar after data block. - if (need_render_progress && (stdout_is_a_tty || is_interactive)) + if (need_render_progress && tty_buf) { if (select_into_file && !select_into_file_and_stdout) std::cerr << "\r"; @@ -445,7 +445,7 @@ void ClientBase::onData(Block & block, ASTPtr parsed_query) void ClientBase::onLogData(Block & block) { initLogsOutputStream(); - if (need_render_progress) + if (need_render_progress && tty_buf) progress_indication.clearProgressOutput(*tty_buf); logs_out_stream->writeLogs(block); logs_out_stream->flush(); @@ -989,14 +989,14 @@ void ClientBase::onProgress(const Progress & value) if (output_format) output_format->onProgress(value); - if (need_render_progress) + if (need_render_progress && tty_buf) progress_indication.writeProgress(*tty_buf); } void ClientBase::onEndOfStream() { - if (need_render_progress) + if (need_render_progress && tty_buf) progress_indication.clearProgressOutput(*tty_buf); if (output_format) @@ -1048,7 +1048,7 @@ void ClientBase::onProfileEvents(Block & block) } progress_indication.updateThreadEventData(thread_times); - if (need_render_progress) + if (need_render_progress && tty_buf) progress_indication.writeProgress(*tty_buf); if (profile_events.print) @@ -1056,7 +1056,7 @@ void ClientBase::onProfileEvents(Block & block) if (profile_events.watch.elapsedMilliseconds() >= profile_events.delay_ms) { initLogsOutputStream(); - if (need_render_progress) + if (need_render_progress && tty_buf) progress_indication.clearProgressOutput(*tty_buf); logs_out_stream->writeProfileEvents(block); logs_out_stream->flush(); @@ -1231,7 +1231,8 @@ void ClientBase::sendData(Block & sample, const ColumnsDescription & columns_des progress_indication.updateProgress(Progress(file_progress)); /// Set callback to be called on file progress. - progress_indication.setFileProgressCallback(global_context, *tty_buf); + if (tty_buf) + progress_indication.setFileProgressCallback(global_context, *tty_buf); } /// If data fetched from file (maybe compressed file) @@ -1483,7 +1484,7 @@ bool ClientBase::receiveEndOfQuery() void ClientBase::cancelQuery() { connection->sendCancel(); - if (need_render_progress) + if (need_render_progress && tty_buf) progress_indication.clearProgressOutput(*tty_buf); std::cout << "Cancelling query." << std::endl; @@ -1606,7 +1607,7 @@ void ClientBase::processParsedSingleQuery(const String & full_query, const Strin if (profile_events.last_block) { initLogsOutputStream(); - if (need_render_progress) + if (need_render_progress && tty_buf) progress_indication.clearProgressOutput(*tty_buf); logs_out_stream->writeProfileEvents(profile_events.last_block); logs_out_stream->flush(); From 8eb751fbe551aeb8a5af94c0c7e7b4960865645a Mon Sep 17 00:00:00 2001 From: Alexey Milovidov Date: Sun, 23 Oct 2022 05:28:28 +0200 Subject: [PATCH 17/59] Fix error --- src/Client/ClientBase.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Client/ClientBase.cpp b/src/Client/ClientBase.cpp index 1c0251e7671..ba553b07e73 100644 --- a/src/Client/ClientBase.cpp +++ b/src/Client/ClientBase.cpp @@ -1487,7 +1487,9 @@ void ClientBase::cancelQuery() if (need_render_progress && tty_buf) progress_indication.clearProgressOutput(*tty_buf); - std::cout << "Cancelling query." << std::endl; + if (is_interactive) + std::cout << "Cancelling query." << std::endl; + cancelled = true; } From 2f30c817bfb51ae47bb7dde8c15f3f4999d0d924 Mon Sep 17 00:00:00 2001 From: JackyWoo Date: Mon, 24 Oct 2022 17:23:47 +0800 Subject: [PATCH 18/59] little fix --- src/Coordination/KeeperServer.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 87ebea0b4ab..042ab35d709 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -910,10 +910,11 @@ Keeper4LWInfo KeeperServer::getPartiallyFilled4LWInfo() const bool KeeperServer::createSnapshot() { std::lock_guard lock(snapshot_mutex); - if (raft_instance->create_snapshot()) + uint64_t log_idx = raft_instance->create_snapshot(); + if (log_idx != 0) { - last_manual_snapshot_log_idx = raft_instance->get_last_snapshot_idx(); - LOG_INFO(log, "Successfully schedule a keeper snapshot creation task at log index {}", last_manual_snapshot_log_idx); + last_manual_snapshot_log_idx = log_idx; + LOG_INFO(log, "Successfully schedule a keeper snapshot creation task at log index {}", log_idx); return true; } return false; From b5d1c4e6574fbc4916056a3c78ea87aa7c74a3f6 Mon Sep 17 00:00:00 2001 From: JackyWoo Date: Mon, 24 Oct 2022 20:08:58 +0800 Subject: [PATCH 19/59] replace snpd with lgif --- docs/en/operations/clickhouse-keeper.md | 13 +++++++++---- src/Coordination/FourLetterCommand.cpp | 19 ++++++++++++++++--- src/Coordination/FourLetterCommand.h | 17 ++++++++++++----- src/Coordination/Keeper4LWInfo.h | 22 ++++++++++++++++++++++ src/Coordination/KeeperDispatcher.h | 8 ++++---- src/Coordination/KeeperServer.cpp | 24 +++++++++++++----------- src/Coordination/KeeperServer.h | 8 ++------ 7 files changed, 78 insertions(+), 33 deletions(-) diff --git a/docs/en/operations/clickhouse-keeper.md b/docs/en/operations/clickhouse-keeper.md index 66b4685bff5..2ab76e1a1ea 100644 --- a/docs/en/operations/clickhouse-keeper.md +++ b/docs/en/operations/clickhouse-keeper.md @@ -309,16 +309,21 @@ Sessions with Ephemerals (1): /clickhouse/task_queue/ddl ``` -- `csnp`: Schedule a snapshot creation task. Return `Snapshot creation scheduled.` if successfully scheduled or `Fail to scheduled snapshot creation.` if failed. +- `csnp`: Schedule a snapshot creation task. Return `Snapshot creation scheduled with last committed log index xxx.` if successfully scheduled or `Fail to scheduled snapshot creation task.` if failed. ``` -Snapshot creation scheduled. +Snapshot creation scheduled with last committed log index 100. ``` -- `snpd`: Whether the last successfully scheduled snapshot creation is done. Return `Yes` if true or `No` if false. +- `lgif`: Keeper log information. `last_log_idx` : my last log index in log store; `last_log_term` : my last log term; `last_committed_log_idx` : my last committed log index in state machine; `leader_committed_log_idx` : leader's committed log index from my perspective; `target_committed_log_idx` : target log index should be committed to; `last_snapshot_idx` : the largest committed log index in last snapshot. ``` -Yes +last_log_idx : 101 +last_log_term : 1 +last_committed_log_idx : 100 +leader_committed_log_idx : 101 +target_committed_log_idx : 101 +last_snapshot_idx : 50 ``` ## [experimental] Migration from ZooKeeper {#migration-from-zookeeper} diff --git a/src/Coordination/FourLetterCommand.cpp b/src/Coordination/FourLetterCommand.cpp index 3d1077ea84c..c5841ce3404 100644 --- a/src/Coordination/FourLetterCommand.cpp +++ b/src/Coordination/FourLetterCommand.cpp @@ -139,6 +139,9 @@ void FourLetterCommandFactory::registerCommands(KeeperDispatcher & keeper_dispat FourLetterCommandPtr create_snapshot_command = std::make_shared(keeper_dispatcher); factory.registerCommand(create_snapshot_command); + FourLetterCommandPtr log_info_command = std::make_shared(keeper_dispatcher); + factory.registerCommand(log_info_command); + factory.initializeAllowList(keeper_dispatcher); factory.setInitialize(true); } @@ -477,12 +480,22 @@ String ApiVersionCommand::run() String CreateSnapshotCommand::run() { - return keeper_dispatcher.createSnapshot() ? "Snapshot creation scheduled." : "Fail to scheduled snapshot creation task."; + auto log_index = keeper_dispatcher.createSnapshot(); + return log_index > 0 ? "Snapshot creation scheduled with last committed log index " + std::to_string(log_index) + "." + : "Fail to scheduled snapshot creation task."; } -String CheckSnapshotDoneCommand::run() +String LogInfoCommand::run() { - return keeper_dispatcher.snapshotDone() ? "Snapshot creation done." : "Fail to scheduled snapshot creation task."; + KeeperLogInfo log_info = keeper_dispatcher.getKeeperLogInfo(); + StringBuffer ret; + print(ret, "last_log_idx", log_info.last_log_idx); + print(ret, "last_log_term", log_info.last_log_term); + print(ret, "last_committed_log_idx", log_info.last_committed_log_idx); + print(ret, "leader_committed_log_idx", log_info.leader_committed_log_idx); + print(ret, "target_committed_log_idx", log_info.target_committed_log_idx); + print(ret, "last_snapshot_idx", log_info.last_snapshot_idx); + return ret.str(); } } diff --git a/src/Coordination/FourLetterCommand.h b/src/Coordination/FourLetterCommand.h index 28f1d7f153f..99005bab987 100644 --- a/src/Coordination/FourLetterCommand.h +++ b/src/Coordination/FourLetterCommand.h @@ -341,17 +341,24 @@ struct CreateSnapshotCommand : public IFourLetterCommand ~CreateSnapshotCommand() override = default; }; -/// Check whether last manual snapshot done -struct CheckSnapshotDoneCommand : public IFourLetterCommand +/** Raft log information: + * last_log_idx : 101 + * last_log_term : 1 + * last_committed_idx : 100 + * leader_committed_log_idx : 101 + * target_committed_log_idx : 101 + * last_snapshot_idx : 50 + */ +struct LogInfoCommand : public IFourLetterCommand { - explicit CheckSnapshotDoneCommand(KeeperDispatcher & keeper_dispatcher_) + explicit LogInfoCommand(KeeperDispatcher & keeper_dispatcher_) : IFourLetterCommand(keeper_dispatcher_) { } - String name() override { return "snpd"; } + String name() override { return "lgif"; } String run() override; - ~CheckSnapshotDoneCommand() override = default; + ~LogInfoCommand() override = default; }; } diff --git a/src/Coordination/Keeper4LWInfo.h b/src/Coordination/Keeper4LWInfo.h index 7d90152611e..dbddadaefbf 100644 --- a/src/Coordination/Keeper4LWInfo.h +++ b/src/Coordination/Keeper4LWInfo.h @@ -47,4 +47,26 @@ struct Keeper4LWInfo } }; +/// Keeper log information for 4lw commands +struct KeeperLogInfo +{ + /// My last log index in log store. + uint64_t last_log_idx; + + /// My last log term. + uint64_t last_log_term; + + /// My last committed log index in state machine. + uint64_t last_committed_log_idx; + + /// Leader's committed log index from my perspective. + uint64_t leader_committed_log_idx; + + /// Target log index should be committed to. + uint64_t target_committed_log_idx; + + /// The largest committed log index in last snapshot. + uint64_t last_snapshot_idx; +}; + } diff --git a/src/Coordination/KeeperDispatcher.h b/src/Coordination/KeeperDispatcher.h index 48681957c13..0126bf8a1e5 100644 --- a/src/Coordination/KeeperDispatcher.h +++ b/src/Coordination/KeeperDispatcher.h @@ -204,16 +204,16 @@ public: keeper_stats.reset(); } - /// Create snapshot manually - bool createSnapshot() + /// Create snapshot manually, return the last committed log index in the snapshot + uint64_t createSnapshot() { return server->createSnapshot(); } /// Whether the last manually created snapshot is done - bool snapshotDone() + KeeperLogInfo getKeeperLogInfo() { - return server->snapshotDone(); + return server->getKeeperLogInfo(); } }; diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 042ab35d709..38070938fc5 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -907,23 +907,25 @@ Keeper4LWInfo KeeperServer::getPartiallyFilled4LWInfo() const return result; } -bool KeeperServer::createSnapshot() +uint64_t KeeperServer::createSnapshot() { - std::lock_guard lock(snapshot_mutex); uint64_t log_idx = raft_instance->create_snapshot(); if (log_idx != 0) - { - last_manual_snapshot_log_idx = log_idx; - LOG_INFO(log, "Successfully schedule a keeper snapshot creation task at log index {}", log_idx); - return true; - } - return false; + LOG_INFO(log, "Snapshot creation scheduled with last committed log index {}.", log_idx); + else + LOG_WARNING(log, "Fail to scheduled snapshot creation task."); + return log_idx; } -bool KeeperServer::snapshotDone() +KeeperLogInfo KeeperServer::getKeeperLogInfo() { - std::lock_guard lock(snapshot_mutex); - return last_manual_snapshot_log_idx != 0 && last_manual_snapshot_log_idx == raft_instance->get_last_snapshot_idx(); + KeeperLogInfo log_info; + log_info.last_log_idx = raft_instance->get_last_log_idx(); + log_info.last_log_term = raft_instance->get_last_log_term(); + log_info.leader_committed_log_idx = raft_instance->get_leader_committed_log_idx(); + log_info.target_committed_log_idx = raft_instance->get_target_committed_log_idx(); + log_info.last_snapshot_idx = raft_instance->get_last_snapshot_idx(); + return log_info; } } diff --git a/src/Coordination/KeeperServer.h b/src/Coordination/KeeperServer.h index 11e3b75d127..192c8f470b1 100644 --- a/src/Coordination/KeeperServer.h +++ b/src/Coordination/KeeperServer.h @@ -66,10 +66,6 @@ private: const bool create_snapshot_on_exit; - /// Used to check whether the previous manually created snapshot complete. - uint64_t last_manual_snapshot_log_idx; - std::mutex snapshot_mutex; - public: KeeperServer( const KeeperConfigurationAndSettingsPtr & settings_, @@ -136,9 +132,9 @@ public: /// Return true if update was successfully received. bool waitConfigurationUpdate(const ConfigUpdateAction & task); - bool createSnapshot(); + uint64_t createSnapshot(); - bool snapshotDone(); + KeeperLogInfo getKeeperLogInfo(); }; } From 9a36a509fe1272b53698eb0707249e3593fc3088 Mon Sep 17 00:00:00 2001 From: JackyWoo Date: Tue, 25 Oct 2022 17:15:49 +0800 Subject: [PATCH 20/59] fix test --- docs/en/operations/clickhouse-keeper.md | 20 +++++----- src/Coordination/FourLetterCommand.cpp | 25 ++++++++---- src/Coordination/FourLetterCommand.h | 15 ++++--- src/Coordination/Keeper4LWInfo.h | 6 +++ src/Coordination/KeeperServer.cpp | 4 +- .../test_keeper_four_word_command/test.py | 39 ++++++++++++++++--- 6 files changed, 79 insertions(+), 30 deletions(-) diff --git a/docs/en/operations/clickhouse-keeper.md b/docs/en/operations/clickhouse-keeper.md index 2ab76e1a1ea..8eee97ed275 100644 --- a/docs/en/operations/clickhouse-keeper.md +++ b/docs/en/operations/clickhouse-keeper.md @@ -309,21 +309,23 @@ Sessions with Ephemerals (1): /clickhouse/task_queue/ddl ``` -- `csnp`: Schedule a snapshot creation task. Return `Snapshot creation scheduled with last committed log index xxx.` if successfully scheduled or `Fail to scheduled snapshot creation task.` if failed. +- `csnp`: Schedule a snapshot creation task. Return the last committed log index of the scheduled snapshot if successfully scheduled or `Fail to scheduled snapshot creation task.` if failed. ``` -Snapshot creation scheduled with last committed log index 100. +100 ``` -- `lgif`: Keeper log information. `last_log_idx` : my last log index in log store; `last_log_term` : my last log term; `last_committed_log_idx` : my last committed log index in state machine; `leader_committed_log_idx` : leader's committed log index from my perspective; `target_committed_log_idx` : target log index should be committed to; `last_snapshot_idx` : the largest committed log index in last snapshot. +- `lgif`: Keeper log information. `first_log_idx` : my first log index in log store; `first_log_term` : my first log term; `last_log_idx` : my last log index in log store; `last_log_term` : my last log term; `last_committed_log_idx` : my last committed log index in state machine; `leader_committed_log_idx` : leader's committed log index from my perspective; `target_committed_log_idx` : target log index should be committed to; `last_snapshot_idx` : the largest committed log index in last snapshot. ``` -last_log_idx : 101 -last_log_term : 1 -last_committed_log_idx : 100 -leader_committed_log_idx : 101 -target_committed_log_idx : 101 -last_snapshot_idx : 50 +first_log_idx 1 +first_log_term 1 +last_log_idx 101 +last_log_term 1 +last_committed_log_idx 100 +leader_committed_log_idx 101 +target_committed_log_idx 101 +last_snapshot_idx 50 ``` ## [experimental] Migration from ZooKeeper {#migration-from-zookeeper} diff --git a/src/Coordination/FourLetterCommand.cpp b/src/Coordination/FourLetterCommand.cpp index c5841ce3404..402270640d2 100644 --- a/src/Coordination/FourLetterCommand.cpp +++ b/src/Coordination/FourLetterCommand.cpp @@ -481,20 +481,29 @@ String ApiVersionCommand::run() String CreateSnapshotCommand::run() { auto log_index = keeper_dispatcher.createSnapshot(); - return log_index > 0 ? "Snapshot creation scheduled with last committed log index " + std::to_string(log_index) + "." - : "Fail to scheduled snapshot creation task."; + return log_index > 0 ? std::to_string(log_index) : "Fail to scheduled snapshot creation task."; } String LogInfoCommand::run() { KeeperLogInfo log_info = keeper_dispatcher.getKeeperLogInfo(); StringBuffer ret; - print(ret, "last_log_idx", log_info.last_log_idx); - print(ret, "last_log_term", log_info.last_log_term); - print(ret, "last_committed_log_idx", log_info.last_committed_log_idx); - print(ret, "leader_committed_log_idx", log_info.leader_committed_log_idx); - print(ret, "target_committed_log_idx", log_info.target_committed_log_idx); - print(ret, "last_snapshot_idx", log_info.last_snapshot_idx); + + auto append = [&ret] (String key, uint64_t value) -> void + { + writeText(key, ret); + writeText('\t', ret); + writeText(std::to_string(value), ret); + writeText('\n', ret); + }; + append("first_log_idx", log_info.first_log_idx); + append("first_log_term", log_info.first_log_idx); + append("last_log_idx", log_info.last_log_idx); + append("last_log_term", log_info.last_log_term); + append("last_committed_log_idx", log_info.last_committed_log_idx); + append("leader_committed_log_idx", log_info.leader_committed_log_idx); + append("target_committed_log_idx", log_info.target_committed_log_idx); + append("last_snapshot_idx", log_info.last_snapshot_idx); return ret.str(); } diff --git a/src/Coordination/FourLetterCommand.h b/src/Coordination/FourLetterCommand.h index 99005bab987..a8801474bb0 100644 --- a/src/Coordination/FourLetterCommand.h +++ b/src/Coordination/FourLetterCommand.h @@ -17,6 +17,7 @@ using FourLetterCommandPtr = std::shared_ptr; /// Just like zookeeper Four Letter Words commands, CH Keeper responds to a small set of commands. /// Each command is composed of four letters, these commands are useful to monitor and issue system problems. /// The feature is based on Zookeeper 3.5.9, details is in https://zookeeper.apache.org/doc/r3.5.9/zookeeperAdmin.html#sc_zkCommands. +/// Also we add some additional commands such as csnp, lgif etc. struct IFourLetterCommand { public: @@ -342,12 +343,14 @@ struct CreateSnapshotCommand : public IFourLetterCommand }; /** Raft log information: - * last_log_idx : 101 - * last_log_term : 1 - * last_committed_idx : 100 - * leader_committed_log_idx : 101 - * target_committed_log_idx : 101 - * last_snapshot_idx : 50 + * first_log_idx 1 + * first_log_term 1 + * last_log_idx 101 + * last_log_term 1 + * last_committed_idx 100 + * leader_committed_log_idx 101 + * target_committed_log_idx 101 + * last_snapshot_idx 50 */ struct LogInfoCommand : public IFourLetterCommand { diff --git a/src/Coordination/Keeper4LWInfo.h b/src/Coordination/Keeper4LWInfo.h index dbddadaefbf..105478457cc 100644 --- a/src/Coordination/Keeper4LWInfo.h +++ b/src/Coordination/Keeper4LWInfo.h @@ -50,6 +50,12 @@ struct Keeper4LWInfo /// Keeper log information for 4lw commands struct KeeperLogInfo { + /// My first log index in log store. + uint64_t first_log_idx; + + /// My first log term. + uint64_t first_log_term; + /// My last log index in log store. uint64_t last_log_idx; diff --git a/src/Coordination/KeeperServer.cpp b/src/Coordination/KeeperServer.cpp index 38070938fc5..bea69ea0ba8 100644 --- a/src/Coordination/KeeperServer.cpp +++ b/src/Coordination/KeeperServer.cpp @@ -114,7 +114,6 @@ KeeperServer::KeeperServer( , is_recovering(config.getBool("keeper_server.force_recovery", false)) , keeper_context{std::make_shared()} , create_snapshot_on_exit(config.getBool("keeper_server.create_snapshot_on_exit", true)) - , last_manual_snapshot_log_idx(0) { if (coordination_settings->quorum_reads) LOG_WARNING(log, "Quorum reads enabled, Keeper will work slower."); @@ -920,8 +919,11 @@ uint64_t KeeperServer::createSnapshot() KeeperLogInfo KeeperServer::getKeeperLogInfo() { KeeperLogInfo log_info; + log_info.first_log_idx = state_manager->load_log_store()->start_index(); + log_info.first_log_term = state_manager->load_log_store()->term_at(log_info.first_log_idx); log_info.last_log_idx = raft_instance->get_last_log_idx(); log_info.last_log_term = raft_instance->get_last_log_term(); + log_info.last_committed_log_idx = raft_instance->get_committed_log_idx(); log_info.leader_committed_log_idx = raft_instance->get_leader_committed_log_idx(); log_info.target_committed_log_idx = raft_instance->get_target_committed_log_idx(); log_info.last_snapshot_idx = raft_instance->get_last_snapshot_idx(); diff --git a/tests/integration/test_keeper_four_word_command/test.py b/tests/integration/test_keeper_four_word_command/test.py index 4949d6f70de..4559904f8b7 100644 --- a/tests/integration/test_keeper_four_word_command/test.py +++ b/tests/integration/test_keeper_four_word_command/test.py @@ -598,19 +598,46 @@ def test_cmd_wchp(started_cluster): destroy_zk_client(zk) -def test_cmd_snapshot(started_cluster): +def test_cmd_csnp(started_cluster): + zk = None + try: + wait_nodes() + zk = get_fake_zk(node1.name, timeout=30.0) + data = keeper_utils.send_4lw_cmd(cluster, node1, cmd="csnp") + try: + int(data) + assert True + except ValueError: + assert False + finally: + destroy_zk_client(zk) + + +def test_cmd_lgif(started_cluster): zk = None try: wait_nodes() clear_znodes() - reset_node_stats() zk = get_fake_zk(node1.name, timeout=30.0) + do_some_action(zk, create_cnt=100) - create = send_4lw_cmd(cmd="csnp") - assert create == "Snapshot creation scheduled." + data = keeper_utils.send_4lw_cmd(cluster, node1, cmd="lgif") + print(data) + reader = csv.reader(data.split("\n"), delimiter="\t") + result = {} - check = send_4lw_cmd(cmd="snpd") - assert check == "Yes" or check == "No" + for row in reader: + if len(row) != 0: + result[row[0]] = row[1] + + assert int(result["first_log_idx"]) == 1 + assert int(result["first_log_term"]) == 1 + assert int(result["last_log_idx"]) >= 1 + assert int(result["last_log_term"]) == 1 + assert int(result["last_committed_log_idx"]) >= 1 + assert int(result["leader_committed_log_idx"]) >= 1 + assert int(result["target_committed_log_idx"]) >= 1 + assert int(result["last_snapshot_idx"]) >= 1 finally: destroy_zk_client(zk) From c7a0ebeb05ba81f4259b2c5910ac88c10520cafc Mon Sep 17 00:00:00 2001 From: JackyWoo Date: Tue, 25 Oct 2022 17:46:24 +0800 Subject: [PATCH 21/59] little fix --- docs/en/operations/clickhouse-keeper.md | 2 +- src/Coordination/KeeperDispatcher.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/operations/clickhouse-keeper.md b/docs/en/operations/clickhouse-keeper.md index 8eee97ed275..269e18023df 100644 --- a/docs/en/operations/clickhouse-keeper.md +++ b/docs/en/operations/clickhouse-keeper.md @@ -309,7 +309,7 @@ Sessions with Ephemerals (1): /clickhouse/task_queue/ddl ``` -- `csnp`: Schedule a snapshot creation task. Return the last committed log index of the scheduled snapshot if successfully scheduled or `Fail to scheduled snapshot creation task.` if failed. +- `csnp`: Schedule a snapshot creation task. Return the last committed log index of the scheduled snapshot if success or `Fail to scheduled snapshot creation task.` if failed. ``` 100 diff --git a/src/Coordination/KeeperDispatcher.h b/src/Coordination/KeeperDispatcher.h index 0126bf8a1e5..84345ca1ff5 100644 --- a/src/Coordination/KeeperDispatcher.h +++ b/src/Coordination/KeeperDispatcher.h @@ -210,7 +210,7 @@ public: return server->createSnapshot(); } - /// Whether the last manually created snapshot is done + /// Get Raft information KeeperLogInfo getKeeperLogInfo() { return server->getKeeperLogInfo(); From 611c2e2bd75614e8eeb9d10933d76d6c5cd9f89b Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Tue, 25 Oct 2022 13:34:34 +0000 Subject: [PATCH 22/59] Support for optimizing old parts for entire partition only --- .../MergeTree/MergeTreeDataMergerMutator.cpp | 27 ++++++++++++++++++- src/Storages/MergeTree/MergeTreeSettings.h | 1 + .../test.py | 24 ++++++++++++----- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index b0ef1522685..27000796343 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -214,6 +214,14 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectPartsToMerge( /// Previous part only in boundaries of partition frame const MergeTreeData::DataPartPtr * prev_part = nullptr; + /// collect min_age for each partition while iterating parts + struct PartitionInfo + { + time_t min_age{std::numeric_limits::max()}; + }; + + std::unordered_map partitions_info; + size_t parts_selected_precondition = 0; for (const MergeTreeData::DataPartPtr & part : data_parts) { @@ -277,6 +285,9 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectPartsToMerge( part_info.compression_codec_desc = part->default_codec->getFullCodecDesc(); part_info.shall_participate_in_merges = has_volumes_with_disabled_merges ? part->shallParticipateInMerges(storage_policy) : true; + auto & partition_info = partitions_info[partition_id]; + partition_info.min_age = std::min(partition_info.min_age, part_info.age); + ++parts_selected_precondition; parts_ranges.back().emplace_back(part_info); @@ -333,7 +344,8 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectPartsToMerge( SimpleMergeSelector::Settings merge_settings; /// Override value from table settings merge_settings.max_parts_to_merge_at_once = data_settings->max_parts_to_merge_at_once; - merge_settings.min_age_to_force_merge = data_settings->min_age_to_force_merge_seconds; + if (!data_settings->min_age_to_force_merge_on_partition_only) + merge_settings.min_age_to_force_merge = data_settings->min_age_to_force_merge_seconds; if (aggressive) merge_settings.base = 1; @@ -347,6 +359,19 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectPartsToMerge( if (parts_to_merge.empty()) { + if (data_settings->min_age_to_force_merge_on_partition_only && data_settings->min_age_to_force_merge_seconds) + { + auto best_partition_it = std::max_element( + partitions_info.begin(), + partitions_info.end(), + [](const auto & e1, const auto & e2) { return e1.second.min_age > e2.second.min_age; }); + + if (best_partition_it != partitions_info.end() + && static_cast(best_partition_it->second.min_age) >= data_settings->min_age_to_force_merge_seconds) + return selectAllPartsToMergeWithinPartition( + future_part, can_merge_callback, best_partition_it->first, true, metadata_snapshot, txn, out_disable_reason); + } + if (out_disable_reason) *out_disable_reason = "There is no need to merge parts according to merge selector algorithm"; return SelectPartsDecision::CANNOT_SELECT; diff --git a/src/Storages/MergeTree/MergeTreeSettings.h b/src/Storages/MergeTree/MergeTreeSettings.h index 3fecb85f484..844c1ddbfe5 100644 --- a/src/Storages/MergeTree/MergeTreeSettings.h +++ b/src/Storages/MergeTree/MergeTreeSettings.h @@ -63,6 +63,7 @@ struct Settings; M(UInt64, merge_tree_clear_old_parts_interval_seconds, 1, "The period of executing the clear old parts operation in background.", 0) \ M(UInt64, merge_tree_clear_old_broken_detached_parts_ttl_timeout_seconds, 1ULL * 3600 * 24 * 30, "Remove old broken detached parts in the background if they remained intouched for a specified by this setting period of time.", 0) \ M(UInt64, min_age_to_force_merge_seconds, 0, "If all parts in a certain range are older than this value, range will be always eligible for merging. Set to 0 to disable.", 0) \ + M(Bool, min_age_to_force_merge_on_partition_only, false, "Whether min_age_to_force_merge_seconds should be applied only on the entire partition and not on subset.", false) \ M(UInt64, merge_tree_enable_clear_old_broken_detached, false, "Enable clearing old broken detached parts operation in background.", 0) \ M(Bool, remove_rolled_back_parts_immediately, 1, "Setting for an incomplete experimental feature.", 0) \ \ diff --git a/tests/integration/test_merge_tree_optimize_old_parts/test.py b/tests/integration/test_merge_tree_optimize_old_parts/test.py index 7b386eba2c4..87e0ecd8108 100644 --- a/tests/integration/test_merge_tree_optimize_old_parts/test.py +++ b/tests/integration/test_merge_tree_optimize_old_parts/test.py @@ -13,7 +13,7 @@ node = cluster.add_instance( @pytest.fixture(scope="module") -def start_cluster(): +def started_cluster(): try: cluster.start() @@ -42,7 +42,7 @@ def check_expected_part_number(seconds, table_name, expected): assert ok -def test_without_force_merge_old_parts(start_cluster): +def test_without_force_merge_old_parts(started_cluster): node.query( "CREATE TABLE test_without_merge (i Int64) ENGINE = MergeTree ORDER BY i;" ) @@ -60,13 +60,18 @@ def test_without_force_merge_old_parts(start_cluster): node.query("DROP TABLE test_without_merge;") -def test_force_merge_old_parts(start_cluster): +@pytest.mark.parametrize("partition_only", ["True", "False"]) +def test_force_merge_old_parts(started_cluster, partition_only): node.query( - "CREATE TABLE test_with_merge (i Int64) ENGINE = MergeTree ORDER BY i SETTINGS min_age_to_force_merge_seconds=5;" + "CREATE TABLE test_with_merge (i Int64) " + "ENGINE = MergeTree " + "ORDER BY i " + f"SETTINGS min_age_to_force_merge_seconds=5, min_age_to_force_merge_on_partition_only={partition_only};" ) node.query("INSERT INTO test_with_merge SELECT 1") node.query("INSERT INTO test_with_merge SELECT 2") node.query("INSERT INTO test_with_merge SELECT 3") + assert get_part_number("test_with_merge") == TSV("""3\n""") expected = TSV("""1\n""") check_expected_part_number(10, "test_with_merge", expected) @@ -74,15 +79,20 @@ def test_force_merge_old_parts(start_cluster): node.query("DROP TABLE test_with_merge;") -def test_force_merge_old_parts_replicated_merge_tree(start_cluster): +@pytest.mark.parametrize("partition_only", ["True", "False"]) +def test_force_merge_old_parts_replicated_merge_tree(started_cluster, partition_only): node.query( - "CREATE TABLE test_replicated (i Int64) ENGINE = ReplicatedMergeTree('/clickhouse/testing/test', 'node') ORDER BY i SETTINGS min_age_to_force_merge_seconds=5;" + "CREATE TABLE test_replicated (i Int64) " + "ENGINE = ReplicatedMergeTree('/clickhouse/testing/test', 'node') " + "ORDER BY i " + f"SETTINGS min_age_to_force_merge_seconds=5, min_age_to_force_merge_on_partition_only={partition_only};" ) node.query("INSERT INTO test_replicated SELECT 1") node.query("INSERT INTO test_replicated SELECT 2") node.query("INSERT INTO test_replicated SELECT 3") + assert get_part_number("test_replicated") == TSV("""3\n""") expected = TSV("""1\n""") check_expected_part_number(10, "test_replicated", expected) - node.query("DROP TABLE test_replicated;") + node.query("DROP TABLE test_replicated SYNC;") From 97aaebfa1808e9b90258a78de2927c97bae05feb Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Wed, 26 Oct 2022 10:06:56 +0000 Subject: [PATCH 23/59] Address PR comments --- .../MergeTree/MergeTreeDataMergerMutator.cpp | 5 +- .../__init__.py | 0 .../configs/zookeeper_config.xml | 8 -- .../test.py | 98 ------------------- .../02473_optimize_old_parts.reference | 10 ++ .../0_stateless/02473_optimize_old_parts.sql | 67 +++++++++++++ 6 files changed, 80 insertions(+), 108 deletions(-) delete mode 100644 tests/integration/test_merge_tree_optimize_old_parts/__init__.py delete mode 100644 tests/integration/test_merge_tree_optimize_old_parts/configs/zookeeper_config.xml delete mode 100644 tests/integration/test_merge_tree_optimize_old_parts/test.py create mode 100644 tests/queries/0_stateless/02473_optimize_old_parts.reference create mode 100644 tests/queries/0_stateless/02473_optimize_old_parts.sql diff --git a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp index 27000796343..0f44d1a7da3 100644 --- a/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp +++ b/src/Storages/MergeTree/MergeTreeDataMergerMutator.cpp @@ -366,8 +366,9 @@ SelectPartsDecision MergeTreeDataMergerMutator::selectPartsToMerge( partitions_info.end(), [](const auto & e1, const auto & e2) { return e1.second.min_age > e2.second.min_age; }); - if (best_partition_it != partitions_info.end() - && static_cast(best_partition_it->second.min_age) >= data_settings->min_age_to_force_merge_seconds) + assert(best_partition_it != partitions_info.end()); + + if (static_cast(best_partition_it->second.min_age) >= data_settings->min_age_to_force_merge_seconds) return selectAllPartsToMergeWithinPartition( future_part, can_merge_callback, best_partition_it->first, true, metadata_snapshot, txn, out_disable_reason); } diff --git a/tests/integration/test_merge_tree_optimize_old_parts/__init__.py b/tests/integration/test_merge_tree_optimize_old_parts/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/integration/test_merge_tree_optimize_old_parts/configs/zookeeper_config.xml b/tests/integration/test_merge_tree_optimize_old_parts/configs/zookeeper_config.xml deleted file mode 100644 index 18412349228..00000000000 --- a/tests/integration/test_merge_tree_optimize_old_parts/configs/zookeeper_config.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - zoo1 - 2181 - - - diff --git a/tests/integration/test_merge_tree_optimize_old_parts/test.py b/tests/integration/test_merge_tree_optimize_old_parts/test.py deleted file mode 100644 index 87e0ecd8108..00000000000 --- a/tests/integration/test_merge_tree_optimize_old_parts/test.py +++ /dev/null @@ -1,98 +0,0 @@ -import pytest -import time -from helpers.client import QueryRuntimeException -from helpers.cluster import ClickHouseCluster -from helpers.test_tools import TSV - -cluster = ClickHouseCluster(__file__) -node = cluster.add_instance( - "node", - main_configs=["configs/zookeeper_config.xml"], - with_zookeeper=True, -) - - -@pytest.fixture(scope="module") -def started_cluster(): - try: - cluster.start() - - yield cluster - finally: - cluster.shutdown() - - -def get_part_number(table_name): - return TSV( - node.query( - f"SELECT count(*) FROM system.parts where table='{table_name}' and active=1" - ) - ) - - -def check_expected_part_number(seconds, table_name, expected): - ok = False - for i in range(int(seconds) * 2): - result = get_part_number(table_name) - if result == expected: - ok = True - break - else: - time.sleep(1) - assert ok - - -def test_without_force_merge_old_parts(started_cluster): - node.query( - "CREATE TABLE test_without_merge (i Int64) ENGINE = MergeTree ORDER BY i;" - ) - node.query("INSERT INTO test_without_merge SELECT 1") - node.query("INSERT INTO test_without_merge SELECT 2") - node.query("INSERT INTO test_without_merge SELECT 3") - - expected = TSV("""3\n""") - # verify that the parts don't get merged - for i in range(10): - if get_part_number("test_without_merge") != expected: - assert False - time.sleep(1) - - node.query("DROP TABLE test_without_merge;") - - -@pytest.mark.parametrize("partition_only", ["True", "False"]) -def test_force_merge_old_parts(started_cluster, partition_only): - node.query( - "CREATE TABLE test_with_merge (i Int64) " - "ENGINE = MergeTree " - "ORDER BY i " - f"SETTINGS min_age_to_force_merge_seconds=5, min_age_to_force_merge_on_partition_only={partition_only};" - ) - node.query("INSERT INTO test_with_merge SELECT 1") - node.query("INSERT INTO test_with_merge SELECT 2") - node.query("INSERT INTO test_with_merge SELECT 3") - assert get_part_number("test_with_merge") == TSV("""3\n""") - - expected = TSV("""1\n""") - check_expected_part_number(10, "test_with_merge", expected) - - node.query("DROP TABLE test_with_merge;") - - -@pytest.mark.parametrize("partition_only", ["True", "False"]) -def test_force_merge_old_parts_replicated_merge_tree(started_cluster, partition_only): - node.query( - "CREATE TABLE test_replicated (i Int64) " - "ENGINE = ReplicatedMergeTree('/clickhouse/testing/test', 'node') " - "ORDER BY i " - f"SETTINGS min_age_to_force_merge_seconds=5, min_age_to_force_merge_on_partition_only={partition_only};" - ) - node.query("INSERT INTO test_replicated SELECT 1") - node.query("INSERT INTO test_replicated SELECT 2") - node.query("INSERT INTO test_replicated SELECT 3") - assert get_part_number("test_replicated") == TSV("""3\n""") - - expected = TSV("""1\n""") - check_expected_part_number(10, "test_replicated", expected) - - node.query("DROP TABLE test_replicated SYNC;") diff --git a/tests/queries/0_stateless/02473_optimize_old_parts.reference b/tests/queries/0_stateless/02473_optimize_old_parts.reference new file mode 100644 index 00000000000..6767887ba86 --- /dev/null +++ b/tests/queries/0_stateless/02473_optimize_old_parts.reference @@ -0,0 +1,10 @@ +Without merge +6 +With merge any part range +1 +With merge partition only +1 +With merge replicated any part range +1 +With merge replicated partition only +1 diff --git a/tests/queries/0_stateless/02473_optimize_old_parts.sql b/tests/queries/0_stateless/02473_optimize_old_parts.sql new file mode 100644 index 00000000000..545bd58dddc --- /dev/null +++ b/tests/queries/0_stateless/02473_optimize_old_parts.sql @@ -0,0 +1,67 @@ +DROP TABLE IF EXISTS test_without_merge; +DROP TABLE IF EXISTS test_with_merge; +DROP TABLE IF EXISTS test_replicated; + +SELECT 'Without merge'; + +CREATE TABLE test_without_merge (i Int64) ENGINE = MergeTree ORDER BY i; +INSERT INTO test_without_merge SELECT 1; +INSERT INTO test_without_merge SELECT 2; +INSERT INTO test_without_merge SELECT 3; + +SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT count(*) FROM system.parts where table='test_without_merge' and active=1; + +DROP TABLE test_without_merge; + +SELECT 'With merge any part range'; + +CREATE TABLE test_with_merge (i Int64) ENGINE = MergeTree ORDER BY i +SETTINGS min_age_to_force_merge_seconds=3, min_age_to_force_merge_on_partition_only=false; +INSERT INTO test_with_merge SELECT 1; +INSERT INTO test_with_merge SELECT 2; +INSERT INTO test_with_merge SELECT 3; + +SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT count(*) FROM system.parts where table='test_with_merge' and active=1; + +DROP TABLE test_with_merge; + +SELECT 'With merge partition only'; + +CREATE TABLE test_with_merge (i Int64) ENGINE = MergeTree ORDER BY i +SETTINGS min_age_to_force_merge_seconds=3, min_age_to_force_merge_on_partition_only=true; +INSERT INTO test_with_merge SELECT 1; +INSERT INTO test_with_merge SELECT 2; +INSERT INTO test_with_merge SELECT 3; + +SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT count(*) FROM system.parts where table='test_with_merge' and active=1; + +DROP TABLE test_with_merge; + +SELECT 'With merge replicated any part range'; + +CREATE TABLE test_replicated (i Int64) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{database}/test02473', 'node') ORDER BY i +SETTINGS min_age_to_force_merge_seconds=3, min_age_to_force_merge_on_partition_only=false; +INSERT INTO test_replicated SELECT 1; +INSERT INTO test_replicated SELECT 2; +INSERT INTO test_replicated SELECT 3; + +SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT count(*) FROM system.parts where table='test_replicated' and active=1; + +DROP TABLE test_replicated; + +SELECT 'With merge replicated partition only'; + +CREATE TABLE test_replicated (i Int64) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{database}/test02473_partition_only', 'node') ORDER BY i +SETTINGS min_age_to_force_merge_seconds=3, min_age_to_force_merge_on_partition_only=true; +INSERT INTO test_replicated SELECT 1; +INSERT INTO test_replicated SELECT 2; +INSERT INTO test_replicated SELECT 3; + +SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT count(*) FROM system.parts where table='test_replicated' and active=1; + +DROP TABLE test_replicated; From 598b45f1ec2194183ec20418d7b7caf7192429be Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Thu, 27 Oct 2022 08:06:39 +0000 Subject: [PATCH 24/59] Add test for partition only and new parts --- .../02473_optimize_old_parts.reference | 2 ++ .../0_stateless/02473_optimize_old_parts.sql | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/tests/queries/0_stateless/02473_optimize_old_parts.reference b/tests/queries/0_stateless/02473_optimize_old_parts.reference index e80812bddcd..9002d73ff27 100644 --- a/tests/queries/0_stateless/02473_optimize_old_parts.reference +++ b/tests/queries/0_stateless/02473_optimize_old_parts.reference @@ -8,3 +8,5 @@ With merge replicated any part range 1 With merge replicated partition only 1 +With merge partition only and new parts +3 diff --git a/tests/queries/0_stateless/02473_optimize_old_parts.sql b/tests/queries/0_stateless/02473_optimize_old_parts.sql index d673ef22f67..76c1ba73097 100644 --- a/tests/queries/0_stateless/02473_optimize_old_parts.sql +++ b/tests/queries/0_stateless/02473_optimize_old_parts.sql @@ -65,3 +65,21 @@ SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; SELECT count(*) FROM system.parts WHERE database = currentDatabase() AND table='test_replicated' AND active; DROP TABLE test_replicated; + +SELECT 'With merge partition only and new parts'; + +CREATE TABLE test_with_merge (i Int64) ENGINE = MergeTree ORDER BY i +SETTINGS min_age_to_force_merge_seconds=3, min_age_to_force_merge_on_partition_only=true; +SYSTEM STOP MERGES test_with_merge; +-- These three parts will have min_age=6 at the time of merge +INSERT INTO test_with_merge SELECT 1; +INSERT INTO test_with_merge SELECT 2; +SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +-- These three parts will have min_age=0 at the time of merge +-- and so, nothing will be merged. +INSERT INTO test_with_merge SELECT 3; +SYSTEM START MERGES test_with_merge; + +SELECT count(*) FROM system.parts WHERE database = currentDatabase() AND table='test_with_merge' AND active; + +DROP TABLE test_with_merge; From 0fe0aa44d0ec318d3e9c35aa5f5af964fa28dc5e Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Fri, 28 Oct 2022 07:38:57 +0000 Subject: [PATCH 25/59] Increase wait time --- .../queries/0_stateless/02473_optimize_old_parts.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/queries/0_stateless/02473_optimize_old_parts.sql b/tests/queries/0_stateless/02473_optimize_old_parts.sql index 76c1ba73097..106175ab6f5 100644 --- a/tests/queries/0_stateless/02473_optimize_old_parts.sql +++ b/tests/queries/0_stateless/02473_optimize_old_parts.sql @@ -9,7 +9,7 @@ INSERT INTO test_without_merge SELECT 1; INSERT INTO test_without_merge SELECT 2; INSERT INTO test_without_merge SELECT 3; -SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT sleepEachRow(1) FROM numbers(9) FORMAT Null; SELECT count(*) FROM system.parts WHERE database = currentDatabase() AND table='test_without_merge' AND active; DROP TABLE test_without_merge; @@ -22,7 +22,7 @@ INSERT INTO test_with_merge SELECT 1; INSERT INTO test_with_merge SELECT 2; INSERT INTO test_with_merge SELECT 3; -SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT sleepEachRow(1) FROM numbers(9) FORMAT Null; SELECT count(*) FROM system.parts WHERE database = currentDatabase() AND table='test_with_merge' AND active; DROP TABLE test_with_merge; @@ -35,7 +35,7 @@ INSERT INTO test_with_merge SELECT 1; INSERT INTO test_with_merge SELECT 2; INSERT INTO test_with_merge SELECT 3; -SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT sleepEachRow(1) FROM numbers(9) FORMAT Null; SELECT count(*) FROM system.parts WHERE database = currentDatabase() AND table='test_with_merge' AND active; DROP TABLE test_with_merge; @@ -48,7 +48,7 @@ INSERT INTO test_replicated SELECT 1; INSERT INTO test_replicated SELECT 2; INSERT INTO test_replicated SELECT 3; -SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT sleepEachRow(1) FROM numbers(9) FORMAT Null; SELECT count(*) FROM system.parts WHERE database = currentDatabase() AND table='test_replicated' AND active; DROP TABLE test_replicated; @@ -61,7 +61,7 @@ INSERT INTO test_replicated SELECT 1; INSERT INTO test_replicated SELECT 2; INSERT INTO test_replicated SELECT 3; -SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT sleepEachRow(1) FROM numbers(9) FORMAT Null; SELECT count(*) FROM system.parts WHERE database = currentDatabase() AND table='test_replicated' AND active; DROP TABLE test_replicated; @@ -74,7 +74,7 @@ SYSTEM STOP MERGES test_with_merge; -- These three parts will have min_age=6 at the time of merge INSERT INTO test_with_merge SELECT 1; INSERT INTO test_with_merge SELECT 2; -SELECT sleepEachRow(1) FROM numbers(6) FORMAT Null; +SELECT sleepEachRow(1) FROM numbers(9) FORMAT Null; -- These three parts will have min_age=0 at the time of merge -- and so, nothing will be merged. INSERT INTO test_with_merge SELECT 3; From e4786a611ffc8fc5426c409c4f1e8749af446a34 Mon Sep 17 00:00:00 2001 From: Antonio Andelic Date: Fri, 28 Oct 2022 11:45:18 +0000 Subject: [PATCH 26/59] Add long tag --- tests/queries/0_stateless/02473_optimize_old_parts.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/queries/0_stateless/02473_optimize_old_parts.sql b/tests/queries/0_stateless/02473_optimize_old_parts.sql index 106175ab6f5..c2bd37033c1 100644 --- a/tests/queries/0_stateless/02473_optimize_old_parts.sql +++ b/tests/queries/0_stateless/02473_optimize_old_parts.sql @@ -1,3 +1,5 @@ +-- Tags: long + DROP TABLE IF EXISTS test_without_merge; DROP TABLE IF EXISTS test_with_merge; DROP TABLE IF EXISTS test_replicated; From 1ec10ade5bf5df2271f897bafb2ffabcb11d754a Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Fri, 28 Oct 2022 09:56:16 -0400 Subject: [PATCH 27/59] add link to geo fn, fix index --- .../example-datasets/cell-towers.md | 4 +- docs/en/sql-reference/functions/geo/index.md | 113 +++++++++--------- 2 files changed, 58 insertions(+), 59 deletions(-) diff --git a/docs/en/getting-started/example-datasets/cell-towers.md b/docs/en/getting-started/example-datasets/cell-towers.md index 989ae6f0a7e..3258f186b08 100644 --- a/docs/en/getting-started/example-datasets/cell-towers.md +++ b/docs/en/getting-started/example-datasets/cell-towers.md @@ -128,13 +128,13 @@ SELECT mcc, count() FROM cell_towers GROUP BY mcc ORDER BY count() DESC LIMIT 10 10 rows in set. Elapsed: 0.019 sec. Processed 43.28 million rows, 86.55 MB (2.33 billion rows/s., 4.65 GB/s.) ``` -So, the top countries are: the USA, Germany, and Russia. +Based on the above query and the [MCC list](https://en.wikipedia.org/wiki/Mobile_country_code), the countries with the most cell towers are: the USA, Germany, and Russia. You may want to create an [External Dictionary](../../sql-reference/dictionaries/external-dictionaries/external-dicts.md) in ClickHouse to decode these values. ## Use case: Incorporate geo data {#use-case} -Using `pointInPolygon` function. +Using the [`pointInPolygon`](/docs/en/sql-reference/functions/geo/coordinates.md/#pointinpolygon) function. 1. Create a table where we will store polygons: diff --git a/docs/en/sql-reference/functions/geo/index.md b/docs/en/sql-reference/functions/geo/index.md index 64e23094105..8d659236d4c 100644 --- a/docs/en/sql-reference/functions/geo/index.md +++ b/docs/en/sql-reference/functions/geo/index.md @@ -8,70 +8,69 @@ title: "Geo Functions" ## Geographical Coordinates Functions -- [greatCircleDistance](./coordinates.md#greatCircleDistance) -- [geoDistance](./coordinates.md#geoDistance) -- [greatCircleAngle](./coordinates.md#greatCircleAngle) -- [pointInEllipses](./coordinates.md#pointInEllipses) -- [pointInPolygon](./coordinates.md#pointInPolygon) +- [greatCircleDistance](./coordinates.md#greatcircledistance) +- [geoDistance](./coordinates.md#geodistance) +- [greatCircleAngle](./coordinates.md#greatcircleangle) +- [pointInEllipses](./coordinates.md#pointinellipses) +- [pointInPolygon](./coordinates.md#pointinpolygon) ## Geohash Functions -- [geohashEncode](./geohash.md#geohashEncode) -- [geohashDecode](./geohash.md#geohashDecode) -- [geohashesInBox](./geohash.md#geohashesInBox) +- [geohashEncode](./geohash.md#geohashencode) +- [geohashDecode](./geohash.md#geohashdecode) +- [geohashesInBox](./geohash.md#geohashesinbox) ## H3 Indexes Functions -- [h3IsValid](./h3.md#h3IsValid) -- [h3GetResolution](./h3.md#h3GetResolution) -- [h3EdgeAngle](./h3.md#h3EdgeAngle) -- [h3EdgeLengthM​](./h3.md#h3EdgeLengthM​) -- [h3EdgeLengthKm](./h3.md#h3EdgeLengthKm) -- [geoToH3](./h3.md#geoToH3) -- [h3ToGeo](./h3.md#h3ToGeo) -- [h3ToGeoBoundary](./h3.md#h3ToGeoBoundary) -- [h3kRing](./h3.md#h3kRing) -- [h3GetBaseCell](./h3.md#h3GetBaseCell) -- [h3HexAreaM2](./h3.md#h3HexAreaM2) -- [h3HexAreaKm2](./h3.md#h3HexAreaKm2) -- [h3IndexesAreNeighbors](./h3.md#h3IndexesAreNeighbors) -- [h3ToChildren](./h3.md#h3ToChildren) -- [h3ToParent](./h3.md#h3ToParent) -- [h3ToString](./h3.md#h3ToString) -- [stringToH3](./h3.md#stringToH3) -- [h3GetResolution](./h3.md#h3GetResolution) -- [h3IsResClassIII](./h3.md#h3IsResClassIII) -- [h3IsPentagon](./h3.md#h3IsPentagon) -- [h3GetFaces](./h3.md#h3GetFaces) -- [h3CellAreaM2](./h3.md#h3CellAreaM2) -- [h3CellAreaRads2](./h3.md#h3CellAreaRads2) -- [h3ToCenterChild](./h3.md#h3ToCenterChild) -- [h3ExactEdgeLengthM](./h3.md#h3ExactEdgeLengthM) -- [h3ExactEdgeLengthKm](./h3.md#h3ExactEdgeLengthKm) -- [h3ExactEdgeLengthRads](./h3.md#h3ExactEdgeLengthRads) -- [h3NumHexagons](./h3.md#h3NumHexagons) -- [h3Line](./h3.md#h3Line) -- [h3Distance](./h3.md#h3Distance) -- [h3HexRing](./h3.md#h3HexRing) -- [h3GetUnidirectionalEdge](./h3.md#h3GetUnidirectionalEdge) -- [h3UnidirectionalEdgeIsValid](./h3.md#h3UnidirectionalEdgeIsValid) -- [h3GetOriginIndexFromUnidirectionalEdge](./h3.md#h3GetOriginIndexFromUnidirectionalEdge) -- [h3GetDestinationIndexFromUnidirectionalEdge](./h3.md#h3GetDestinationIndexFromUnidirectionalEdge) -- [h3GetIndexesFromUnidirectionalEdge](./h3.md#h3GetIndexesFromUnidirectionalEdge) -- [h3GetUnidirectionalEdgesFromHexagon](./h3.md#h3GetUnidirectionalEdgesFromHexagon) -- [h3GetUnidirectionalEdgeBoundary](./h3.md#h3GetUnidirectionalEdgeBoundary) +- [h3IsValid](./h3.md#h3isvalid) +- [h3GetResolution](./h3.md#h3getresolution) +- [h3EdgeAngle](./h3.md#h3edgeangle) +- [h3EdgeLengthM](./h3.md#h3edgelengthm) +- [h3EdgeLengthKm](./h3.md#h3edgelengthkm) +- [geoToH3](./h3.md#geotoh3) +- [h3ToGeo](./h3.md#h3togeo) +- [h3ToGeoBoundary](./h3.md#h3togeoboundary) +- [h3kRing](./h3.md#h3kring) +- [h3GetBaseCell](./h3.md#h3getbasecell) +- [h3HexAreaM2](./h3.md#h3hexaream2) +- [h3HexAreaKm2](./h3.md#h3hexareakm2) +- [h3IndexesAreNeighbors](./h3.md#h3indexesareneighbors) +- [h3ToChildren](./h3.md#h3tochildren) +- [h3ToParent](./h3.md#h3toparent) +- [h3ToString](./h3.md#h3tostring) +- [stringToH3](./h3.md#stringtoh3) +- [h3GetResolution](./h3.md#h3getresolution) +- [h3IsResClassIII](./h3.md#h3isresclassiii) +- [h3IsPentagon](./h3.md#h3ispentagon) +- [h3GetFaces](./h3.md#h3getfaces) +- [h3CellAreaM2](./h3.md#h3cellaream2) +- [h3CellAreaRads2](./h3.md#h3cellarearads2) +- [h3ToCenterChild](./h3.md#h3tocenterchild) +- [h3ExactEdgeLengthM](./h3.md#h3exactedgelengthm) +- [h3ExactEdgeLengthKm](./h3.md#h3exactedgelengthkm) +- [h3ExactEdgeLengthRads](./h3.md#h3exactedgelengthrads) +- [h3NumHexagons](./h3.md#h3numhexagons) +- [h3Line](./h3.md#h3line) +- [h3Distance](./h3.md#h3distance) +- [h3HexRing](./h3.md#h3hexring) +- [h3GetUnidirectionalEdge](./h3.md#h3getunidirectionaledge) +- [h3UnidirectionalEdgeIsValid](./h3.md#h3unidirectionaledgeisvalid) +- [h3GetOriginIndexFromUnidirectionalEdge](./h3.md#h3getoriginindexfromunidirectionaledge) +- [h3GetDestinationIndexFromUnidirectionalEdge](./h3.md#h3getdestinationindexfromunidirectionaledge) +- [h3GetIndexesFromUnidirectionalEdge](./h3.md#h3getindexesfromunidirectionaledge) +- [h3GetUnidirectionalEdgesFromHexagon](./h3.md#h3getunidirectionaledgesfromhexagon) +- [h3GetUnidirectionalEdgeBoundary](./h3.md#h3getunidirectionaledgeboundary) ## S2 Index Functions -- [geoToS2](./s2.md#geoToS2) -- [s2ToGeo](./s2.md#s2ToGeo) -- [s2GetNeighbors](./s2.md#s2GetNeighbors) -- [s2CellsIntersect](./s2.md#s2CellsIntersect) -- [s2CapContains](./s2.md#s2CapContains) -- [s2CapUnion](./s2.md#s2CapUnion) -- [s2RectAdd](./s2.md#s2RectAdd) -- [s2RectContains](./s2.md#s2RectContains) -- [s2RectUinion](./s2.md#s2RectUinion) -- [s2RectIntersection](./s2.md#s2RectIntersection) +- [geoToS2](./s2.md#geotos2) +- [s2ToGeo](./s2.md#s2togeo) +- [s2GetNeighbors](./s2.md#s2getneighbors) +- [s2CellsIntersect](./s2.md#s2cellsintersect) +- [s2CapContains](./s2.md#s2capcontains) +- [s2CapUnion](./s2.md#s2capunion) +- [s2RectAdd](./s2.md#s2rectadd) +- [s2RectContains](./s2.md#s2rectcontains) +- [s2RectUnion](./s2.md#s2rectunion) +- [s2RectIntersection](./s2.md#s2rectintersection) -[Original article](https://clickhouse.com/docs/en/sql-reference/functions/geo/) From 418539cd514fbf34c3ce5436e76bc9d81bbe1298 Mon Sep 17 00:00:00 2001 From: DanRoscigno Date: Fri, 28 Oct 2022 11:54:44 -0400 Subject: [PATCH 28/59] add images --- .../example-datasets/cell-towers.md | 48 ++++++++++++++++-- .../images/superset-cell-tower-dashboard.png | Bin 0 -> 486355 bytes .../images/superset-connect-a-database.png | Bin 0 -> 74606 bytes .../images/superset-create-map.png | Bin 0 -> 294614 bytes .../images/superset-lon-lat.png | Bin 0 -> 38436 bytes .../images/superset-mcc-204.png | Bin 0 -> 12467 bytes .../images/superset-radio-umts.png | Bin 0 -> 12698 bytes .../images/superset-umts-netherlands.png | Bin 0 -> 46590 bytes 8 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 docs/en/getting-started/example-datasets/images/superset-cell-tower-dashboard.png create mode 100644 docs/en/getting-started/example-datasets/images/superset-connect-a-database.png create mode 100644 docs/en/getting-started/example-datasets/images/superset-create-map.png create mode 100644 docs/en/getting-started/example-datasets/images/superset-lon-lat.png create mode 100644 docs/en/getting-started/example-datasets/images/superset-mcc-204.png create mode 100644 docs/en/getting-started/example-datasets/images/superset-radio-umts.png create mode 100644 docs/en/getting-started/example-datasets/images/superset-umts-netherlands.png diff --git a/docs/en/getting-started/example-datasets/cell-towers.md b/docs/en/getting-started/example-datasets/cell-towers.md index 3258f186b08..c66b49d4751 100644 --- a/docs/en/getting-started/example-datasets/cell-towers.md +++ b/docs/en/getting-started/example-datasets/cell-towers.md @@ -12,15 +12,27 @@ import ActionsMenu from '@site/docs/en/_snippets/_service_actions_menu.md'; import SQLConsoleDetail from '@site/docs/en/_snippets/_launch_sql_console.md'; import SupersetDocker from '@site/docs/en/_snippets/_add_superset_detail.md'; +## Goal + +In this guide you will learn how to: +- load the OpenCellID data in Clickhouse +- connect Apache Superset to ClickHouse +- build a dashboard based on data available in the dataset +- how to choose data types for your table fields +- how to choose a primary key + +Here is a preview of the dashboard created in this guide: + +![Dashboard of cell towers by radio type in mcc 204](@site/docs/en/getting-started/example-datasets/images/superset-cell-tower-dashboard.png) + +## Get the Dataset {#get-the-dataset} + This dataset is from [OpenCellid](https://www.opencellid.org/) - The world's largest Open Database of Cell Towers. As of 2021, it contains more than 40 million records about cell towers (GSM, LTE, UMTS, etc.) around the world with their geographical coordinates and metadata (country code, network, etc). OpenCelliD Project is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License, and we redistribute a snapshot of this dataset under the terms of the same license. The up-to-date version of the dataset is available to download after sign in. - -## Get the Dataset {#get-the-dataset} - @@ -248,6 +260,36 @@ Superset is easy to run from Docker. If you already have Superset running, all +## Build a dashboard + +1. foo + +![Dashboard of cell towers by radio type in mcc 204](@site/docs/en/getting-started/example-datasets/images/superset-cell-tower-dashboard.png) + +1. foo + +![Add ClickHouse as a Superset datasource](@site/docs/en/getting-started/example-datasets/images/superset-connect-a-database.png) + +1. foo + +![Create a map in Superset](@site/docs/en/getting-started/example-datasets/images/superset-create-map.png) + +1. foo + +![Specify longitude and latitude fields](@site/docs/en/getting-started/example-datasets/images/superset-lon-lat.png) + +1. foo + +![Filter on MCC 204](@site/docs/en/getting-started/example-datasets/images/superset-mcc-204.png) + +1. foo + +![Filter on radio = UMTS](@site/docs/en/getting-started/example-datasets/images/superset-radio-umts.png) + +1. foo + +![Chart for UMTS radios in MCC 204](@site/docs/en/getting-started/example-datasets/images/superset-umts-netherlands.png) + The data is also available for interactive queries in the [Playground](https://play.clickhouse.com/play?user=play), [example](https://play.clickhouse.com/play?user=play#U0VMRUNUIG1jYywgY291bnQoKSBGUk9NIGNlbGxfdG93ZXJzIEdST1VQIEJZIG1jYyBPUkRFUiBCWSBjb3VudCgpIERFU0M=). Although you cannot create temporary tables there. diff --git a/docs/en/getting-started/example-datasets/images/superset-cell-tower-dashboard.png b/docs/en/getting-started/example-datasets/images/superset-cell-tower-dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..8197ea223c278f314fd9b7b17b95d15e9373a92f GIT binary patch literal 486355 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