EmptyClassroom uses Redis to cache classroom availability data with a 24-hour TTL (time-to-live). This reduces load on the BU scheduling API and provides fast response times for users.
The caching strategy balances freshness (data updates daily) with performance (sub-100ms response times) and API politeness (minimal requests to BU systems).
By default, Redis returns byte strings (b'value'). Setting decode_responses=True automatically converts these to Python strings, eliminating the need for manual .decode('utf-8') calls throughout the codebase.
The /api/open-classrooms endpoint checks cache first:
backend/main.py
@app.get('/api/open-classrooms')async def get_classroom_availability_by_building(): try: # Check cache first cache = rd.get('classrooms:availability') if cache: print('Cache hit') availability_data = json.loads(cache) else: print('Cache miss - fetching new data') availability_data = await get_classroom_availability() rd.set('classrooms:availability', json.dumps(availability_data), ex=CACHE_EXPIRY) # Update last refresh timestamp when fetching new data now = datetime.now(pytz.timezone('America/New_York')) rd.set('classrooms:last_refresh', now.isoformat(), ex=CACHE_EXPIRY) # ... organize and return data
Cache Hit (Fast)
Cache Miss (Slow)
User requests /api/open-classrooms
Backend calls rd.get('classrooms:availability')
Redis returns cached JSON string
Backend parses JSON and returns data
Response time: ~50-100ms
User requests /api/open-classrooms
Backend calls rd.get('classrooms:availability')
Redis returns None (key doesn’t exist or expired)
Backend fetches from BU API (~77 concurrent requests)
Backend stores result in Redis with 24h TTL
Backend returns data
Response time: ~500-1500ms (first request of the day)
Wake-up refresh logic automatically refreshes data on startup if needed:
backend/main.py
def should_refresh_on_wake(): try: last_refresh_key = 'classrooms:last_refresh' last_refresh_str = rd.get(last_refresh_key) if not last_refresh_str: return True # No previous refresh, should refresh last_refresh = datetime.fromisoformat(last_refresh_str) now = datetime.now(pytz.timezone('America/New_York')) # Refresh if data not fetched today return last_refresh.date() < now.date() except Exception as e: print(f'Error checking if should refresh on wake: {str(e)}') return True # Default to refreshing on error@app.on_event('startup')async def startup_event(): # Wait for Redis to be ready for i in range(5): try: rd.ping() print('Redis connection established') break except Exception: print(f'Waiting for Redis to be ready... (attempt {i+1}/5)') await asyncio.sleep(1) try: print('App starting up - checking if refresh is needed') # Check if refresh needed if should_refresh_on_wake(): print('App was sleeping or no recent data - fetching fresh data') await update_cache() # Set refresh timestamp now = datetime.now(pytz.timezone('America/New_York')) rd.set('classrooms:last_refresh', now.isoformat(), ex=CACHE_EXPIRY) print('Wake-up refresh completed successfully') else: print('Recent data available, skipping wake-up refresh') except Exception as e: print(f'Failed to handle wake-up refresh: {str(e)}')
Result: ⏭️ Skip refresh (today’s data still valid)
Situation:
Redis was restarted/cleared
classrooms:last_refresh doesn’t exist
Logic:
last_refresh_str = Nonereturn True # No previous refresh
Result: ✅ Refresh on startup (rebuild cache)
Wake-up refresh happens asynchronously during startup. The app is ready to serve requests even if the refresh is still in progress (though those requests will be slow until cache is populated).
The app waits for Redis to be ready before processing requests:
backend/main.py
@app.on_event('startup')async def startup_event(): # Wait for Redis to be ready for i in range(5): try: rd.ping() print('Redis connection established') break except Exception: print(f'Waiting for Redis to be ready... (attempt {i+1}/5)') await asyncio.sleep(1)
Retry logic:
Attempts: 5 retries
Delay: 1 second between attempts
Total timeout: ~5 seconds
On failure: App continues (requests will fail until Redis is available)
Connect to Railway Redis via CLI to inspect cache:
# Get cache valueredis-cli -u $REDIS_URL GET classrooms:availability# Get last refresh timestampredis-cli -u $REDIS_URL GET classrooms:last_refresh# Check TTL (time remaining)redis-cli -u $REDIS_URL TTL classrooms:availability# Returns: 43200 (12 hours remaining in seconds)# Clear cache (force refresh on next request)redis-cli -u $REDIS_URL DEL classrooms:availability classrooms:last_refresh