Final Project and Beyond

🎉 Capstone Project: Build Your Production Database

Congratulations! You've learned all the fundamentals. Now it's time to build a complete, production-ready distributed key-value database with advanced features and real-world deployment.

Estimated Time: 20-30 hours | Difficulty: Advanced | Prerequisites: All previous modules

Lesson 15.1: Advanced Features Implementation

Feature 1: Time-To-Live (TTL)

TTL allows keys to automatically expire after a duration, essential for caching and session management.

// TTLManager handles key expiration
type TTLManager struct {
    store       Store
    expirations map[string]time.Time
    mu          sync.RWMutex
    ticker      *time.Ticker
    done        chan struct
}

// SetWithTTL stores value with expiration time
func (tm *TTLManager) SetWithTTL(key []byte, value []byte, ttl time.Duration) error {
    keyStr := string(key)
    
    // Store value
    if err := tm.store.Put(key, value); err != nil {
        return err
    }
    
    // Record expiration time
    tm.mu.Lock()
    tm.expirations[keyStr] = time.Now().Add(ttl)
    tm.mu.Unlock()
    
    return nil
}

// Get returns value if not expired
func (tm *TTLManager) Get(key []byte) ([]byte, error) {
    keyStr := string(key)
    
    tm.mu.RLock()
    expireTime, exists := tm.expirations[keyStr]
    tm.mu.RUnlock()
    
    // Check if expired
    if exists && time.Now().After(expireTime) {
        tm.store.Delete(key)
        tm.mu.Lock()
        delete(tm.expirations, keyStr)
        tm.mu.Unlock()
        return nil, fmt.Errorf("key expired")
    }
    
    return tm.store.Get(key)
}

// Start begins cleanup of expired keys
func (tm *TTLManager) Start() {
    tm.ticker = time.NewTicker(1 * time.Second)
    
    go func() {
        for {
            select {
            case <-tm.ticker.C:
                tm.cleanupExpired()
            case <-tm.done:
                return
            }
        }
    }()
}

// cleanupExpired removes all expired keys
func (tm *TTLManager) cleanupExpired() {
    tm.mu.Lock()
    defer tm.mu.Unlock()
    
    now := time.Now()
    for key, expireTime := range tm.expirations {
        if now.After(expireTime) {
            tm.store.Delete([]byte(key))
            delete(tm.expirations, key)
        }
    }
}

Feature 2: Pub/Sub Messaging

Pub/Sub allows publishers to send messages to multiple subscribers, enabling real-time communication patterns.

// PubSubManager handles publish/subscribe
type PubSubManager struct {
    subscribers map[string][]*Subscriber
    mu          sync.RWMutex
}

type Subscriber struct {
    ID      string
    Channel string
    Messages chan []byte
}

// Subscribe adds subscriber to channel
func (ps *PubSubManager) Subscribe(channel string) *Subscriber {
    subscriber := &Subscriber{
        ID:       generateID(),
        Channel:  channel,
        Messages: make(chan []byte, 100),
    }
    
    ps.mu.Lock()
    ps.subscribers[channel] = append(ps.subscribers[channel], subscriber)
    ps.mu.Unlock()
    
    return subscriber
}

// Publish sends message to all subscribers
func (ps *PubSubManager) Publish(channel string, message []byte) int {
    ps.mu.RLock()
    subscribers, exists := ps.subscribers[channel]
    ps.mu.RUnlock()
    
    if !exists {
        return 0
    }
    
    count := 0
    for _, sub := range subscribers {
        select {
        case sub.Messages <- message:
            count++
        default:
            // Subscriber queue full, skip
        }
    }
    
    return count
}

// Unsubscribe removes subscriber
func (ps *PubSubManager) Unsubscribe(subscriber *Subscriber) {
    ps.mu.Lock()
    defer ps.mu.Unlock()
    
    subscribers, exists := ps.subscribers[subscriber.Channel]
    if !exists {
        return
    }
    
    // Remove subscriber
    for i, sub := range subscribers {
        if sub.ID == subscriber.ID {
            ps.subscribers[subscriber.Channel] = append(
                subscribers[:i],
                subscribers[i+1:]...,
            )
            close(sub.Messages)
            break
        }
    }
}

Feature 3: Lua Scripting

Lua scripting allows atomic operations on multiple keys, enabling complex business logic at the database level.

import "github.com/yuin/gopher-lua"

// LuaEngine executes Lua scripts
type LuaEngine struct {
    store Store
    vm    *lua.LState
}

// NewLuaEngine creates Lua engine
func NewLuaEngine(store Store) *LuaEngine {
    L := lua.NewState()
    
    engine := &LuaEngine{
        store: store,
        vm:    L,
    }
    
    // Register database functions
    engine.registerFunctions()
    
    return engine
}

// registerFunctions registers Go functions for Lua
func (le *LuaEngine) registerFunctions() {
    le.vm.SetGlobal("get", le.vm.NewFunction(le.luaGet))
    le.vm.SetGlobal("set", le.vm.NewFunction(le.luaSet))
    le.vm.SetGlobal("del", le.vm.NewFunction(le.luaDel))
}

// luaGet implements get() in Lua
func (le *LuaEngine) luaGet(L *lua.LState) int {
    key := L.ToString(1)
    
    value, err := le.store.Get([]byte(key))
    if err != nil {
        L.Push(lua.LNil)
        return 1
    }
    
    L.Push(lua.LString(value))
    return 1
}

// Execute runs Lua script
func (le *LuaEngine) Execute(script string) (interface, error) {
    if err := le.vm.DoString(script); err != nil {
        return nil, err
    }
    
    result := le.vm.Get(-1)
    return result, nil
}

Feature 4: Geo-spatial Queries

Geo-spatial queries allow querying by geographic location, essential for location-based applications.

import "math"

// GeoPoint represents latitude/longitude
type GeoPoint struct {
    Latitude  float64
    Longitude float64
}

// GeoManager handles geographic data
type GeoManager struct {
    store Store
    index map[string]*GeoPoint
    mu    sync.RWMutex
}

// AddLocation stores location with key
func (gm *GeoManager) AddLocation(key string, point GeoPoint) error {
    gm.mu.Lock()
    gm.index[key] = &point
    gm.mu.Unlock()
    
    // Store in main store as well
    data, _ := json.Marshal(point)
    return gm.store.Put([]byte(key), data)
}

// Distance calculates distance in km using Haversine formula
func (gm *GeoManager) Distance(point1, point2 GeoPoint) float64 {
    const R = 6371 // Earth radius in km
    
    lat1 := point1.Latitude * math.Pi / 180
    lat2 := point2.Latitude * math.Pi / 180
    deltaLat := (point2.Latitude - point1.Latitude) * math.Pi / 180
    deltaLon := (point2.Longitude - point1.Longitude) * math.Pi / 180
    
    a := math.Sin(deltaLat/2)*math.Sin(deltaLat/2) +
        math.Cos(lat1)*math.Cos(lat2)*
            math.Sin(deltaLon/2)*math.Sin(deltaLon/2)
    
    c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
    
    return R * c
}

// NearbyLocations finds locations within radius
func (gm *GeoManager) NearbyLocations(center GeoPoint, radiusKm float64) []string {
    gm.mu.RLock()
    defer gm.mu.RUnlock()
    
    var nearby []string
    
    for key, point := range gm.index {
        distance := gm.Distance(center, *point)
        if distance <= radiusKm {
            nearby = append(nearby, key)
        }
    }
    
    return nearby
}

Lesson 15.2: Documentation

API Documentation

# KVDB API Reference

## Commands

### GET
Retrieve value for key
```
GET key
```
Response: bulk string or null

### SET
Store value for key
```
SET key value [EX seconds] [PX milliseconds]
```
Response: OK

### DEL
Delete key
```
DEL key [key ...]
```
Response: integer (number deleted)

### TTL
Get remaining TTL in seconds
```
TTL key
```
Response: integer (-2 if not exists, -1 if no expiry)

### EXPIRE
Set expiration in seconds
```
EXPIRE key seconds
```
Response: 1 if set, 0 if not exists

### PUBLISH
Publish message to channel
```
PUBLISH channel message
```
Response: integer (number of subscribers)

### SUBSCRIBE
Subscribe to channel
```
SUBSCRIBE channel [channel ...]
```
Response: array of messages

### EVAL
Execute Lua script
```
EVAL script numkeys [key ...] [arg ...]
```
Response: script result

Operational Runbook

# KVDB Operational Runbook

## Starting Server
```bash
./kvdb -config config.yaml
```

## Health Checks
- Liveness: GET /healthz
- Readiness: GET /readyz

## Backup Procedures
```bash
# Create backup
curl -X POST http://localhost:8080/backup

# List backups
curl http://localhost:8080/backups

# Restore backup
curl -X POST http://localhost:8080/restore/backup-name
```

## Scaling Cluster
```bash
# Add new node
kubectl scale deployment kvdb --replicas=5

# Remove node
kubectl scale deployment kvdb --replicas=3
```

## Troubleshooting
- High latency: Check p99 latency in metrics
- Memory growth: Check for memory leaks with pprof
- Replication lag: Monitor replication metrics

Lesson 15.3: Production Deployment

Pre-Deployment Checklist

✅ Pre-Deployment Checklist

  • □ All tests pass (>80% coverage)
  • □ Performance benchmarks meet targets
  • □ Security audit completed
  • □ Load testing completed
  • □ Backup/restore tested
  • □ Documentation complete
  • □ Monitoring configured
  • □ Health checks working
  • □ Rate limiting configured
  • □ TLS certificates ready
  • □ Database backups available
  • □ Rollback plan documented

Deployment Script

#!/bin/bash
# deploy.sh - Production deployment script

set -e

VERSION=$1
ENVIRONMENT=$2

if [ -z "$VERSION" ] || [ -z "$ENVIRONMENT" ]; then
    echo "Usage: ./deploy.sh <version> <environment>"
    exit 1
fi

echo "Starting deployment of version $VERSION to $ENVIRONMENT"

# 1. Build Docker image
echo "Building Docker image..."
docker build -t kvdb:$VERSION .

# 2. Push to registry
echo "Pushing to registry..."
docker tag kvdb:$VERSION registry.example.com/kvdb:$VERSION
docker push registry.example.com/kvdb:$VERSION

# 3. Update Kubernetes deployment
echo "Updating Kubernetes deployment..."
kubectl set image deployment/kvdb kvdb=registry.example.com/kvdb:$VERSION

# 4. Wait for rollout
echo "Waiting for rollout..."
kubectl rollout status deployment/kvdb --timeout=5m

# 5. Verify health
echo "Verifying health..."
for i in {1..10}; do
    if kubectl exec -it deployment/kvdb -- curl http://localhost:8080/readyz; then
        echo "Health check passed"
        exit 0
    fi
    sleep 10
done

echo "Health check failed"
exit 1

Architecture Overview

KVDB - Distributed Key-Value Database

┌─────────────────────────────────────────┐
│         Client Application              │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│      Client Library (Pooling, Retry)    │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│    RESP Protocol (Redis Compatible)     │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│    TCP Server (10K+ connections)        │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│  Transaction Engine (MVCC + Conflicts)  │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│ Replication Layer (Failover, Consensus)│
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│    Storage Engine (LSM Tree)            │
│  ├─ Memtables (Sorted)                  │
│  ├─ SSTables (Persisted)                │
│  ├─ Compaction (Leveled)                │
│  ├─ Bloom Filters                       │
│  └─ Block Cache                         │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│    WAL + Crash Recovery                 │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│   Sharded In-Memory Store               │
└─────────────────────────────────────────┘

Key Achievements

Performance

  • • 1M+ ops/sec single-node
  • • 10K+ ops/sec distributed
  • • <100ms p99 latency
  • • Sub-millisecond MVCC lookup

Reliability

  • • Zero data loss (WAL + replication)
  • • <500ms automatic failover
  • • ACID transactions
  • • Deadlock detection

Operability

  • • Prometheus metrics
  • • Structured logging
  • • Health checks
  • • Kubernetes ready
  • • TLS encryption

Scalability

  • • Multi-node clusters
  • • Automatic replication
  • • Leader-follower topology
  • • Consistent hashing

Capstone Project Assignment

Project Requirements

Build a complete, production-ready key-value database with all the features we've learned:

Core Engine (Modules 1-3)

  • ✅ Sharded in-memory storage
  • ✅ LSM tree persistence
  • ✅ MVCC transactions
  • ✅ Crash recovery

Networking (Module 4)

  • ✅ TCP server (10K+ connections)
  • ✅ RESP protocol
  • ✅ Client library with pooling

Distribution (Module 5)

  • ✅ Leader-follower replication
  • ✅ Automatic failover
  • ✅ Consensus-based

Production (Modules 6-7)

  • ✅ Prometheus metrics
  • ✅ Structured logging
  • ✅ Docker & Kubernetes
  • ✅ TLS security

Advanced Features (Module 8)

Implement at least 2 of the following advanced features:

  • Time-To-Live (TTL): Automatic key expiration
  • Pub/Sub Messaging: Publish-subscribe pattern
  • Lua Scripting: Atomic multi-key operations
  • Geo-spatial Queries: Location-based queries

Deliverables

1. Source Code

  • • Organized project structure
  • • >80% test coverage
  • • Clean code (gofmt compliant)
  • • Comprehensive error handling

2. Documentation

  • • API reference
  • • Operational runbook
  • • Architecture decision records
  • • Performance tuning guide

3. Deployment

  • • Working Docker image
  • • Kubernetes manifests
  • • Backup/restore procedures
  • • Health checks

4. Testing

  • • Unit tests (>80% coverage)
  • • Integration tests
  • • Load tests
  • • Chaos engineering tests

Grading Rubric

Criteria Excellent Good Fair
Functionality All features working Most features work Basic features only
Performance 1M+ ops/sec 100K+ ops/sec 10K+ ops/sec
Reliability <1% error, zero data loss <5% error >10% error
Code Quality Clean, tested Tested Minimal tests

🎉 Course Complete!

Congratulations! You've successfully completed the comprehensive "Build a Key-Value Database in Go" course.

What You've Built

A production-ready, distributed key-value database in Go featuring:

  • • Multi-node replication with automatic failover
  • • ACID transactions with MVCC
  • • LSM tree persistence with Bloom filters
  • • Redis-compatible protocol
  • • Kubernetes deployment ready
  • • Comprehensive monitoring
  • • Advanced features (TTL, Pub/Sub, Lua, Geo)

Course Statistics

15
Weeks of Material
4000+
Lines of Content
80-100
Hours of Learning

Next Steps

  1. 1. Deploy to production - Use the Kubernetes manifests you've created
  2. 2. Add more features - Implement additional advanced features
  3. 3. Optimize performance - Profile and tune for specific workloads
  4. 4. Contribute to open source - Consider releasing as open source
  5. 5. Build on it - Use as foundation for new projects

Resources

  • Source code: https://github.com/your-username/kvdb
  • Documentation: https://kvdb.example.com/docs
  • Issues: https://github.com/your-username/kvdb/issues

🚀 Congratulations!

You've successfully built a complete, production-grade distributed database system.

This is a significant achievement. Well done!