Skip to content

Commit 624224c

Browse files
committed
#828 Persistent change id between sessions
1 parent 6d7c45e commit 624224c

5 files changed

Lines changed: 64 additions & 26 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from contextlib import contextmanager
2+
3+
import redis
4+
5+
from data_retrieval_app.external_data_retrieval.config import settings
6+
7+
8+
@contextmanager
9+
def get_cache():
10+
cache = redis.from_url(str(settings.CACHE_URI), decode_responses=True)
11+
12+
try:
13+
yield cache
14+
finally:
15+
cache.close()

src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/config.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from pydantic import (
2+
AnyUrl,
23
HttpUrl,
34
computed_field,
45
)
6+
from pydantic_core import MultiHostUrl
57
from pydantic_settings import BaseSettings, SettingsConfigDict
68

79

@@ -20,6 +22,22 @@ def BACKEND_BASE_URL(self) -> HttpUrl:
2022
else:
2123
return HttpUrl("http://src-backend-1:8000/api/api_v1")
2224

25+
REDIS_PORT: int = 6379
26+
REDIS_SERVER: str
27+
REDIS_CACHE: str = str(0)
28+
REDIS_PASSWORD: str = ""
29+
30+
@computed_field # type: ignore[prop-decorator]
31+
@property
32+
def CACHE_URI(self) -> AnyUrl:
33+
return MultiHostUrl.build(
34+
scheme="redis",
35+
password=self.REDIS_PASSWORD,
36+
host=self.REDIS_SERVER,
37+
port=self.REDIS_PORT,
38+
path=self.REDIS_CACHE,
39+
)
40+
2341
MANUAL_NEXT_CHANGE_ID: bool
2442
NEXT_CHANGE_ID: str
2543
OATH_ACC_TOKEN_CONTACT_EMAIL: str

src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import httpx
1111
import pandas as pd
12+
import redis
1213

1314
from data_retrieval_app.external_data_retrieval.config import settings
1415
from data_retrieval_app.external_data_retrieval.data_retrieval.utils import (
@@ -102,7 +103,7 @@ def _json_to_df(self, stashes: list) -> pd.DataFrame | None:
102103

103104
return df
104105

105-
def _check_stashes(self, stashes: list) -> pd.DataFrame:
106+
def _detector_filter(self, stashes: list) -> pd.DataFrame:
106107
"""
107108
Parameters:
108109
:param stashes: (list) A list of stash objects
@@ -176,11 +177,7 @@ def _get_latest_change_id(self) -> str:
176177

177178
return next_change_id
178179

179-
def _follow_stream(
180-
self,
181-
client: httpx.Client,
182-
stop_event: threading.Event,
183-
):
180+
def _follow_stream(self, client: httpx.Client, stop_event: threading.Event):
184181
local_pending = []
185182
change_id = self.initial_change_id
186183
weird_errors = 0
@@ -246,7 +243,7 @@ def _follow_stream(
246243
self.pending_queue.put(None)
247244

248245
@sync_timing_tracker
249-
def _process_stream(self) -> pd.DataFrame | None:
246+
def _process_stream(self, cache: redis.Redis) -> pd.DataFrame | None:
250247
i = 0
251248
stashes = []
252249
while i < self.mini_batch_size:
@@ -259,26 +256,25 @@ def _process_stream(self) -> pd.DataFrame | None:
259256
if pending is None:
260257
return stashes
261258

262-
# try:
263259
obj = json.loads(pending.response.decode("utf-8"))
264260
stashes.extend(obj["stashes"])
265-
# finally:
266-
# pending.response.close()
261+
262+
cache.set("next_change_id", pending.next_change_id)
267263

268264
i += 1
269265

270266
logger.info("Stashes are ready for processing")
271-
wanted_df = self._check_stashes(stashes)
267+
wanted_df = self._detector_filter(stashes)
272268
logger.info("Finished processing the data, waiting for more")
273269
if wanted_df.empty:
274270
return None
275271
return wanted_df
276272

277-
def _gather_n_checkpoints(self, n: int) -> pd.DataFrame | None:
273+
def _gather_n_checkpoints(self, cache: redis.Redis, n: int) -> pd.DataFrame | None:
278274
df = None
279275
for _ in range(n):
280276
start_time = time.perf_counter()
281-
wanted_df = self._process_stream()
277+
wanted_df = self._process_stream(cache)
282278
end_time = time.perf_counter()
283279

284280
time_per_mini_batch = end_time - start_time
@@ -299,9 +295,16 @@ def _gather_n_checkpoints(self, n: int) -> pd.DataFrame | None:
299295
return df
300296

301297
def initialize_data_stream_threads(
302-
self, executor: ThreadPoolExecutor, stop_event: threading.Event
298+
self,
299+
executor: ThreadPoolExecutor,
300+
stop_event: threading.Event,
301+
cache: redis.Redis,
303302
):
304-
self.initial_change_id = self._get_latest_change_id()
303+
self.initial_change_id = cache.get("next_change_id")
304+
if self.initial_change_id is None or settings.MANUAL_NEXT_CHANGE_ID:
305+
logger.info("Using manually set change id")
306+
self.initial_change_id = self._get_latest_change_id()
307+
305308
self.rate_limiter = RateLimiter()
306309
self.mini_batch_size = settings.MINI_BATCH_SIZE
307310
self.pending_queue = Queue(maxsize=self.mini_batch_size)
@@ -318,11 +321,12 @@ def initialize_data_stream_threads(
318321
logging.getLogger("httpx").setLevel(logging.WARNING)
319322
return executor.submit(self._follow_stream, client, stop_event)
320323

321-
def dump_stream(self) -> Iterator[pd.DataFrame]:
324+
def dump_stream(self, cache: redis.Redis) -> Iterator[pd.DataFrame]:
322325
time.sleep(5) # Waits for the listening threads to have time to start up.
323326
while True:
324327
logger.info("Waiting for data from the stream")
325328
df = self._gather_n_checkpoints(
329+
cache,
326330
n=settings.N_CHECKPOINTS_PER_TRANSFORMATION,
327331
)
328332
if df is None:

src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
from typing import Any
1111

1212
import pandas as pd
13+
import redis
1314

15+
from data_retrieval_app.external_data_retrieval.cache import get_cache
1416
from data_retrieval_app.external_data_retrieval.config import settings
1517
from data_retrieval_app.external_data_retrieval.data_retrieval.currency_api_handler import (
1618
CurrencyAPIHandler,
@@ -208,7 +210,7 @@ def _initialize_data_stream_threads(
208210
executor, listeners, has_crashed
209211
)
210212

211-
def _follow_data_dump_stream(self):
213+
def _follow_data_dump_stream(self, cache: redis.Redis):
212214
current_hours = find_hours_since_launch(self.leagues)
213215
# Only need to refer to one league to see when a new hour starts
214216
current_hour = current_hours[self.leagues[0]["leagueId"]]
@@ -217,7 +219,7 @@ def _follow_data_dump_stream(self):
217219
modifier_dfs = self._get_modifiers()
218220
item_base_types = self._get_item_base_types()
219221
currency_df = self._get_new_currency_data(current_hours)
220-
get_df = self.poe_api_handler.dump_stream()
222+
get_df = self.poe_api_handler.dump_stream(cache)
221223
while current_hour < next_hour:
222224
df = next(get_df)
223225
split_dfs = self._categorize_new_items(df)
@@ -239,16 +241,15 @@ def retrieve_data(self):
239241
logger.info("Initiating data stream.")
240242
max_workers = 2
241243
stop_event = threading.Event()
242-
with ThreadPoolExecutor(max_workers=max_workers) as executor:
243-
# futures = self._initialize_data_stream_threads(
244-
# executor, listeners=listeners
245-
# )
244+
with ThreadPoolExecutor(
245+
max_workers=max_workers
246+
) as executor, get_cache() as cache:
246247
futures = {}
247248
listener_future = self.poe_api_handler.initialize_data_stream_threads(
248-
executor, stop_event
249+
executor, stop_event, cache
249250
)
250251
futures[listener_future] = "listener"
251-
follow_future = executor.submit(self._follow_data_dump_stream)
252+
follow_future = executor.submit(self._follow_data_dump_stream, cache)
252253
futures[follow_future] = "data_processing"
253254
logger.info("Waiting for futures to crash.")
254255
finished = False

src/docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ services:
3030
retries: 5
3131

3232
cache:
33-
image: redis:7.4.0
33+
image: redis:7.4
3434
restart: always
3535
networks:
3636
- ${TRAEFIK_PUBLIC_NETWORK?Variable not set}
@@ -49,7 +49,7 @@ services:
4949
interval: 1s
5050
timeout: 3s
5151
retries: 5
52-
command: redis-server --save 20 1 --loglevel warning --maxmemory 1000mb --requirepass ${REDIS_PASSWORD?Variable not set}
52+
command: redis-server --save 20 1 --loglevel warning --maxmemory 1000mb --requirepass ${REDIS_PASSWORD?Variable not set} --appendonly yes
5353

5454
backend:
5555
image: "${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}"

0 commit comments

Comments
 (0)