travel-proxy 이미지 썸네일 로딩 최적화, 페이지네이션 추가

This commit is contained in:
2026-01-25 19:40:26 +09:00
parent 64c526488a
commit 0fde916120

View File

@@ -149,16 +149,19 @@ def scan_album(album: str) -> List[Dict[str, Any]]:
return [] return []
items = [] items = []
for p in album_dir.iterdir(): # os.scandir이 iterdir보다 빠름 (파일 속성 한 번에 가져옴)
if p.is_file() and p.suffix.lower() in IMAGE_EXT: with os.scandir(album_dir) as entries:
# ✅ 썸네일 생성 보장 for entry in entries:
ensure_thumb(p, album) if entry.is_file() and Path(entry.name).suffix.lower() in IMAGE_EXT:
# ⚡ 성능 핵심: 여기서 썸네일 생성(ensure_thumb) 절대 하지 않음!
# 파일 존재 여부만 확인하고 바로 리턴
items.append({ items.append({
"album": album, "album": album,
"file": p.name, "file": entry.name,
"url": f"{MEDIA_BASE}/{album}/{p.name}", "url": f"{MEDIA_BASE}/{album}/{entry.name}",
"thumb": f"{MEDIA_BASE}/.thumb/{album}/{p.name}", "thumb": f"{MEDIA_BASE}/.thumb/{album}/{entry.name}",
# 정렬을 위해 수정시간/이름 필요하면 여기서 저장
"mtime": entry.stat().st_mtime
}) })
return items return items
@@ -174,15 +177,17 @@ def regions():
@app.get("/api/travel/photos") @app.get("/api/travel/photos")
def photos( def photos(
region: str = Query(...), region: str = Query(...),
limit: int = Query(500, le=5000), page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
): ):
_meta_changed_invalidate_cache() _meta_changed_invalidate_cache()
# 1. 캐시 키에 페이지 정보 포함하지 않음 (전체 리스트 캐싱 후 슬라이싱 전략)
# 왜냐하면 파일 스캔 비용이 크지, 메모리 슬라이싱 비용은 작기 때문.
now = time.time() now = time.time()
cached = CACHE.get(region) cached = CACHE.get(region)
if cached and now - cached["ts"] < CACHE_TTL:
return cached["data"]
if not cached or now - cached["ts"] >= CACHE_TTL:
region_map = load_region_map() region_map = load_region_map()
albums = _get_albums_for_region(region, region_map) albums = _get_albums_for_region(region, region_map)
@@ -194,19 +199,35 @@ def photos(
matched.append({"album": album, "count": len(items)}) matched.append({"album": album, "count": len(items)})
all_items.extend(items) all_items.extend(items)
# 정렬: 앨범명 > 파일명 (또는 찍은 날짜)
all_items.sort(key=lambda x: (x["album"], x["file"])) all_items.sort(key=lambda x: (x["album"], x["file"]))
data = { cached_data = {
"region": region, "region": region,
"matched_albums": matched, "matched_albums": matched,
"items": all_items[:limit], "items": all_items,
"total": len(all_items), "total": len(all_items),
"cached_at": int(now),
"cache_ttl": CACHE_TTL,
} }
CACHE[region] = {"ts": now, "data": cached_data}
else:
cached_data = cached["data"]
CACHE[region] = {"ts": now, "data": data} # 2. 페이지네이션 슬라이싱
return data all_items = cached_data["items"]
total = len(all_items)
start = (page - 1) * size
end = start + size
paged_items = all_items[start:end]
return {
"region": region,
"page": page,
"size": size,
"total": total,
"has_next": end < total,
"items": paged_items,
}
@app.get("/media/travel/.thumb/{album}/{filename}") @app.get("/media/travel/.thumb/{album}/{filename}")