kevo/pkg/client
2025-05-17 14:58:26 -06:00
..
client_test.go test: add tests for edge case putting a key after delete 2025-04-23 16:49:38 -06:00
client.go fix: fixes issue with sstables not having sequence saved so older data can be retrieved after an update 2025-05-17 14:58:26 -06:00
iterator.go feat: add suffix scanning 2025-04-23 09:00:26 -06:00
options_test.go chore: formatting 2025-04-22 14:09:54 -06:00
README.md chore: update the module path in grpc proto file 2025-05-02 23:51:11 -06:00
replication_test.go feat: Update client sdk (Go) with smart connection logic 2025-04-29 15:03:03 -06:00
simple_test.go chore: formatting 2025-04-22 14:09:54 -06:00
transaction.go chore: formatting 2025-04-22 14:09:54 -06:00
utils.go chore: formatting 2025-04-22 14:09:54 -06:00

Kevo Go Client SDK

This package provides a Go client for connecting to a Kevo database server. The client uses the gRPC transport layer to communicate with the server and provides an idiomatic Go API for working with Kevo.

Features

  • Simple key-value operations (Get, Put, Delete)
  • Batch operations for atomic writes
  • Transaction support with ACID guarantees
  • Iterator API for efficient range scans
  • Connection pooling and automatic retries
  • TLS support for secure communication
  • Comprehensive error handling
  • Configurable timeouts and backoff strategies

Installation

go get github.com/KevoDB/kevo

Quick Start

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/KevoDB/kevo/pkg/client"
	_ "github.com/KevoDB/kevo/pkg/grpc/transport" // Register gRPC transport
)

func main() {
	// Create a client with default options
	options := client.DefaultClientOptions()
	options.Endpoint = "localhost:50051"

	c, err := client.NewClient(options)
	if err != nil {
		log.Fatalf("Failed to create client: %v", err)
	}

	// Connect to the server
	ctx := context.Background()
	if err := c.Connect(ctx); err != nil {
		log.Fatalf("Failed to connect: %v", err)
	}
	defer c.Close()

	// Basic key-value operations
	key := []byte("hello")
	value := []byte("world")

	// Store a value
	if _, err := c.Put(ctx, key, value, true); err != nil {
		log.Fatalf("Put failed: %v", err)
	}

	// Retrieve a value
	val, found, err := c.Get(ctx, key)
	if err != nil {
		log.Fatalf("Get failed: %v", err)
	}

	if found {
		fmt.Printf("Value: %s\n", val)
	} else {
		fmt.Println("Key not found")
	}

	// Delete a value
	if _, err := c.Delete(ctx, key, true); err != nil {
		log.Fatalf("Delete failed: %v", err)
	}
}

Configuration Options

The client can be configured using the ClientOptions struct:

options := client.ClientOptions{
	// Connection options
	Endpoint:        "localhost:50051",
	ConnectTimeout:  5 * time.Second,
	RequestTimeout:  10 * time.Second,
	TransportType:   "grpc",
	PoolSize:        5,

	// Security options
	TLSEnabled:      true,
	CertFile:        "/path/to/cert.pem",
	KeyFile:         "/path/to/key.pem",
	CAFile:          "/path/to/ca.pem",

	// Retry options
	MaxRetries:      3,
	InitialBackoff:  100 * time.Millisecond,
	MaxBackoff:      2 * time.Second,
	BackoffFactor:   1.5,
	RetryJitter:     0.2,

	// Performance options
	Compression:     client.CompressionGzip,
	MaxMessageSize:  16 * 1024 * 1024, // 16MB
}

Transactions

// Begin a transaction
tx, err := client.BeginTransaction(ctx, false) // readOnly=false
if err != nil {
	log.Fatalf("Failed to begin transaction: %v", err)
}

// Perform operations within the transaction
success, err := tx.Put(ctx, []byte("key1"), []byte("value1"))
if err != nil {
	tx.Rollback(ctx) // Rollback on error
	log.Fatalf("Transaction put failed: %v", err)
}

// Commit the transaction
if err := tx.Commit(ctx); err != nil {
	log.Fatalf("Transaction commit failed: %v", err)
}

Scans and Iterators

// Set up scan options
scanOptions := client.ScanOptions{
	Prefix:   []byte("user:"),  // Optional prefix
	StartKey: []byte("user:1"), // Optional start key (inclusive)
	EndKey:   []byte("user:9"), // Optional end key (exclusive)
	Limit:    100,              // Optional limit
}

// Create a scanner
scanner, err := client.Scan(ctx, scanOptions)
if err != nil {
	log.Fatalf("Failed to create scanner: %v", err)
}
defer scanner.Close()

// Iterate through results
for scanner.Next() {
	fmt.Printf("Key: %s, Value: %s\n", scanner.Key(), scanner.Value())
}

// Check for errors after iteration
if err := scanner.Error(); err != nil {
	log.Fatalf("Scan error: %v", err)
}

Batch Operations

// Create a batch of operations
operations := []client.BatchOperation{
	{Type: "put", Key: []byte("key1"), Value: []byte("value1")},
	{Type: "put", Key: []byte("key2"), Value: []byte("value2")},
	{Type: "delete", Key: []byte("old-key")},
}

// Execute the batch atomically
success, err := client.BatchWrite(ctx, operations, true)
if err != nil {
	log.Fatalf("Batch write failed: %v", err)
}

Error Handling and Retries

The client automatically handles retries for transient errors using exponential backoff with jitter. You can configure the retry behavior using the RetryPolicy in the client options.

// Manual retry with custom policy
err = client.RetryWithBackoff(
	ctx,
	func() error {
		_, _, err := c.Get(ctx, key)
		return err
	},
	3,                     // maxRetries
	100*time.Millisecond,  // initialBackoff
	2*time.Second,         // maxBackoff
	2.0,                   // backoffFactor
	0.2,                   // jitter
)

Database Statistics

// Get database statistics
stats, err := client.GetStats(ctx)
if err != nil {
	log.Fatalf("Failed to get stats: %v", err)
}

fmt.Printf("Key count: %d\n", stats.KeyCount)
fmt.Printf("Storage size: %d bytes\n", stats.StorageSize)
fmt.Printf("MemTable count: %d\n", stats.MemtableCount)
fmt.Printf("SSTable count: %d\n", stats.SstableCount)
fmt.Printf("Write amplification: %.2f\n", stats.WriteAmplification)
fmt.Printf("Read amplification: %.2f\n", stats.ReadAmplification)

Compaction

// Trigger compaction
success, err := client.Compact(ctx, false) // force=false
if err != nil {
	log.Fatalf("Compaction failed: %v", err)
}