Initial commit: Open sourcing all of the Maple Open Technologies code.

This commit is contained in:
Bartlomiej Mika 2025-12-02 14:33:08 -05:00
commit 755d54a99d
2010 changed files with 448675 additions and 0 deletions

View file

@ -0,0 +1,114 @@
// File Path: monorepo/cloud/maplepress-backend/pkg/cache/twotier.go
package cache
import (
"context"
"time"
"go.uber.org/zap"
)
// TwoTierCacher defines the interface for two-tier cache operations
type TwoTierCacher interface {
Shutdown(ctx context.Context)
Get(ctx context.Context, key string) ([]byte, error)
Set(ctx context.Context, key string, val []byte) error
SetWithExpiry(ctx context.Context, key string, val []byte, expiry time.Duration) error
Delete(ctx context.Context, key string) error
PurgeExpired(ctx context.Context) error
}
// twoTierCache implements a clean 2-layer (read-through write-through) cache
//
// L1: Redis (fast, in-memory)
// L2: Cassandra (persistent)
//
// On Get: check Redis → then Cassandra → if found in Cassandra → populate Redis
// On Set: write to both
// On SetWithExpiry: write to both with expiry
// On Delete: remove from both
type twoTierCache struct {
redisCache RedisCacher
cassandraCache CassandraCacher
logger *zap.Logger
}
// NewTwoTierCache creates a new two-tier cache instance
func NewTwoTierCache(redisCache RedisCacher, cassandraCache CassandraCacher, logger *zap.Logger) TwoTierCacher {
logger = logger.Named("two-tier-cache")
logger.Info("✓ Two-tier cache initialized (Redis L1 + Cassandra L2)")
return &twoTierCache{
redisCache: redisCache,
cassandraCache: cassandraCache,
logger: logger,
}
}
func (c *twoTierCache) Get(ctx context.Context, key string) ([]byte, error) {
// Try L1 (Redis) first
val, err := c.redisCache.Get(ctx, key)
if err != nil {
return nil, err
}
if val != nil {
c.logger.Debug("cache hit from Redis", zap.String("key", key))
return val, nil
}
// Not in Redis, try L2 (Cassandra)
val, err = c.cassandraCache.Get(ctx, key)
if err != nil {
return nil, err
}
if val != nil {
// Found in Cassandra, populate Redis for future lookups
c.logger.Debug("cache hit from Cassandra, writing back to Redis", zap.String("key", key))
_ = c.redisCache.Set(ctx, key, val) // Best effort, don't fail if Redis write fails
}
return val, nil
}
func (c *twoTierCache) Set(ctx context.Context, key string, val []byte) error {
// Write to both layers
if err := c.redisCache.Set(ctx, key, val); err != nil {
return err
}
if err := c.cassandraCache.Set(ctx, key, val); err != nil {
return err
}
return nil
}
func (c *twoTierCache) SetWithExpiry(ctx context.Context, key string, val []byte, expiry time.Duration) error {
// Write to both layers with expiry
if err := c.redisCache.SetWithExpiry(ctx, key, val, expiry); err != nil {
return err
}
if err := c.cassandraCache.SetWithExpiry(ctx, key, val, expiry); err != nil {
return err
}
return nil
}
func (c *twoTierCache) Delete(ctx context.Context, key string) error {
// Remove from both layers
if err := c.redisCache.Delete(ctx, key); err != nil {
return err
}
if err := c.cassandraCache.Delete(ctx, key); err != nil {
return err
}
return nil
}
func (c *twoTierCache) PurgeExpired(ctx context.Context) error {
// Only Cassandra needs purging (Redis handles TTL automatically)
return c.cassandraCache.PurgeExpired(ctx)
}
func (c *twoTierCache) Shutdown(ctx context.Context) {
c.logger.Info("shutting down two-tier cache")
c.redisCache.Shutdown(ctx)
c.cassandraCache.Shutdown(ctx)
c.logger.Info("two-tier cache shutdown complete")
}