Skip to content

Commit d9633bb

Browse files
committed
refactor(cache): improve redis connection handling for serverless
Add lazy connection with timeouts and better environment detection to prevent startup crashes in serverless environments. Fallback to in-memory cache when Redis is unavailable or misconfigured.
1 parent 9a004a1 commit d9633bb

1 file changed

Lines changed: 34 additions & 10 deletions

File tree

backend/app/cache.py

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,37 @@
99
WEATHER_CACHE_TTL = 600
1010
GEOCODING_CACHE_TTL = 3600
1111

12-
# Redis Connection
13-
redis_url = os.getenv("REDIS_URL", "redis://localhost:6379/0")
14-
try:
15-
redis_client = redis.from_url(redis_url, decode_responses=True)
16-
# Test connection
17-
redis_client.ping()
18-
logger.info(f"Connected to Redis at {redis_url}")
19-
except Exception as e:
20-
logger.warning(f"Failed to connect to Redis: {e}. Fallback to in-memory cache.")
21-
redis_client = None
12+
# Redis Connection Setup
13+
# We do NOT connect or ping here to avoid startup crashes in Serverless environments
14+
# if the Redis URL is invalid or unreachable (e.g. localhost in Vercel).
15+
16+
redis_url = os.getenv("REDIS_URL", "")
17+
redis_client = None
18+
19+
# Determine if we should attempt Redis connection
20+
# In Vercel, if REDIS_URL is missing or is localhost, we skip directly to memory cache
21+
should_use_redis = bool(redis_url and "localhost" not in redis_url)
22+
23+
if os.getenv("IS_DOCKER") or os.getenv("IS_LOCAL"):
24+
# Force attempt if explicitly in Docker/Local dev, even if localhost
25+
should_use_redis = bool(redis_url)
26+
27+
if should_use_redis:
28+
try:
29+
# Create client but don't ping immediately (lazy connection)
30+
# Socket timeout is critical to avoid hanging the serverless function
31+
redis_client = redis.from_url(
32+
redis_url,
33+
decode_responses=True,
34+
socket_timeout=2.0,
35+
socket_connect_timeout=2.0
36+
)
37+
logger.info(f"Redis client configured for {redis_url}")
38+
except Exception as e:
39+
logger.warning(f"Failed to configure Redis client: {e}. Using in-memory cache.")
40+
redis_client = None
41+
else:
42+
logger.info("Redis disabled or invalid URL. Using in-memory cache.")
2243

2344
# In-memory fallback
2445
_memory_cache = {}
@@ -30,6 +51,7 @@ def _get_from_redis(key: str) -> Optional[Any]:
3051
val = redis_client.get(key)
3152
return json.loads(val) if val else None
3253
except Exception as e:
54+
# If Redis fails during operation, log and fallback to memory (optional, currently just returns None)
3355
logger.error(f"Redis get error: {e}")
3456
return None
3557

@@ -41,6 +63,8 @@ def _set_in_redis(key: str, value: Any, ttl: int) -> None:
4163
redis_client.setex(key, ttl, json.dumps(value))
4264
except Exception as e:
4365
logger.error(f"Redis set error: {e}")
66+
# Fallback to memory on write failure
67+
_memory_cache[key] = value
4468

4569
def get_geocoding_cache(key: str) -> Optional[List[City]]:
4670
"""Get cached geocoding results"""

0 commit comments

Comments
 (0)