""" 이 파일은 스트림릿 대시보드의 최초 로딩 시간을 제거하기 위한 커스텀 캐싱 스크립트입니다. 로컬 파일 캐시를 우선적으로 반환하고 백그라운드에서 구글 시트와 데이터를 동기화합니다. """ import os import time import pickle import threading import streamlit as st BASE_DIR = os.path.dirname(os.path.abspath(__file__)) def local_first_cache(cache_filename, ttl=3600): """ Stale-While-Revalidate (로컬 우선 읽기) 패턴을 구현한 커스텀 캐시 데코레이터. - 로컬에 pickle 캐시가 있으면 0.1초 만에 즉시 반환 (대시보드 로딩 딜레이 제거) - 캐시가 ttl보다 오래되었으면 기존 캐시를 반환한 뒤, 백그라운드 스레드에서 구글 시트를 조회하여 캐시를 업데이트함 """ def decorator(func): def wrapper(*args, **kwargs): cache_path = os.path.join(BASE_DIR, "scratch", cache_filename) # 1. 로컬 캐시가 존재하면 최우선으로 읽어옴 if os.path.exists(cache_path): try: with open(cache_path, "rb") as f: data = pickle.load(f) # 2. 캐시가 만료(ttl 경과)되었다면, 화면에는 기존 데이터를 먼저 뿌리고 백그라운드에서 조용히 업데이트 if time.time() - os.path.getmtime(cache_path) > ttl: def _update_cache(): try: res = func(*args, **kwargs) os.makedirs(os.path.dirname(cache_path), exist_ok=True) with open(cache_path, "wb") as f: pickle.dump(res, f) except Exception as e: print(f"[경고] 백그라운드 캐시 업데이트 실패 ({cache_filename}): {e}") threading.Thread(target=_update_cache, daemon=True).start() return data except Exception as e: print(f"[경고] 로컬 캐시 읽기 실패 ({cache_filename}), 동기식으로 재조회합니다: {e}") # 3. 로컬 캐시가 아예 없는 경우 (서버 생애 최초 1회) -> 동기식으로 로드하고 캐시 저장 print(f"[시작] 최초 데이터 로드 중... ({cache_filename})") res = func(*args, **kwargs) try: os.makedirs(os.path.dirname(cache_path), exist_ok=True) with open(cache_path, "wb") as f: pickle.dump(res, f) except Exception as e: print(f"[경고] 로컬 캐시 저장 실패 ({cache_filename}): {e}") return res return wrapper return decorator