Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,7 @@
## 2026-05-20 - Joined Queries for Integrity Verification
**Learning:** Performing multiple sequential database queries to verify cryptographically chained records (e.g., fetching a record and then its associated token/metadata from another table) introduces unnecessary latency and increases database load.
**Action:** Consolidate associated data retrieval into a single SQL `JOIN` query within the verification hot-path. This reduces database round-trips and improves end-to-end latency for blockchain-style integrity checks.

## 2026-05-22 - Bypass Redundant Python Bounding Box Filtering
**Learning:** Performing point-in-bounding-box checks locally in Python on results already filtered by a database spatial query is redundant and wastes CPU cycles, adding latency to spatial endpoints.
**Action:** Add a `pre_filtered` flag to `find_nearby_issues` to allow callers (like `get_nearby_issues` in `issues.py`) to bypass the local bounding box check when passing a query result set that has already been spatially restricted.
72 changes: 40 additions & 32 deletions backend/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@

logger = logging.getLogger(__name__)


class ThreadSafeCache:
"""
Thread-safe cache implementation with TTL and memory management.
Fixes race conditions and implements proper cache expiration.
Utilizes an OrderedDict for O(1) LRU operations.
"""

def __init__(self, ttl: int = 300, max_size: int = 100):
self._data = collections.OrderedDict()
self._timestamps = collections.OrderedDict()
Expand All @@ -21,14 +22,14 @@ def __init__(self, ttl: int = 300, max_size: int = 100):
self._lock = threading.RLock() # Reentrant lock for thread safety
self._hits = 0
self._misses = 0

def get(self, key: str = "default") -> Optional[Any]:
"""
Thread-safe get operation with automatic cleanup.
"""
with self._lock:
current_time = time.time()

# Check if key exists and is not expired
if key in self._data and key in self._timestamps:
if current_time - self._timestamps[key] < self._ttl:
Expand All @@ -41,44 +42,44 @@ def get(self, key: str = "default") -> Optional[Any]:
else:
# Expired entry - remove it
self._remove_key(key)

self._misses += 1
return None

def set(self, data: Any, key: str = "default") -> None:
"""
Thread-safe set operation with memory management.
"""
with self._lock:
current_time = time.time()

# We don't need to aggressively clean up expired entries on every `set`
# since we have max_size eviction and `get` also checks for expiration.
# This speeds up hot-path cache population.

# If cache is full, evict least recently used entry
if len(self._data) >= self._max_size and key not in self._data:
# First try to clean up expired to free space, if still full, then evict LRU
self._cleanup_expired(current_time)
if len(self._data) >= self._max_size:
self._evict_lru()

# Set new data atomically (adds to end, updating if exists)
self._data[key] = data
self._data.move_to_end(key)
self._timestamps[key] = current_time
self._timestamps.move_to_end(key)
logger.debug(f"Cache set: key={key}, size={len(self._data)}")

logger.debug(f"Cache set operation completed, size={len(self._data)}")

def invalidate(self, key: str = "default") -> None:
"""
Thread-safe invalidation of specific key.
"""
with self._lock:
self._remove_key(key)
logger.debug(f"Cache invalidated: key={key}")
logger.debug(f"Cache invalidated operation completed")

def clear(self) -> None:
"""
Thread-safe clear all cache entries.
Expand All @@ -87,35 +88,34 @@ def clear(self) -> None:
self._data.clear()
self._timestamps.clear()
logger.debug("Cache cleared")

def get_stats(self) -> dict:
"""
Get cache statistics for monitoring.
"""
with self._lock:
current_time = time.time()
expired_count = sum(
1 for ts in self._timestamps.values()
if current_time - ts >= self._ttl
1 for ts in self._timestamps.values() if current_time - ts >= self._ttl
)

return {
"total_entries": len(self._data),
"expired_entries": expired_count,
"max_size": self._max_size,
"ttl_seconds": self._ttl,
"hits": self._hits,
"misses": self._misses
"misses": self._misses,
}

def _remove_key(self, key: str) -> None:
"""
Internal method to remove a key from all tracking dictionaries.
Must be called within lock context.
"""
self._data.pop(key, None)
self._timestamps.pop(key, None)

def _cleanup_expired(self, current_time: Optional[float] = None) -> None:
"""
Internal method to clean up expired entries.
Expand All @@ -133,13 +133,13 @@ def _cleanup_expired(self, current_time: Optional[float] = None) -> None:
expired_keys.append(key)
else:
break

for key in expired_keys:
self._remove_key(key)

if expired_keys:
logger.debug(f"Cleaned up {len(expired_keys)} expired cache entries")

def _evict_lru(self) -> None:
"""
Internal method to evict least recently used entry.
Expand All @@ -152,31 +152,39 @@ def _evict_lru(self) -> None:
try:
lru_key, _ = self._data.popitem(last=False)
self._timestamps.pop(lru_key, None)
logger.debug(f"Evicted LRU cache entry: {lru_key}")
logger.debug(f"Evicted LRU cache entry")
except KeyError:
pass


class SimpleCache:
"""
Backward compatibility wrapper for existing code.
"""

def __init__(self, ttl: int = 60):
self._cache = ThreadSafeCache(ttl=ttl, max_size=50)

def get(self):
return self._cache.get("default")

def set(self, data):
self._cache.set(data=data, key="default")

def invalidate(self):
self._cache.invalidate("default")


# Global instances with improved configuration
recent_issues_cache = ThreadSafeCache(ttl=300, max_size=20) # 5 minutes TTL, max 20 entries
nearby_issues_cache = ThreadSafeCache(ttl=60, max_size=100) # 1 minute TTL, max 100 entries
user_upload_cache = ThreadSafeCache(ttl=3600, max_size=1000) # 1 hour TTL for upload limits
recent_issues_cache = ThreadSafeCache(
ttl=300, max_size=20
) # 5 minutes TTL, max 20 entries
nearby_issues_cache = ThreadSafeCache(
ttl=60, max_size=100
) # 1 minute TTL, max 100 entries
user_upload_cache = ThreadSafeCache(
ttl=3600, max_size=1000
) # 1 hour TTL for upload limits
blockchain_last_hash_cache = ThreadSafeCache(ttl=3600, max_size=1)
grievance_last_hash_cache = ThreadSafeCache(ttl=3600, max_size=1)
resolution_last_hash_cache = ThreadSafeCache(ttl=3600, max_size=1)
Expand All @@ -185,7 +193,7 @@ def invalidate(self):
audit_last_hash_cache = ThreadSafeCache(ttl=3600, max_size=2)
evidence_audit_last_hash_cache = ThreadSafeCache(ttl=3600, max_size=1)
closure_last_hash_cache = ThreadSafeCache(ttl=3600, max_size=1)
user_issues_cache = ThreadSafeCache(ttl=300, max_size=50) # 5 minutes TTL
user_issues_cache = ThreadSafeCache(ttl=300, max_size=50) # 5 minutes TTL
grievance_list_cache = ThreadSafeCache(ttl=300, max_size=50)
escalation_stats_cache = ThreadSafeCache(ttl=300, max_size=10)
visit_stats_cache = ThreadSafeCache(ttl=300, max_size=10)
Loading
Loading