Skip to content

Commit c34bf42

Browse files
committed
AmateurMergeTree: ci
1 parent 3850941 commit c34bf42

6 files changed

Lines changed: 493 additions & 0 deletions

File tree

.github/workflows/amateur_ci.yaml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
name: AmateurCI
3+
4+
on:
5+
push:
6+
branches: ['amateur-merge-tree']
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
name: "Build"
12+
steps:
13+
- name: Checkout code
14+
uses: actions/checkout@v6
15+
with:
16+
submodules: recursive
17+
18+
- name: Restore ccache
19+
uses: actions/cache/restore@v4
20+
with:
21+
path: /tmp/ccache
22+
key: ccache-${{ github.sha }}
23+
restore-keys: |
24+
ccache-
25+
26+
- name: Build
27+
run: |
28+
docker run --rm -v $PWD:/ClickHouse -v /tmp/ccache:/tmp/ccache clickhouse/fasttest:b63954f8a1b316d0686e \
29+
/bin/bash -c "
30+
set -e
31+
apt-get update && apt-get install -y ccache
32+
export CC=clang-21
33+
export CXX=clang++-21
34+
export CCACHE_DIR=/tmp/ccache
35+
cd /ClickHouse/
36+
cmake -DCOMPILER_CACHE=ccache -DCMAKE_BUILD_TYPE=Debug -DOMIT_HEAVY_DEBUG_SYMBOLS=1 -DENABLE_LIBRARIES=0 -DENABLE_UTILS=0 -DENABLE_TESTS=0 -DENABLE_RUST=0 -DENABLE_JEMALLOC=1 -DENABLE_YAML_CPP=1 -DENABLE_AWS_S3=1 -G Ninja -S . -B build
37+
ninja -C build
38+
ccache --show-stats
39+
"
40+
41+
- name: Save ccache
42+
if: github.ref_name == 'amateur-merge-tree'
43+
uses: actions/cache/save@v4
44+
with:
45+
path: /tmp/ccache
46+
key: ccache-${{ github.sha }}
47+
48+
- name: Integration tests
49+
run: |
50+
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v $PWD:$PWD --network host clickhouse/integration-tests-runner:c909f2e80ec78b3a6532 \
51+
/bin/bash -c "
52+
CLICKHOUSE_TESTS_SERVER_BIN_PATH=$PWD/build/programs/clickhouse \
53+
pytest $PWD/tests/integration/test_amateur_merge_tree/ -v --timeout=600
54+
"

tests/integration/test_amateur_merge_tree/__init__.py

Whitespace-only changes.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
storage_configuration:
3+
disks:
4+
s3:
5+
type: object_storage
6+
object_storage_type: s3
7+
metadata_type: amateur
8+
endpoint: http://minio1:9001/root/data/
9+
keeper_prefix: /clickhouse/disks/s3
10+
access_key_id: minio
11+
secret_access_key: ClickHouse_Minio_P@ssw0rd
12+
policies:
13+
s3:
14+
volumes:
15+
- main:
16+
disks:
17+
- s3
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import time
2+
3+
4+
def wait_table_sync(zk_client, zk_table_path):
5+
_, parts_stat = zk_client.get(f"{zk_table_path}/parts")
6+
nodes = zk_client.get_children(f"{zk_table_path}/replicas")
7+
8+
for _ in range(60):
9+
nodes_in_sync = 0
10+
for node in nodes:
11+
node_epoch, _ = zk_client.get(f"{zk_table_path}/replicas/{node}/epoch")
12+
if int(node_epoch) >= parts_stat.pzxid:
13+
nodes_in_sync += 1
14+
15+
if nodes_in_sync == len(nodes):
16+
return
17+
18+
time.sleep(0.5)
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
#!/usr/bin/env python3
2+
3+
import time
4+
import pytest
5+
6+
from helpers.cluster import ClickHouseCluster
7+
from helpers.test_tools import TSV
8+
9+
from test_amateur_merge_tree.helpers import wait_table_sync
10+
11+
cluster = ClickHouseCluster(__file__)
12+
13+
14+
@pytest.fixture(scope="module")
15+
def started_cluster():
16+
try:
17+
cluster.add_instance(
18+
"node1",
19+
main_configs=["configs/config.yaml"],
20+
with_minio=True,
21+
with_zookeeper=True,
22+
stay_alive=True,
23+
use_keeper=False,
24+
cpu_limit=3,
25+
)
26+
cluster.add_instance(
27+
"node2",
28+
main_configs=["configs/config.yaml"],
29+
with_minio=True,
30+
with_zookeeper=True,
31+
stay_alive=True,
32+
use_keeper=False,
33+
cpu_limit=3
34+
)
35+
cluster.start()
36+
yield cluster
37+
finally:
38+
cluster.shutdown()
39+
40+
41+
def test_insert_select(started_cluster):
42+
"""
43+
Basic scenario with inserts and replica synchronization
44+
"""
45+
46+
for node_name, node in cluster.instances.items():
47+
data = node.query(f"""
48+
CREATE TABLE test_basic (key UInt32, val String)
49+
ENGINE = AmateurMergeTree('/clickhouse/tables/default/test_basic', '{node_name}')
50+
ORDER BY key
51+
SETTINGS storage_policy = 's3'
52+
""")
53+
54+
for i in range(10):
55+
node = list(cluster.instances.values())[i % 2]
56+
node.query(f"""
57+
INSERT INTO test_basic SELECT number + {i} * 10 as key, 'val-' || key AS val FROM numbers(10)
58+
""")
59+
60+
zk_client = cluster.get_kazoo_client("zoo1")
61+
wait_table_sync(zk_client, "/clickhouse/tables/default/test_basic")
62+
63+
expected = [[100, 0, 'val-0', 99, 'val-99']]
64+
for node_name, node in cluster.instances.items():
65+
actual = node.query("""
66+
SELECT count(), min(key), argMin(val, key), max(key), argMax(val, key) FROM test_basic
67+
""")
68+
69+
assert TSV(actual) == TSV(expected), f"Received an unexpected result from node {node_name}"
70+
71+
72+
def test_drop_partititon(started_cluster):
73+
"""
74+
Insert into partitioned tables and drop partition
75+
"""
76+
77+
for node_name, node in cluster.instances.items():
78+
data = node.query(f"""
79+
CREATE TABLE test_partitioned (key UInt32, prt UInt32, val String)
80+
ENGINE = AmateurMergeTree('/clickhouse/tables/default/test_partitioned', '{node_name}')
81+
PARTITION BY prt
82+
ORDER BY key
83+
SETTINGS storage_policy = 's3'
84+
""")
85+
86+
for i in range(10):
87+
node = list(cluster.instances.values())[i % 2]
88+
node.query(f"""
89+
INSERT INTO test_partitioned SELECT number + {i} * 10 as key, number % 5 AS prt, 'val-' || key FROM numbers(10)
90+
""")
91+
92+
cluster.instances["node1"].query("ALTER TABLE test_partitioned DROP PARTITION 1")
93+
cluster.instances["node2"].query("ALTER TABLE test_partitioned DROP PARTITION 3")
94+
95+
zk_client = cluster.get_kazoo_client("zoo1")
96+
wait_table_sync(zk_client, "/clickhouse/tables/default/test_partitioned")
97+
98+
expected = [[60, 0, 'val-0', 99, 'val-99', 0, 0]]
99+
for node_name, node in cluster.instances.items():
100+
actual = node.query("""
101+
SELECT count(), min(key), argMin(val, key), max(key), argMax(val, key),
102+
countIf(prt = 1), countIf(prt = 3)
103+
FROM test_partitioned
104+
""")
105+
106+
assert TSV(actual) == TSV(expected), f"Received an unexpected result from node {node_name}"
107+
108+
109+
def test_optimize(started_cluster):
110+
"""
111+
Run optimize on both replicas
112+
"""
113+
114+
for node_name, node in cluster.instances.items():
115+
data = node.query(f"""
116+
CREATE TABLE test_optimize(key UInt32, val String)
117+
ENGINE = AmateurMergeTree('/clickhouse/tables/default/test_optimize', '{node_name}')
118+
ORDER BY key
119+
SETTINGS storage_policy = 's3'
120+
""")
121+
122+
for i in range(10):
123+
node = list(cluster.instances.values())[i % 2]
124+
node.query(f"""
125+
INSERT INTO test_optimize SELECT number + {i} * 10 as key, 'val-' || key FROM numbers(10)
126+
""")
127+
128+
zk_client = cluster.get_kazoo_client("zoo1")
129+
wait_table_sync(zk_client, "/clickhouse/tables/default/test_optimize")
130+
131+
for node_name, node in cluster.instances.items():
132+
actual = node.query("OPTIMIZE TABLE test_optimize FINAL")
133+
134+
wait_table_sync(zk_client, "/clickhouse/tables/default/test_optimize")
135+
136+
expected = [[60, 0, 'val-0', 99, 'val-99', 0, 0, 'all_0_9']]
137+
for node_name, node in cluster.instances.items():
138+
actual = node.query("""
139+
SELECT count(), min(key), argMin(val, key), max(key), argMax(val, key),
140+
any(substr(_part, 1, 7))
141+
FROM test_optimize
142+
""")
143+
144+
145+
def test_add_remove_replica(started_cluster):
146+
"""
147+
Add table replica while the data already exists
148+
"""
149+
150+
cluster.instances["node1"].query("""
151+
CREATE TABLE test_add_remove_replica (key UInt32, val String)
152+
ENGINE = AmateurMergeTree('/clickhouse/tables/default/test_add_remove_replica', 'node1')
153+
ORDER BY key
154+
SETTINGS storage_policy = 's3'
155+
""")
156+
157+
for i in range(10):
158+
cluster.instances["node1"].query(f"""
159+
INSERT INTO test_add_remove_replica SELECT number + {i} * 10 as key, 'val-' || key AS val FROM numbers(10)
160+
""")
161+
162+
expected = [[100, 0, 'val-0', 99, 'val-99']]
163+
164+
actual_first = cluster.instances["node1"].query("""
165+
SELECT count(), min(key), argMin(val, key), max(key), argMax(val, key) FROM test_add_remove_replica
166+
""")
167+
assert TSV(actual_first) == TSV(expected), "Received an unexpected result from node1"
168+
169+
cluster.instances["node2"].query("""
170+
CREATE TABLE test_add_remove_replica (key UInt32, val String)
171+
ENGINE = AmateurMergeTree('/clickhouse/tables/default/test_add_remove_replica', 'node2')
172+
ORDER BY key
173+
SETTINGS storage_policy = 's3'
174+
""")
175+
176+
zk_client = cluster.get_kazoo_client("zoo1")
177+
wait_table_sync(zk_client, "/clickhouse/tables/default/test_add_remove_replica")
178+
179+
actual_replica = cluster.instances["node2"].query("""
180+
SELECT count(), min(key), argMin(val, key), max(key), argMax(val, key) FROM test_add_remove_replica
181+
""")
182+
assert TSV(actual_first) == TSV(expected), "Received an unexpected result from node2"
183+
184+
cluster.instances["node2"].query("DROP TABLE test_add_remove_replica SYNC")
185+
186+
actual_first = cluster.instances["node1"].query("""
187+
SELECT count(), min(key), argMin(val, key), max(key), argMax(val, key) FROM test_add_remove_replica
188+
""")
189+
assert TSV(actual_first) == TSV(expected), "Received an unexpected result from node1 after replica drop"
190+
191+
192+
def test_epoch_hold_by_snapshot(started_cluster):
193+
"""
194+
Assert epoch (used for cleanup) not propagating when snapshot is hold by long running query
195+
"""
196+
197+
for node_name, node in cluster.instances.items():
198+
data = node.query(f"""
199+
CREATE TABLE test_epoch_hold_by_snapshot (key UInt32, val String)
200+
ENGINE = AmateurMergeTree('/clickhouse/tables/default/test_epoch_hold_by_snapshot', '{node_name}')
201+
ORDER BY key
202+
SETTINGS storage_policy = 's3', old_parts_lifetime = 1
203+
""")
204+
205+
cluster.instances["node1"].query(f"""
206+
INSERT INTO test_epoch_hold_by_snapshot SELECT number, 'val-' || number FROM numbers(10)
207+
""")
208+
209+
zk_client = cluster.get_kazoo_client("zoo1")
210+
wait_table_sync(zk_client, "/clickhouse/tables/default/test_epoch_hold_by_snapshot")
211+
212+
node1_initial_epoch = cluster.instances["node1"].query("""
213+
SELECT value FROM system.zookeeper WHERE path = '/clickhouse/tables/default/test_epoch_hold_by_snapshot/replicas/node1'
214+
""").strip()
215+
node2_initial_epoch = cluster.instances["node1"].query("""
216+
SELECT value FROM system.zookeeper WHERE path = '/clickhouse/tables/default/test_epoch_hold_by_snapshot/replicas/node2'
217+
""").strip()
218+
219+
assert int(node1_initial_epoch) == int(node2_initial_epoch), "Expected initial epochs to be equal"
220+
221+
response = cluster.instances["node2"].get_query_request(
222+
f"SELECT * FROM loop(test_epoch_hold_by_snapshot) settings max_threads = 1",
223+
query_id="hold_snapshot"
224+
)
225+
226+
for _ in range(3):
227+
cluster.instances["node1"].query(f"""
228+
INSERT INTO test_epoch_hold_by_snapshot SELECT number, 'val-' || number FROM numbers(10)
229+
""")
230+
231+
cluster.instances["node1"].query("""
232+
OPTIMIZE TABLE test_epoch_hold_by_snapshot FINAL
233+
SETTINGS alter_sync = 1, optimize_throw_if_noop = 1
234+
""")
235+
236+
time.sleep(2)
237+
238+
node1_after_opt_epoch = cluster.instances["node1"].query("""
239+
SELECT value FROM system.zookeeper WHERE path = '/clickhouse/tables/default/test_epoch_hold_by_snapshot/replicas/node1'
240+
""").strip()
241+
node2_after_opt_epoch = cluster.instances["node1"].query("""
242+
SELECT value FROM system.zookeeper WHERE path = '/clickhouse/tables/default/test_epoch_hold_by_snapshot/replicas/node2'
243+
""").strip()
244+
245+
assert int(node1_after_opt_epoch) > int(node2_after_opt_epoch), "Expected node1 epoch to be greater than node2 epoch"
246+
assert int(node2_initial_epoch) == int(node2_after_opt_epoch), "Expected node2 unchanged after inserts / optimize"
247+
248+
response = cluster.instances["node2"].query("KILL QUERY WHERE query_id = 'hold_snapshot' SYNC")
249+
250+
wait_table_sync(zk_client, "/clickhouse/tables/default/test_epoch_hold_by_snapshot")
251+
252+
node2_after_sync_epoch = cluster.instances["node1"].query("""
253+
SELECT value FROM system.zookeeper WHERE path = '/clickhouse/tables/default/test_epoch_hold_by_snapshot/replicas/node2'
254+
""").strip()
255+
256+
assert int(node1_after_opt_epoch) == int(node2_after_sync_epoch), "Expected epochs to be equal after final sync"
257+
258+
259+
def test_deduplicate(started_cluster):
260+
"""
261+
Assert block deduplication
262+
"""
263+
264+
for node_name, node in cluster.instances.items():
265+
data = node.query(f"""
266+
CREATE TABLE test_deduplicate(key UInt32, val String)
267+
ENGINE = AmateurMergeTree('/clickhouse/tables/default/test_deduplicate', '{node_name}')
268+
ORDER BY key
269+
SETTINGS storage_policy = 's3'
270+
""")
271+
272+
for i in range(6):
273+
node = list(cluster.instances.values())[i % 2]
274+
node.query(f"""
275+
INSERT INTO test_deduplicate VALUES (0, 'a'), (1, 'b'), (2, 'c'), (4, 'd'), (5, 'e')
276+
""")
277+
278+
zk_client = cluster.get_kazoo_client("zoo1")
279+
wait_table_sync(zk_client, "/clickhouse/tables/default/test_deduplicate")
280+
281+
expected = [[5, 0, 'a', 5, 'e']]
282+
for node_name, node in cluster.instances.items():
283+
actual = node.query("""
284+
SELECT count(), min(key), argMin(val, key), max(key), argMax(val, key),
285+
FROM test_deduplicate
286+
""")

0 commit comments

Comments
 (0)