#![allow(unused_crate_dependencies)]
#![allow(clippy::doc_markdown)]
use std::sync::Arc;
use std::time::Instant;
use clickhouse_arrow::prelude::ClickHouseEngine;
use clickhouse_arrow::test_utils::get_or_create_container;
use clickhouse_datafusion::prelude::*;
use datafusion::arrow::array::*;
use datafusion::arrow::datatypes::*;
use datafusion::arrow::record_batch::RecordBatch;
use datafusion::arrow::util::pretty::print_batches;
use datafusion::prelude::*;
const CATALOG: &str = "ch_df_examples";
const SCHEMA: &str = "concurrency_demo";
#[tokio::main]
#[allow(clippy::too_many_lines)]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("🚀 Write Concurrency Example\n");
println!("This example demonstrates parallel write performance for bulk inserts.\n");
let ch = get_or_create_container(None).await;
let num_batches = 10;
let rows_per_batch = 2_000;
let total_rows = num_batches * rows_per_batch;
println!("📊 Test Configuration:");
println!(" - Total rows: {total_rows}");
println!(" - Batches: {num_batches}");
println!(" - Rows per batch: {rows_per_batch}\n");
println!("1️⃣ Testing with DEFAULT concurrency (4 concurrent writes)...\n");
let ctx1 = SessionContext::new();
let builder1 = ClickHouseBuilder::new(ch.get_native_url())
.configure_client(|c| {
c.with_username(&ch.user).with_password(&ch.password).with_ipv4_only(true)
})
.configure_pool(|p| p.max_size(8))
.build_catalog(&ctx1, Some(CATALOG))
.await?;
let clickhouse1 = builder1
.with_schema(SCHEMA)
.await?
.with_new_table(
"metrics_default",
ClickHouseEngine::MergeTree,
Arc::new(Schema::new(vec![
Field::new("id", DataType::Int32, false),
Field::new("timestamp", DataType::Int64, false),
Field::new("value", DataType::Float64, false),
Field::new("category", DataType::Utf8, false),
])),
)
.update_create_options(|opts| opts.with_order_by(&["id".into()]))
.create(&ctx1)
.await?
.build_schema(None, &ctx1)
.await?;
drop(clickhouse1.build(&ctx1).await?);
let start1 = Instant::now();
for i in 0..num_batches {
let offset = i * rows_per_batch;
let batch = create_test_batch(offset, rows_per_batch)?;
let temp_table = format!("temp_batch_{i}");
drop(ctx1.register_batch(&temp_table, batch)?);
let _result = ctx1
.sql(&format!(
"INSERT INTO {CATALOG}.{SCHEMA}.metrics_default SELECT * FROM {temp_table}"
))
.await?
.collect()
.await?;
}
let duration1 = start1.elapsed();
let throughput1 = f64::from(total_rows) / duration1.as_secs_f64();
println!(" ✅ Completed in {duration1:?}");
println!(" 📈 Throughput: {throughput1:.0} rows/sec\n");
println!("2️⃣ Testing with HIGH concurrency (8 concurrent writes)...\n");
let ctx2 = SessionContext::new();
let builder2 = ClickHouseBuilder::new(ch.get_native_url())
.configure_client(|c| {
c.with_username(&ch.user).with_password(&ch.password).with_ipv4_only(true)
})
.configure_pool(|p| p.max_size(16)) .with_write_concurrency(8) .build_catalog(&ctx2, Some(CATALOG))
.await?;
let clickhouse2 = builder2
.with_schema(SCHEMA)
.await?
.with_new_table(
"metrics_high_concurrency",
ClickHouseEngine::MergeTree,
Arc::new(Schema::new(vec![
Field::new("id", DataType::Int32, false),
Field::new("timestamp", DataType::Int64, false),
Field::new("value", DataType::Float64, false),
Field::new("category", DataType::Utf8, false),
])),
)
.update_create_options(|opts| opts.with_order_by(&["id".into()]))
.create(&ctx2)
.await?
.build_schema(None, &ctx2)
.await?;
drop(clickhouse2.build(&ctx2).await?);
let start2 = Instant::now();
for i in 0..num_batches {
let offset = i * rows_per_batch;
let batch = create_test_batch(offset, rows_per_batch)?;
let temp_table = format!("temp_batch_{i}");
drop(ctx2.register_batch(&temp_table, batch)?);
let _result = ctx2
.sql(&format!(
"INSERT INTO {CATALOG}.{SCHEMA}.metrics_high_concurrency SELECT * FROM \
{temp_table}"
))
.await?
.collect()
.await?;
}
let duration2 = start2.elapsed();
let throughput2 = f64::from(total_rows) / duration2.as_secs_f64();
println!(" ✅ Completed in {duration2:?}");
println!(" 📈 Throughput: {throughput2:.0} rows/sec\n");
println!("3️⃣ Demonstrating EXPLAIN ANALYZE metrics visibility...\n");
let demo_batch = create_test_batch(0, 1000)?;
drop(ctx2.register_batch("demo_data", demo_batch)?);
let explain_result = ctx2
.sql(&format!(
"EXPLAIN ANALYZE INSERT INTO {CATALOG}.{SCHEMA}.metrics_high_concurrency SELECT * \
FROM demo_data"
))
.await?
.collect()
.await?;
println!(" EXPLAIN ANALYZE output:");
print_batches(&explain_result)?;
println!();
println!("📊 Performance Comparison:\n");
println!("┌─────────────────────┬──────────────┬──────────────────┬────────────┐");
println!("│ Configuration │ Duration │ Throughput │ vs Default │");
println!("├─────────────────────┼──────────────┼──────────────────┼────────────┤");
println!("│ Default (conc=4) │ {duration1:>10.2?} │ {throughput1:>13.0} r/s │ baseline │");
println!(
"│ High (conc=8) │ {:>10.2?} │ {:>13.0} r/s │ {:>8.2}x │",
duration2,
throughput2,
throughput2 / throughput1
);
println!("└─────────────────────┴──────────────┴──────────────────┴────────────┘");
println!();
println!("4️⃣ Verifying data integrity...\n");
let verify_ctx = SessionContext::new();
let builder_verify = ClickHouseBuilder::new(ch.get_native_url())
.configure_client(|c| {
c.with_username(&ch.user).with_password(&ch.password).with_ipv4_only(true)
})
.build_catalog(&verify_ctx, Some(CATALOG))
.await?;
drop(
builder_verify
.with_schema(SCHEMA)
.await?
.build_schema(None, &verify_ctx)
.await?
.build(&verify_ctx)
.await?,
);
let count1 = verify_ctx
.sql(&format!("SELECT COUNT(*) FROM {CATALOG}.{SCHEMA}.metrics_default"))
.await?
.collect()
.await?;
let count2 = verify_ctx
.sql(&format!("SELECT COUNT(*) FROM {CATALOG}.{SCHEMA}.metrics_high_concurrency"))
.await?
.collect()
.await?;
println!(" Default concurrency table rows:");
print_batches(&count1)?;
println!();
println!(" High concurrency table rows:");
print_batches(&count2)?;
println!();
println!("💡 Best Practices:\n");
println!(" ✅ Use default (concurrency=4) for most workloads");
println!(" ✅ Increase concurrency for bulk inserts (100k+ rows)");
println!(" ✅ Match concurrency to connection pool size");
println!(" ✅ Monitor with EXPLAIN ANALYZE for optimization");
println!(" ✅ Test different concurrency levels for your workload\n");
println!("✅ Example completed successfully!");
let _ = ch.shutdown().await.ok();
Ok(())
}
fn create_test_batch(
offset: i32,
num_rows: i32,
) -> Result<RecordBatch, Box<dyn std::error::Error>> {
let ids: Vec<i32> = (offset..offset + num_rows).collect();
let timestamps: Vec<i64> = ids.iter().map(|&i| i64::from(i) * 1000).collect();
let values: Vec<f64> = ids.iter().map(|&i| f64::from(i) * 1.5 + 10.0).collect();
let categories: Vec<String> = ids.iter().map(|&i| format!("category_{}", i % 10)).collect();
Ok(RecordBatch::try_new(
Arc::new(Schema::new(vec![
Field::new("id", DataType::Int32, false),
Field::new("timestamp", DataType::Int64, false),
Field::new("value", DataType::Float64, false),
Field::new("category", DataType::Utf8, false),
])),
vec![
Arc::new(Int32Array::from(ids)),
Arc::new(Int64Array::from(timestamps)),
Arc::new(Float64Array::from(values)),
Arc::new(StringArray::from(categories)),
],
)?)
}