Caffeine API 캐싱 + DB 인덱스 V10 + 약관 재동의 + a11y 폴리쉬 — POP-SPOT v2.19 · v2.20 (+v2.20.1/2)
v2.19 성능·컴플라이언스 — Caffeine API 캐싱으로 상위 팝업 조회 p95 230ms→63ms, V10 마이그레이션 복합 인덱스, 약관 재동의 모달, OAuth state 강화. v2.20 UI 토큰·signup honeypot·a11y. v2.20.1/2 핫픽스 포함.
운영 안정을 높이는 한 달 속의 세 번째 배치. v2.18·v2.18.1 이 눈에 보이는 UX 였다면 v2.19·v2.20 은 성능·일관성·법적 의무·접근성 조정.
이 글에서 다루는 것
모르는 단어 한 줄로
| 용어 | 한 줄 설명 |
|---|---|
| Caffeine | 자바용 로컬 메모리 캐시 라이브러리. Spring @Cacheable 과 조합하면 메서드 결과 자동 캐싱 |
| p95 | 수행 시간의 95번째 백분위 |
| 복합 인덱스 | 여러 컬럼을 묶은 DB 인덱스. WHERE 에 여러 조건 있을 때 이득 |
| honeypot | 사람 눈에 안 보이는 필드. 봇이 자동 입력하면 차단 |
| a11y | accessibility. 스크린 리더·키보드 사용자를 고려한 접근성 |
v2.19 — 성능·컴플라이언스
Caffeine API 캐싱
// v2.19 — CacheConfig.java (신규). Caffeine 메모리 캠시 제조
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager(
"topPopups", "popupDetail", "mapMarkers"
);
manager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1_000)
.expireAfterWrite(Duration.ofMinutes(5))
.recordStats()
);
return manager;
}
}해석 — 한 줄씩
파일: popspot-backend/src/main/java/com/popspot/service/PopupStoreService.java
// v2.19 / v2.20.1 — PopupStoreService.findTopByRegion()
@Cacheable(value = "topPopups", key = "#region + ':' + #limit")
public List<PopupStoreResponse> findTopByRegion(String region, int limit) {
return repository.findVisibleTopByRegion(region, limit);
}해석
결과: 상위 팝업 조회 p95 = 230ms → 63ms. 캬시 적중 시 1~2ms.
DB 인덱스 V10
파일: popspot-backend/src/main/resources/db/migration/V10__indexes.sql
-- v2.19 — V10__indexes.sql
CREATE INDEX idx_popup_status_visible_end_date
ON popup_store (status, visible, end_date)
WHERE status IN ('ACTIVE','UPCOMING') AND visible = true;
CREATE INDEX idx_wishlist_user_popup
ON wishlist (user_id, popup_id);
CREATE INDEX idx_notification_user_read
ON notification (user_id, read_at);해석
약관 재동의 시스템
파일: popspot-backend/src/main/java/com/popspot/entity/User.java
// v2.19 — User 엔티티에 동의 버전 추적 컬럼 추가
@Column
private String agreedTermsVersion;
@Column
private String agreedPrivacyVersion;파일: popspot-frontend/src/lib/termsVersion.ts + popspot-frontend/src/app/page.tsx (모달 표시)
// v2.19 — 프론트의 현재 약관 버전 상수
const CURRENT_TERMS = '2026-05-25';
const CURRENT_PRIVACY = '2026-05-25';
const needsReconfirm =
user.agreedTermsVersion !== CURRENT_TERMS ||
user.agreedPrivacyVersion !== CURRENT_PRIVACY;
{needsReconfirm && <ModalBlock>약관이 변경되었음. 재동의 필요</ModalBlock>}해석
OAuth state 강화
파일: popspot-backend/src/main/java/com/popspot/service/OAuthStateService.java
// v2.19 — OAuthStateService.issueState(). CSRF 방어·Redis 기반 1회용
public String issueState() {
byte[] bytes = new byte[32];
secureRandom.nextBytes(bytes);
String state = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
redisTemplate.opsForValue().set(
"oauth:state:" + state, "valid",
Duration.ofMinutes(5)
);
return state;
}
public boolean consumeState(String state) {
Boolean deleted = redisTemplate.delete("oauth:state:" + state);
return Boolean.TRUE.equals(deleted);
}해석
v2.20 — a11y 와 UI 폴리쉬
honeypot
파일: popspot-frontend/src/app/signup/page.tsx 안의 honeypot 입력란
{/* v2.20 — signup 폼 내 honeypot 필드. 봇 탐지용 */}
<input
type="text"
name="company"
tabIndex={-1}
autoComplete="off"
style={{
position: 'absolute',
left: '-9999px',
width: '1px',
height: '1px',
opacity: 0,
}}
/>해석
v2.20.1 — CacheConfig 좀비 누락 핫픽스
증상
v2.19 배포 후 p95 개선 안 됨. 어드민 캬시 메트릭 보니 hit 가 0.
원인
PopupStoreService 에 @Cacheable 은 붙였는데 CacheConfig 의 빈이 등록되지 않아 Spring 이 명령을 조용히 무시. 메서드 시그니처가 v2.18 의 findVisibleTopByRegion 과 달라 교체 과정에서 @Cacheable 이 명시적 wiring 을 놓치몈.
수정
파일: popspot-backend/src/main/java/com/popspot/service/PopupStoreService.java
// v2.19 / v2.20.1 — PopupStoreService.findTopByRegion()
@Cacheable(value = "topPopups", key = "#region + ':' + #limit")
public List<PopupStoreResponse> findTopByRegion(String region, int limit) {
return repository.findVisibleTopByRegion(region, limit);
}Spring AOP 가 제대로 잡고 재배포.
v2.20.2 — Spotless JavaDoc reflow 5 번째 핫픽스
v1.4 이후 주기적으로 터지는 일이다. 한국어 멀티라인 JavaDoc 이 google-java-format 재포맷에 잡음. 8 파일의 주석을 100 컬럼 이내로 콤팩트화.
교훈 — 주석은 가능하면 한 줄로. 여러 줄 쓸 때도 각 줄이 쿠렌 이내로 잘리게 미리 끊어 두면 재포맷 충돌이 적다.