feat: Add Redis upload cache for multi-replica deployments (#5724)
This commit is contained in:
85
http/upload_cache_memory.go
Normal file
85
http/upload_cache_memory.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package fbhttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/jellydator/ttlcache/v3"
|
||||
)
|
||||
|
||||
const uploadCacheTTL = 3 * time.Minute
|
||||
|
||||
// UploadCache is an interface for tracking active uploads.
|
||||
// Allows for different backends (e.g. in-memory or redis)
|
||||
// to support both single instance and multi replica deployments.
|
||||
type UploadCache interface {
|
||||
// Register stores an upload with its expected file size
|
||||
Register(filePath string, fileSize int64)
|
||||
|
||||
// Complete removes an upload from the cache
|
||||
Complete(filePath string)
|
||||
|
||||
// GetLength returns the expected file size for an active upload
|
||||
GetLength(filePath string) (int64, error)
|
||||
|
||||
// Touch refreshes the TTL for an active upload
|
||||
Touch(filePath string)
|
||||
|
||||
// Close cleans up any resources
|
||||
Close()
|
||||
}
|
||||
|
||||
// memoryUploadCache is an upload cache for single replica deployments
|
||||
type memoryUploadCache struct {
|
||||
cache *ttlcache.Cache[string, int64]
|
||||
}
|
||||
|
||||
func newMemoryUploadCache() *memoryUploadCache {
|
||||
cache := ttlcache.New[string, int64]()
|
||||
cache.OnEviction(func(_ context.Context, reason ttlcache.EvictionReason, item *ttlcache.Item[string, int64]) {
|
||||
if reason == ttlcache.EvictionReasonExpired {
|
||||
fmt.Printf("deleting incomplete upload file: \"%s\"\n", item.Key())
|
||||
os.Remove(item.Key())
|
||||
}
|
||||
})
|
||||
go cache.Start()
|
||||
|
||||
return &memoryUploadCache{cache: cache}
|
||||
}
|
||||
|
||||
func (c *memoryUploadCache) Register(filePath string, fileSize int64) {
|
||||
c.cache.Set(filePath, fileSize, uploadCacheTTL)
|
||||
}
|
||||
|
||||
func (c *memoryUploadCache) Complete(filePath string) {
|
||||
c.cache.Delete(filePath)
|
||||
}
|
||||
|
||||
func (c *memoryUploadCache) GetLength(filePath string) (int64, error) {
|
||||
item := c.cache.Get(filePath)
|
||||
if item == nil {
|
||||
return 0, fmt.Errorf("no active upload found for the given path")
|
||||
}
|
||||
return item.Value(), nil
|
||||
}
|
||||
|
||||
func (c *memoryUploadCache) Touch(filePath string) {
|
||||
c.cache.Touch(filePath)
|
||||
}
|
||||
|
||||
func (c *memoryUploadCache) Close() {
|
||||
c.cache.Stop()
|
||||
}
|
||||
|
||||
// NewUploadCache creates a new upload cache.
|
||||
// If redisURL is empty, an in-memory cache will be used (suitable for single instance deployments).
|
||||
// Otherwise, Redis will be used for the cache (suitable for multi-instance deployments).
|
||||
// The redisURL can include credentials, e.g. redis://user:pass@host:port
|
||||
func NewUploadCache(redisURL string) (UploadCache, error) {
|
||||
if redisURL != "" {
|
||||
return newRedisUploadCache(redisURL)
|
||||
}
|
||||
return newMemoryUploadCache(), nil
|
||||
}
|
||||
Reference in New Issue
Block a user