Skip to content

Instantly share code, notes, and snippets.

View kavirajk's full-sized avatar
😎
Talk database to me

Kaviraj Kanagaraj kavirajk

😎
Talk database to me
View GitHub Profile
@kavirajk
kavirajk / string_query_param_chgo.go
Last active August 10, 2026 08:14
query_param_clickhouse.go
package main
import (
"context"
"fmt"
"github.com/ClickHouse/ch-go"
"github.com/ClickHouse/ch-go/proto"
"github.com/ClickHouse/clickhouse-go/v2"
)
@kavirajk
kavirajk / input_ch_sql.go
Created August 3, 2026 15:19
go_input_ch_sql
package main
import (
"fmt"
"log"
"time"
"github.com/ClickHouse/clickhouse-go/v2"
)
@kavirajk
kavirajk / latency-issue.go
Created July 20, 2026 10:07
RTT-issue-reproduction
// Minimal native-protocol client to observe the end-of-query packet-train
// stall (the "2xRTT" issue).
//
// It runs N identical SELECTs on ONE reused connection and, for each query,
// splits the client-observed latency into:
//
// first_row : query sent -> first result row decoded (the query itself)
// tail : first row -> EndOfStream (the epilogue train)
//
// A healthy query has tail ~= 0. When the epilogue train (profile info,
@kavirajk
kavirajk / e2e.go
Created July 16, 2026 09:06
e2e.go (customer)
// Read-your-own-write: why "write a row, then read it back" takes 200ms+
// when the target is <20ms.
//
// End-to-end latency = write ack + visibility delay + read. With sync
// single-row inserts the write path drowns everything (scenario A - see the
// write repro for why). With async_insert + wait_for_async_insert=0 writes
// are ~ms and e2e is bounded by the VISIBILITY WINDOW: a row only becomes
// readable when the server flushes its async buffer, every
// async_insert_busy_timeout_ms (default 200). A write lands at a random
// point inside the window, so median e2e ≈ window/2 + one read (scenario B).
@kavirajk
kavirajk / write.go
Created July 16, 2026 09:01
write.go (customer)
// Single-row inserts: why writing 100 rows/second (one INSERT each) shows
// 1s+ latency when a lone insert takes ~5ms.
//
// Two stacked causes:
//
// 1. Server-side: concurrent single-row INSERTs to one table convoy -
// ~200ms each at concurrency 10, nearly zero CPU. This affects every
// client (clickhouse-client too), even in-memory Buffer tables.
// 2. Client-side amplification: 100 writes/s x 0.2s each needs 20
// connections in flight, but the driver default is MaxOpenConns=10 -
@kavirajk
kavirajk / read.go
Last active July 16, 2026 09:00
read.go (customer)
dle connections: why queries after an idle gap take ~2x longer.
//
// The clickhouse-go pool reuses one TCP connection for sequential queries:
// scenarios A and B below finish with dials=1 no matter how long the gaps
// are. Latency only doubles when the PEER (a load balancer, proxy, NAT box,
// or a server setting) closes connections that sit idle. Then every query
// finds a dead pooled connection and pays a reconnect - TCP handshake +
// protocol hello = 2 extra round trips (scenario C: dials == queries).
//
// The fix is in the environment, not the driver: find what closes idle
@kavirajk
kavirajk / schema.sql
Created July 16, 2026 08:54
schema.sql (benchmark_test)
CREATE TABLE IF NOT EXISTS benchmark_test
(
id UInt64,
value Array(Float64)
)
ENGINE = MergeTree
ORDER BY id;
INSERT INTO benchmark_test SELECT number, arrayMap(x -> rand64()/1e19, range(200)) FROM numbers(1000)
@kavirajk
kavirajk / variant_null.go
Created March 17, 2026 13:45
Variant ClickHouse Go null
package main
import (
"context"
"fmt"
"github.com/ClickHouse/clickhouse-go/v2"
)
func main() {
@kavirajk
kavirajk / custom_dialer.go
Last active February 16, 2026 10:56
custom_dialer.go
package main
import (
"context"
"crypto/tls"
"fmt"
"os"
"sync"
"time"
@kavirajk
kavirajk / variant_fail.go
Last active January 14, 2026 16:12
Variant type broken on `head` CH
package main
import (
"context"
"fmt"
"github.com/ClickHouse/clickhouse-go/v2"
)
func main() {