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:

  1. Singleton session - Create once with sync.Once
  2. Leverage goroutines - Parallel queries for performance
  3. Worker pools - Control concurrency for bulk operations
  4. Context timeout - Prevent hanging queries
  5. Close iterators - Always check iter.Close() errors
  6. 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 📱