Skip to content

Commit

Permalink
feat: improved caching memory usage (#221)
Browse files Browse the repository at this point in the history
  • Loading branch information
TeKrop authored Nov 26, 2024
1 parent 24df519 commit 62c77bd
Show file tree
Hide file tree
Showing 8 changed files with 34 additions and 20 deletions.
1 change: 0 additions & 1 deletion .env.dist
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ HERO_PATH_CACHE_TIMEOUT=86400
CSV_CACHE_TIMEOUT=86400
CAREER_PATH_CACHE_TIMEOUT=600
SEARCH_ACCOUNT_PATH_CACHE_TIMEOUT=600
SEARCH_DATA_TIMEOUT=7200

# Critical error Discord webhook
DISCORD_WEBHOOK_ENABLED=false
Expand Down
22 changes: 13 additions & 9 deletions app/cache_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,20 @@ def wrapper(self, *args, **kwargs):
return wrapper

@redis_connection_handler
def get_api_cache(self, cache_key: str) -> str | None:
def get_api_cache(self, cache_key: str) -> dict | list | None:
"""Get the API Cache value associated with a given cache key"""
return self.redis_server.get(f"{settings.api_cache_key_prefix}:{cache_key}")
api_cache_key = f"{settings.api_cache_key_prefix}:{cache_key}"
if not (api_cache := self.redis_server.get(api_cache_key)):
return None

return self.__decompress_json_value(api_cache)

@redis_connection_handler
def update_api_cache(self, cache_key: str, value: dict | list, expire: int) -> None:
"""Update or set an API Cache value with an expiration value (in seconds)"""

# Compress the JSON string
str_value = json.dumps(value, separators=(",", ":"))
str_value = self.__compress_json_value(value)

# Store it in API Cache
self.redis_server.set(
Expand Down Expand Up @@ -122,8 +126,8 @@ def update_player_cache(self, player_id: str, value: dict) -> None:
def get_search_data_cache(
self, data_type: SearchDataType, cache_key: str
) -> str | None:
data_cache = self.redis_server.get(
f"{settings.search_data_cache_key_prefix}:{data_type}:{cache_key}",
data_cache = self.redis_server.hget(
f"{settings.search_data_cache_key_prefix}:{data_type}", cache_key
)
return data_cache.decode("utf-8") if data_cache else None

Expand All @@ -133,10 +137,10 @@ def update_search_data_cache(
) -> None:
for data_type, data in search_data.items():
for data_key, data_value in data.items():
self.redis_server.set(
f"{settings.search_data_cache_key_prefix}:{data_type}:{data_key}",
value=data_value,
ex=settings.search_data_timeout,
self.redis_server.hset(
f"{settings.search_data_cache_key_prefix}:{data_type}",
data_key,
data_value,
)

@redis_connection_handler
Expand Down
3 changes: 0 additions & 3 deletions app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,6 @@ class Settings(BaseSettings):
# URI of the page where search data are saved
search_data_path: str = "/search/"

# Cache TTL for search data list
search_data_timeout: int = 7200

############
# BLIZZARD
############
Expand Down
7 changes: 5 additions & 2 deletions build/nginx/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
ARG OPENRESTY_VERSION=1.27.1.1-0

# Use the OpenResty Alpine base image
FROM openresty/openresty:${OPENRESTY_VERSION}-alpine
FROM openresty/openresty:${OPENRESTY_VERSION}-alpine-fat

# Environment variables
ARG OPENRESTY_VERSION
ENV OPENRESTY_VERSION=${OPENRESTY_VERSION}

# For envsubst command in entrypoint
RUN apk add gettext
RUN apk add gettext git zlib-dev

# Install zlib
RUN luarocks install lua-zlib

# Copy Nginx configuration file
COPY overfast-api.conf.template /etc/nginx/conf.d/default.conf.template
Expand Down
15 changes: 13 additions & 2 deletions build/nginx/redis_handler.lua.template
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
local zlib = require "zlib"
local redis = require "resty.redis"

local function handle_redis_request()
Expand All @@ -14,8 +15,8 @@ local function handle_redis_request()

-- Redis operations (e.g., GET, TTL)
local key = "api-cache:" .. ngx.var.request_uri
local value, err = red:get(key)
if not value or value == ngx.null then
local compressed_value, err = red:get(key)
if not compressed_value or compressed_value == ngx.null then
ngx.log(ngx.INFO, "Cache miss for key: ", key)
ngx.exec("@fallback")
return
Expand All @@ -27,6 +28,16 @@ local function handle_redis_request()
return ngx.exit(502)
end

-- Decompress JSON data
local status, value = pcall(function()
return zlib.inflate()(compressed_value)
end)
if not value or value == ngx.null then
ngx.log(ngx.ERR, "Cache error for key: ", key)
ngx.exec("@fallback")
return
end

-- Return response
ngx.header["${CACHE_TTL_HEADER}"] = ttl
ngx.say(value)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "overfast-api"
version = "3.7.0"
version = "3.8.0"
description = "Overwatch API giving data about heroes, maps, and players statistics."
license = {file = "LICENSE"}
authors = [
Expand Down
2 changes: 1 addition & 1 deletion tests/test_cache_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_get_cache_key_from_request(
@pytest.mark.parametrize(
("cache_key", "value", "expire", "sleep_time", "expected"),
[
("/heroes", [{"name": "Sojourn"}], 10, 0, b'[{"name":"Sojourn"}]'),
("/heroes", [{"name": "Sojourn"}], 10, 0, [{"name": "Sojourn"}]),
("/heroes", [{"name": "Sojourn"}], 1, 1, None),
],
)
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 62c77bd

Please sign in to comment.