feat(config): 구글 시트 설정 데이터 인메모리 캐싱 적용 및 동기화 최적화

- 설정 조회 속도 개선을 위한 인메모리 캐싱 도입: read_config(force_refresh=False)로 변경하여 캐시가 존재하고 유효 기간(10분) 내인 경우 구글 시트 API 호출을 건너뛰고 메모리 데이터 즉시 반환 / 다중 스레드 환경을 고려한 threading.Lock 및 Double-Checked Locking 적용으로 동시성 안전 보장

- 실시간 데이터 일관성 유지: write_config(config) 함수 성공 시 새로 작성한 설정 로우(rows) 데이터로 로컬 캐시 즉시 최신화

- 신규 제어 장치 및 Warm up 추가: /api/config/refresh (POST) API 신설 (구글 시트 웹 문서에서 직접 변경 시 강제로 캐시를 갱신할 수 있는 기능 제공) / 서버 시작(startup_event) 시 캐시 초기 적재(Warm up) 처리 추가로 첫 로딩 딜레이 완전 차단
This commit is contained in:
2026-05-26 00:19:42 +09:00
parent c4973224aa
commit ed2819d90e
+51 -5
View File
@@ -9,6 +9,7 @@ import io
import math import math
import datetime import datetime
import configparser import configparser
import threading
import urllib.request import urllib.request
import urllib.parse import urllib.parse
import uuid import uuid
@@ -219,11 +220,23 @@ SETTINGS_SPREADSHEET_ID = '1k7SNEdIaRtGXzQNdJSQAiNSDRwzOePDRTLHY_mNCO8M'
MANUAL_HISTORY_FILE = 'manual_history.dat' MANUAL_HISTORY_FILE = 'manual_history.dat'
# ----- Utility Functions ----- # ----- Utility Functions -----
def read_config(): def read_config(force_refresh=False):
config = configparser.ConfigParser(allow_no_value=True) global _CONFIG_ROWS_CACHE, _CONFIG_ROWS_CACHE_TIME
config.optionxform = str
rows = [] rows = []
# 1. 캐시 유효성 판단 (force_refresh가 아니고, 캐시가 존재하며, TTL이 지나지 않은 경우)
if not force_refresh and _CONFIG_ROWS_CACHE is not None:
if time.time() - _CONFIG_ROWS_CACHE_TIME < CONFIG_CACHE_TTL:
rows = _CONFIG_ROWS_CACHE
# 2. 캐시가 없거나 만료된 경우 구글 시트에서 직접 새로 로드
if not rows:
with _CONFIG_LOCK:
# 락 대기 중에 다른 스레드에서 캐시를 갱신했을 수 있으므로 다시 검사 (Double-Checked Locking)
if not force_refresh and _CONFIG_ROWS_CACHE is not None and time.time() - _CONFIG_ROWS_CACHE_TIME < CONFIG_CACHE_TTL:
rows = _CONFIG_ROWS_CACHE
else:
for _ in range(3): for _ in range(3):
try: try:
ws = get_settings_worksheet() ws = get_settings_worksheet()
@@ -233,6 +246,14 @@ def read_config():
time.sleep(1.5) time.sleep(1.5)
print(f"Google Sheets 불러오기 재시도 중...: {e}") print(f"Google Sheets 불러오기 재시도 중...: {e}")
if rows:
_CONFIG_ROWS_CACHE = rows
_CONFIG_ROWS_CACHE_TIME = time.time()
# 3. 로드된 로우 데이터를 ConfigParser 형식으로 파싱
config = configparser.ConfigParser(allow_no_value=True)
config.optionxform = str
if rows and len(rows) > 1: if rows and len(rows) > 1:
for row in rows: for row in rows:
if not row or not row[0].strip(): if not row or not row[0].strip():
@@ -256,10 +277,10 @@ def read_config():
config.set(section, key, val) config.set(section, key, val)
else: else:
config.set(section, key) config.set(section, key)
config.set(section, key)
return config return config
def write_config(config): def write_config(config):
global _CONFIG_ROWS_CACHE, _CONFIG_ROWS_CACHE_TIME
rows = [["대분류 (Section)", "항목 (Key)", "설정값 1", "설정값 2", "설정값 3", "설정값 4", "설정값 5"]] rows = [["대분류 (Section)", "항목 (Key)", "설정값 1", "설정값 2", "설정값 3", "설정값 4", "설정값 5"]]
for section in config.sections(): for section in config.sections():
items = config.items(section) items = config.items(section)
@@ -278,6 +299,11 @@ def write_config(config):
ws = get_settings_worksheet() ws = get_settings_worksheet()
ws.clear() ws.clear()
ws.update(values=rows) ws.update(values=rows)
# 구글 시트에 저장이 성공하면 로컬 캐시도 즉시 최신화
with _CONFIG_LOCK:
_CONFIG_ROWS_CACHE = rows
_CONFIG_ROWS_CACHE_TIME = time.time()
return return
except Exception as e: except Exception as e:
print(f"Google Sheets 저장 시도 {attempt+1}/3 실패: {e}") print(f"Google Sheets 저장 시도 {attempt+1}/3 실패: {e}")
@@ -318,6 +344,16 @@ async def get_config():
return JSONResponse(data) return JSONResponse(data)
@app.post("/api/config/refresh")
async def refresh_config():
"""구글 시트로부터 실시간으로 설정을 동기화하여 캐시를 강제 갱신합니다."""
try:
read_config(force_refresh=True)
return {"status": "success", "message": "설정 캐시가 성공적으로 갱신되었습니다."}
except Exception as e:
raise HTTPException(status_code=500, detail=f"설정 캐시 갱신 실패: {str(e)}")
class ProductGroup(BaseModel): class ProductGroup(BaseModel):
old_code: str old_code: str
@@ -416,7 +452,12 @@ def format_phone_number(num_str):
# ----- Google Sheets Global Cache ----- # ----- Google Sheets Config Cache & Global Sheets Cache -----
_CONFIG_ROWS_CACHE = None
_CONFIG_ROWS_CACHE_TIME = 0.0
_CONFIG_LOCK = threading.Lock()
CONFIG_CACHE_TTL = 600.0 # 캐시 유효 시간: 10분
gs_client_global = None gs_client_global = None
worksheet_manual_global = None worksheet_manual_global = None
worksheet_settings_global = None worksheet_settings_global = None
@@ -492,6 +533,11 @@ async def startup_event():
get_sms_history_worksheet() get_sms_history_worksheet()
get_manual_history_worksheet() get_manual_history_worksheet()
print("구글 시트 연동 완료!") print("구글 시트 연동 완료!")
# 캐시 초기 적재 (Warm up cache)
print("구글 시트 설정 캐시 초기 적재 시작...")
read_config(force_refresh=True)
print("구글 시트 설정 캐시 적재 완료!")
except Exception as e: except Exception as e:
print(f"구글 시트 연동 초기화 실패 (사용 전 권한을 확인하세요): {str(e)}") print(f"구글 시트 연동 초기화 실패 (사용 전 권한을 확인하세요): {str(e)}")