Connect ClickHouse Go Client to Tinybird¶
The official ClickHouse Go client can connect to Tinybird using the ClickHouse® HTTP protocol compatibility. This enables you to query your Tinybird data sources programmatically from Go applications with high performance and type safety.
The ClickHouse® connection to Tinybird is read-only. You can use it to query and analyze data from your Tinybird data sources, but you cannot modify data through this connection.
Prerequisites¶
- Go 1.19 or later
- A Tinybird workspace with data sources
- A Tinybird Auth Token with read permissions for the workspace data sources
Installation¶
Add the ClickHouse Go client to your project:
go mod init your-project go get github.com/ClickHouse/clickhouse-go/v2
Configuration¶
Create a connection with the following configuration:
package main
import (
"context"
"crypto/tls"
"log"
"github.com/ClickHouse/clickhouse-go/v2"
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
)
func connect() (driver.Conn, error) {
conn, err := clickhouse.Open(&clickhouse.Options{
Addr: []string{"clickhouse.tinybird.co:443"},
Protocol: clickhouse.HTTP, // Use HTTP protocol
Auth: clickhouse.Auth{
Database: "default",
Username: "<WORKSPACE_NAME>", // Optional, for identification
Password: "<TOKEN>", // Your Tinybird Auth Token
},
TLS: &tls.Config{
InsecureSkipVerify: false, // Set to true for testing only
},
ClientInfo: clickhouse.ClientInfo{
Products: []struct {
Name string
Version string
}{
{Name: "your-app-name", Version: "1.0.0"},
},
},
})
if err != nil {
return nil, err
}
// Test the connection
if err := conn.Ping(context.Background()); err != nil {
return nil, err
}
return conn, nil
}
See the list of ClickHouse hosts to find the correct one for your region.
Test the connection¶
Create a simple query to verify your connection:
func main() {
conn, err := connect()
if err != nil {
log.Fatal(err)
}
defer conn.Close()
ctx := context.Background()
// Test query
rows, err := conn.Query(ctx, "SELECT database, name, engine FROM system.tables LIMIT 5")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var database, name, engine string
if err := rows.Scan(&database, &name, &engine); err != nil {
log.Fatal(err)
}
fmt.Printf("Database: %s, Table: %s, Engine: %s\n", database, name, engine)
}
}
Query your data¶
Once connected, you can query your Tinybird data sources:
// Query a specific data source
func queryDataSource(conn driver.Conn) error {
ctx := context.Background()
rows, err := conn.Query(ctx, "SELECT * FROM your_data_source_name LIMIT 10")
if err != nil {
return err
}
defer rows.Close()
// Process results
for rows.Next() {
var col1 string
var col2 int64
// Scan based on your data source schema
if err := rows.Scan(&col1, &col2); err != nil {
return err
}
fmt.Printf("Col1: %s, Col2: %d\n", col1, col2)
}
return rows.Err()
}
// Query with parameters using named parameters
func queryWithParams(conn driver.Conn, startDate string, limit int) error {
ctx := context.Background()
query := `
SELECT timestamp, user_id, event_name
FROM events
WHERE timestamp >= @start_date
LIMIT @limit
`
rows, err := conn.Query(ctx, query,
clickhouse.Named("start_date", startDate),
clickhouse.Named("limit", limit),
)
if err != nil {
return err
}
defer rows.Close()
// Process results...
return nil
}
Observability¶
You can also explore Tinybird's observability data from internal service data sources:
// View recent API endpoint usage and performance
func queryRecentUsage(conn driver.Conn) error {
ctx := context.Background()
query := `
SELECT
start_datetime,
pipe_name,
duration,
result_rows,
read_bytes,
status_code
FROM tinybird.pipe_stats_rt
WHERE start_datetime >= now() - INTERVAL 1 DAY
ORDER BY start_datetime DESC
LIMIT 10
`
rows, err := conn.Query(ctx, query)
if err != nil {
return err
}
defer rows.Close()
fmt.Println("Recent API endpoint usage:")
for rows.Next() {
var startTime time.Time
var pipeName string
var duration float64
var resultRows, readBytes, statusCode uint64
if err := rows.Scan(&startTime, &pipeName, &duration, &resultRows, &readBytes, &statusCode); err != nil {
return err
}
fmt.Printf("[%s] %s - %.2fms, %d rows, status %d\n",
startTime.Format("2006-01-02 15:04:05"), pipeName, duration*1000, resultRows, statusCode)
}
return rows.Err()
}
// Analyze endpoint performance metrics
func queryPerformanceMetrics(conn driver.Conn) error {
ctx := context.Background()
query := `
SELECT
pipe_name,
count() as request_count,
avg(duration) as avg_duration_ms,
avg(result_rows) as avg_result_rows,
sum(read_bytes) as total_bytes_read
FROM tinybird.pipe_stats_rt
WHERE start_datetime >= now() - INTERVAL 1 HOUR
GROUP BY pipe_name
ORDER BY request_count DESC
`
rows, err := conn.Query(ctx, query)
if err != nil {
return err
}
defer rows.Close()
fmt.Println("\nEndpoint performance metrics (last hour):")
for rows.Next() {
var pipeName string
var requestCount uint64
var avgDuration, avgResultRows float64
var totalBytesRead uint64
if err := rows.Scan(&pipeName, &requestCount, &avgDuration, &avgResultRows, &totalBytesRead); err != nil {
return err
}
fmt.Printf("%s: %d requests, %.2fms avg, %.0f avg rows, %d bytes\n",
pipeName, requestCount, avgDuration*1000, avgResultRows, totalBytesRead)
}
return rows.Err()
}
Error handling¶
Handle ClickHouse-specific errors:
import (
"github.com/ClickHouse/clickhouse-go/v2"
)
func handleQueryError(err error) {
if exception, ok := err.(*clickhouse.Exception); ok {
log.Printf("ClickHouse exception [%d]: %s", exception.Code, exception.Message)
if exception.StackTrace != "" {
log.Printf("Stack trace: %s", exception.StackTrace)
}
} else {
log.Printf("Connection error: %v", err)
}
}