Go 1.16+
Go Driver (gocql)
Build lightning-fast cloud-native apps with Cassandra!
📦 Installation & Setup
Get started quickly!
1
Install gocql Driver
Using go get (recommended)
# Install latest version
$ go get github.com/gocql/gocql
# Or use go modules (recommended)
$ go mod init myapp
$ go get github.com/gocql/gocql
# Verify installation
$ go list -m github.com/gocql/gocql
github.com/gocql/gocql v1.6.0
System Requirements
| Component | Requirement | Notes |
|---|---|---|
| Go | 1.16+ | Latest version recommended |
| gocql | 1.6.0+ | Community-maintained driver |
| Cassandra | 2.1+ | Best with 3.11 or 4.0+ |
| Platform | Linux, macOS, Windows | Cross-platform support |
Important Note
gocql is a community-maintained driver (not official DataStax).
- ✅ Most popular Go driver for Cassandra
- ✅ Production-tested by many companies
- ✅ Active community development
- ⚠️ Not officially supported by DataStax
- ⚠️ Some advanced features may lag behind
🚀 Quick Start Example
Your first Go + Cassandra app!
// main.go
package main
import (
"fmt"
"log"
"github.com/gocql/gocql"
)
func main() {
// 1. Create cluster configuration
cluster := gocql.NewCluster("127.0.0.1")
cluster.Keyspace = "tutorial"
cluster.Consistency = gocql.Quorum
// 2. Create session
session, err := cluster.CreateSession()
if err != nil {
log.Fatal("Failed to connect:", err)
}
defer session.Close()
// 3. Create keyspace
err = session.Query(`
CREATE KEYSPACE IF NOT EXISTS tutorial
WITH replication = {
'class': 'SimpleStrategy',
'replication_factor': 1
}
`).Exec()
if err != nil {
log.Fatal("Keyspace creation failed:", err)
}
// 4. Create table
err = session.Query(`
CREATE TABLE IF NOT EXISTS tutorial.users (
user_id UUID PRIMARY KEY,
name TEXT,
email TEXT,
age INT
)
`).Exec()
if err != nil {
log.Fatal("Table creation failed:", err)
}
// 5. Insert data
userId := gocql.TimeUUID()
err = session.Query(`
INSERT INTO tutorial.users (user_id, name, email, age)
VALUES (?, ?, ?, ?)
`, userId, "Alice", "alice@example.com", 28).Exec()
if err != nil {
log.Fatal("Insert failed:", err)
}
// 6. Query data
var name, email string
var age int
iter := session.Query(`SELECT name, email, age FROM tutorial.users`).Iter()
for iter.Scan(&name, &email, &age) {
fmt.Printf("%s (%s) - Age: %d\n", name, email, age)
}
if err := iter.Close(); err != nil {
log.Fatal("Query failed:", err)
}
}
// Output: Alice (alice@example.com) - Age: 28
Run the Example
$ go run main.go
Alice (alice@example.com) - Age: 28
🔌 Connection Management
Proper cluster setup!
Basic Connection
package main
import (
"github.com/gocql/gocql"
"log"
)
func main() {
// Simple connection
cluster := gocql.NewCluster("127.0.0.1")
cluster.Keyspace = "my_keyspace"
session, err := cluster.CreateSession()
if err != nil {
log.Fatal(err)
}
defer session.Close()
// Use session...
}
Production-Ready Connection
package main
import (
"time"
"github.com/gocql/gocql"
)
func createSession() (*gocql.Session, error) {
// Create cluster configuration
cluster := gocql.NewCluster(
"node1",
"node2",
"node3",
)
// Keyspace
cluster.Keyspace = "my_keyspace"
// Consistency level
cluster.Consistency = gocql.Quorum
// Authentication
cluster.Authenticator = gocql.PasswordAuthenticator{
Username: "cassandra",
Password: "cassandra",
}
// Timeouts
cluster.Timeout = 10 * time.Second
cluster.ConnectTimeout = 5 * time.Second
// Connection pooling
cluster.NumConns = 2 // Connections per host
// Retry policy
cluster.RetryPolicy = &gocql.SimpleRetryPolicy{NumRetries: 3}
// Protocol version
cluster.ProtoVersion = 4
// Create session
return cluster.CreateSession()
}
Singleton Pattern (Recommended)
// db/cassandra.go
package db
import (
"sync"
"github.com/gocql/gocql"
)
var (
session *gocql.Session
once sync.Once
initErr error
)
// GetSession returns singleton session
func GetSession() (*gocql.Session, error) {
once.Do(func() {
cluster := gocql.NewCluster("localhost")
cluster.Keyspace = "my_keyspace"
cluster.Consistency = gocql.Quorum
session, initErr = cluster.CreateSession()
})
return session, initErr
}
// Close closes the session
func Close() {
if session != nil {
session.Close()
}
}
// Usage in your app
package main
import (
"myapp/db"
"log"
)
func main() {
// Get session (created once)
session, err := db.GetSession()
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Use session in handlers...
}
📝 Basic Query Operations
CRUD operations!
SELECT Queries
// Simple select
var name, email string
var age int
iter := session.Query(`
SELECT name, email, age FROM users
`).Iter()
for iter.Scan(&name, &email, &age) {
fmt.Printf("%s: %s (%d)\n", name, email, age)
}
if err := iter.Close(); err != nil {
log.Fatal(err)
}
// With WHERE clause
var userName string
err := session.Query(`
SELECT name FROM users WHERE user_id = ?
`, userId).Scan(&userName)
if err == gocql.ErrNotFound {
fmt.Println("User not found")
} else if err != nil {
log.Fatal(err)
}
// Map scan (flexible columns)
m := map[string]interface{}{}
err = session.Query(`
SELECT * FROM users WHERE user_id = ?
`, userId).MapScan(m)
if err == nil {
fmt.Println("Name:", m["name"])
fmt.Println("Email:", m["email"])
}
INSERT Queries
// Simple insert
userId := gocql.TimeUUID()
err := session.Query(`
INSERT INTO users (user_id, name, email, age)
VALUES (?, ?, ?, ?)
`, userId, "Bob", "bob@example.com", 30).Exec()
if err != nil {
log.Fatal(err)
}
// Insert with TTL
err = session.Query(`
INSERT INTO sessions (session_id, user_id)
VALUES (?, ?) USING TTL ?
`, sessionId, userId, 3600).Exec()
// Conditional insert (IF NOT EXISTS)
applied, err := session.Query(`
INSERT INTO users (user_id, name, email)
VALUES (?, ?, ?)
IF NOT EXISTS
`, userId, "Charlie", "charlie@example.com").ScanCAS(nil)
if applied {
fmt.Println("User created")
} else {
fmt.Println("User already exists")
}
UPDATE Queries
// Simple update
err := session.Query(`
UPDATE users
SET age = ?, email = ?
WHERE user_id = ?
`, 31, "newemail@example.com", userId).Exec()
if err != nil {
log.Fatal(err)
}
// Conditional update (lightweight transaction)
applied, err := session.Query(`
UPDATE users
SET age = ?
WHERE user_id = ?
IF age = ?
`, 32, userId, 31).ScanCAS(nil)
if applied {
fmt.Println("Update successful")
}
// Increment counter
err = session.Query(`
UPDATE page_views
SET views = views + 1
WHERE page_id = ?
`, pageId).Exec()
DELETE Queries
// Delete row
err := session.Query(`
DELETE FROM users WHERE user_id = ?
`, userId).Exec()
if err != nil {
log.Fatal(err)
}
// Delete specific columns
err = session.Query(`
DELETE email, age FROM users WHERE user_id = ?
`, userId).Exec()
// Conditional delete
applied, err := session.Query(`
DELETE FROM users
WHERE user_id = ?
IF age > ?
`, userId, 50).ScanCAS(nil)
if applied {
fmt.Println("User deleted")
}
⚡ Concurrency with Goroutines
Leverage Go's concurrency!
Why Go + Cassandra?
- ✅ Goroutines: Lightweight concurrent queries
- ✅ Channels: Coordinate parallel operations
- ✅ Performance: Handle thousands of concurrent requests
- ✅ Session safe: gocql.Session is goroutine-safe
Parallel Queries
package main
import (
"fmt"
"sync"
"github.com/gocql/gocql"
)
// Query users in parallel
func getUsersParallel(session *gocql.Session, userIds []gocql.UUID) {
var wg sync.WaitGroup
results := make(chan string, len(userIds))
// Launch goroutine for each user
for _, userId := range userIds {
wg.Add(1)
go func(id gocql.UUID) {
defer wg.Done()
var name string
err := session.Query(`
SELECT name FROM users WHERE user_id = ?
`, id).Scan(&name)
if err == nil {
results <- name
}
}(userId)
}
// Wait for all goroutines
go func() {
wg.Wait()
close(results)
}()
// Collect results
for name := range results {
fmt.Println("Found user:", name)
}
}
Worker Pool Pattern
// Worker pool for bulk inserts
func bulkInsert(session *gocql.Session, users []User) error {
numWorkers := 10
jobs := make(chan User, len(users))
errors := make(chan error, len(users))
// Start workers
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for user := range jobs {
err := session.Query(`
INSERT INTO users (user_id, name, email, age)
VALUES (?, ?, ?, ?)
`, user.ID, user.Name, user.Email, user.Age).Exec()
if err != nil {
errors <- err
}
}
}()
}
// Send jobs
for _, user := range users {
jobs <- user
}
close(jobs)
// Wait for completion
wg.Wait()
close(errors)
// Check for errors
for err := range errors {
return err // Return first error
}
return nil
}
Rate Limiting
import "golang.org/x/time/rate"
// Rate-limited queries
func queryWithRateLimit(session *gocql.Session, userIds []gocql.UUID) {
// 100 queries per second
limiter := rate.NewLimiter(100, 10)
var wg sync.WaitGroup
for _, userId := range userIds {
// Wait for rate limiter
limiter.Wait(context.Background())
wg.Add(1)
go func(id gocql.UUID) {
defer wg.Done()
var name string
session.Query(`
SELECT name FROM users WHERE user_id = ?
`, id).Scan(&name)
}(userId)
}
wg.Wait()
}
📦 Batch Queries
Atomic operations!
import "github.com/gocql/gocql"
// Create batch
batch := session.NewBatch(gocql.LoggedBatch)
// Add statements to batch
batch.Query(`
INSERT INTO users (user_id, name) VALUES (?, ?)
`, gocql.TimeUUID(), "User1")
batch.Query(`
INSERT INTO users (user_id, name) VALUES (?, ?)
`, gocql.TimeUUID(), "User2")
batch.Query(`
INSERT INTO users (user_id, name) VALUES (?, ?)
`, gocql.TimeUUID(), "User3")
// Execute batch (atomic)
err := session.ExecuteBatch(batch)
if err != nil {
log.Fatal(err)
}
// WARNING: Only batch operations on SAME partition!
// Don't batch unrelated data!
Batch Types
| Type | Use Case | Performance |
|---|---|---|
| LoggedBatch | Atomic operations (default) | Slower (uses batchlog) |
| UnloggedBatch | Same partition updates | Faster (no batchlog) |
| CounterBatch | Counter updates only | Optimized for counters |
🚨 Error Handling
Handle failures gracefully!
Common Errors
import (
"github.com/gocql/gocql"
"fmt"
)
// Check specific errors
var name string
err := session.Query(`
SELECT name FROM users WHERE user_id = ?
`, userId).Scan(&name)
if err == gocql.ErrNotFound {
fmt.Println("User not found")
} else if err == gocql.ErrUnavailable {
fmt.Println("Cassandra unavailable")
} else if err != nil {
fmt.Println("Query error:", err)
}
Retry Logic
import "time"
// Retry with exponential backoff
func executeWithRetry(session *gocql.Session, query string, args ...interface{}) error {
maxRetries := 3
for attempt := 0; attempt < maxRetries; attempt++ {
err := session.Query(query, args...).Exec()
if err == nil {
return nil // Success!
}
// Check if retryable
if err == gocql.ErrNotFound {
return err // Don't retry
}
// Last attempt?
if attempt == maxRetries-1 {
return err
}
// Exponential backoff: 1s, 2s, 4s
waitTime := time.Duration(1<"Retry %d/%d after %v\n", attempt+1, maxRetries, waitTime)
time.Sleep(waitTime)
}
return fmt.Errorf("all retries failed")
}
Context with Timeout
import (
"context"
"time"
)
// Query with timeout
func queryWithTimeout(session *gocql.Session, userId gocql.UUID) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
var name string
err := session.Query(`
SELECT name FROM users WHERE user_id = ?
`, userId).WithContext(ctx).Scan(&name)
if err == context.DeadlineExceeded {
return "", fmt.Errorf("query timeout")
}
return name, err
}
💡 Go Driver Best Practices
Production tips!
DO
- Use singleton pattern for session
- Leverage goroutines for parallelism
- Use worker pools for bulk operations
- Set appropriate timeouts
- Use rate limiting for API endpoints
- Close iterators after use
- Use context for cancellation
- Monitor query performance
DON'T
- Create session per request
- Ignore iterator.Close() errors
- Block goroutines unnecessarily
- Use unbounded goroutines
- Forget error handling
- Mix consistency levels carelessly
- Use string concatenation for queries
- Skip connection pooling config
Performance Tips
| Technique | Benefit | When to Use |
|---|---|---|
| Goroutines | Parallel queries | Multiple independent queries |
| Worker Pools | Controlled concurrency | Bulk operations |
| Batch Queries | Atomic operations | Same partition updates |
| Connection Pooling | Resource efficiency | Always (configure NumConns) |
| Context Timeout | Prevent hanging | User-facing APIs |
Complete Production Example
// repository.go - Production-ready
package repository
import (
"context"
"fmt"
"time"
"github.com/gocql/gocql"
)
type UserRepository struct {
session *gocql.Session
}
func NewUserRepository(session *gocql.Session) *UserRepository {
return &UserRepository{session: session}
}
func (r *UserRepository) Create(ctx context.Context, userId gocql.UUID, name, email string, age int) error {
return r.session.Query(`
INSERT INTO users (user_id, name, email, age)
VALUES (?, ?, ?, ?)
`, userId, name, email, age).WithContext(ctx).Exec()
}
func (r *UserRepository) FindByID(ctx context.Context, userId gocql.UUID) (*User, error) {
var user User
err := r.session.Query(`
SELECT user_id, name, email, age FROM users WHERE user_id = ?
`, userId).WithContext(ctx).Scan(
&user.ID,
&user.Name,
&user.Email,
&user.Age,
)
if err == gocql.ErrNotFound {
return nil, fmt.Errorf("user not found")
}
return &user, err
}
func (r *UserRepository) Update(ctx context.Context, userId gocql.UUID, name, email string, age int) error {
return r.session.Query(`
UPDATE users SET name = ?, email = ?, age = ?
WHERE user_id = ?
`, name, email, age, userId).WithContext(ctx).Exec()
}
func (r *UserRepository) Delete(ctx context.Context, userId gocql.UUID) error {
return r.session.Query(`
DELETE FROM users WHERE user_id = ?
`, userId).WithContext(ctx).Exec()
}
// Usage
func main() {
session, _ := createSession()
defer session.Close()
repo := NewUserRepository(session)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
userId := gocql.TimeUUID()
repo.Create(ctx, userId, "Alice", "alice@example.com", 28)
user, _ := repo.FindByID(ctx, userId)
fmt.Println(user.Name)
}
🎉 Master Go + Cassandra!
You now know how to build high-performance Go apps with Cassandra!
🎓 What You Learned:
- 📦 Installation: go get github.com/gocql/gocql
- 🚀 Quick start: Complete example with cluster setup
- 🔌 Connection: Basic, production-ready, singleton pattern
- 📝 Basic queries: SELECT, INSERT, UPDATE, DELETE, BATCH
- ⚡ Concurrency: Goroutines, worker pools, rate limiting
- 📦 Batch queries: Atomic operations with 3 batch types
- 🚨 Error handling: Common errors, retry logic, context timeout
- 💡 Best practices: Production patterns with repository
💡 Key Takeaways:
- Singleton session - Create once with sync.Once
- Leverage goroutines - Parallel queries for performance
- Worker pools - Control concurrency for bulk operations
- Context timeout - Prevent hanging queries
- Close iterators - Always check iter.Close() errors
- Rate limiting - Protect Cassandra from overload
🐹 Quick Reference:
// Install
$ go get github.com/gocql/gocql
// Connect (singleton)
cluster := gocql.NewCluster("localhost")
session, _ := cluster.CreateSession()
defer session.Close()
// Query
var name string
session.Query("SELECT name FROM users WHERE id = ?", id).Scan(&name)
// Concurrent queries
go func() {
session.Query(...).Exec()
}()
// Context timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
session.Query(...).WithContext(ctx).Exec()
🐹 Go + Cassandra = Cloud-native speed! 🎯
Build scalable microservices now!
Advertisement
📱 Responsive Ad 📱