Skip to content

Commit b2e7127

Browse files
committed
refactor: 다이어리 캐시 로직 단건 → bulk 방식으로 개선
1 parent e935247 commit b2e7127

File tree

3 files changed

+74
-74
lines changed

3 files changed

+74
-74
lines changed

src/main/java/com/example/log4u/domain/map/cache/dao/DiaryCacheDao.java

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package com.example.log4u.domain.map.cache.dao;
22

3+
import java.util.ArrayList;
34
import java.util.Collections;
5+
import java.util.HashMap;
46
import java.util.List;
7+
import java.util.Map;
58
import java.util.Set;
69
import java.util.stream.Collectors;
710

@@ -35,7 +38,7 @@ public Set<Long> getDiaryIdSetFromCache(String geohash) {
3538
.map(obj -> Long.parseLong(obj.toString()))
3639
.collect(Collectors.toSet());
3740
} catch (Exception e) {
38-
log.error("diaryId Set 조회 실패 (key: {})", key, e);
41+
log.warn("diaryId Set 조회 실패 (key: {})", key, e);
3942
return Collections.emptySet();
4043
}
4144
}
@@ -49,39 +52,56 @@ public void cacheDiaryIdSetByGeohash(String geohash, List<Long> diaryIds) {
4952
redisTemplate.opsForSet().add(key, diaryIds.toArray());
5053
redisTemplate.expire(key, RedisTTLPolicy.DIARY_ID_SET_TTL);
5154
} catch (Exception e) {
52-
log.error("diaryId Set 저장 실패 (key: {})", key, e);
55+
log.warn("diaryId Set 저장 실패 (key: {})", key, e);
5356
}
5457
}
5558

56-
public DiaryMarkerResponseDto getDiaryFromCache(Long diaryId) {
57-
String key = CacheKeyGenerator.diaryKey(diaryId);
58-
try {
59-
Object raw = redisTemplate.opsForValue().get(key);
60-
if (raw == null) return null;
61-
62-
return objectMapper.readValue(raw.toString(), DiaryMarkerResponseDto.class);
63-
} catch (Exception e) {
64-
log.error("단건 diary 조회 실패 (key: {})", key, e);
65-
return null;
59+
public List<DiaryMarkerResponseDto> getDiariesFromCacheBulk(List<Long> diaryIds) {
60+
List<String> keys = diaryIds.stream()
61+
.map(CacheKeyGenerator::diaryKey)
62+
.toList();
63+
List<Object> values = redisTemplate.opsForValue().multiGet(keys);
64+
65+
List<DiaryMarkerResponseDto> result = new ArrayList<>();
66+
for (Object raw : values) {
67+
if (raw != null) {
68+
try {
69+
result.add(objectMapper.readValue(raw.toString(), DiaryMarkerResponseDto.class));
70+
} catch (Exception e) {
71+
log.warn("Diary 캐시 역직렬화 실패", e);
72+
}
73+
}
6674
}
75+
return result;
6776
}
6877

69-
public void cacheDiary(Long diaryId, DiaryMarkerResponseDto dto) {
70-
String key = CacheKeyGenerator.diaryKey(diaryId);
78+
public void cacheAllDiaries(List<DiaryMarkerResponseDto> dtos) {
7179
try {
72-
String json = objectMapper.writeValueAsString(dto);
73-
redisTemplate.opsForValue().set(key, json, RedisTTLPolicy.DIARY_TTL);
80+
Map<String, String> map = new HashMap<>();
81+
for (DiaryMarkerResponseDto dto : dtos) {
82+
String key = CacheKeyGenerator.diaryKey(dto.diaryId());
83+
String value = objectMapper.writeValueAsString(dto);
84+
map.put(key, value);
85+
}
86+
redisTemplate.opsForValue().multiSet(map);
87+
88+
// TTL은 multiSet에는 개별로 설정 불가 → 옵션: pipeline 사용
89+
// 또는 각 key에 대해 expire 호출 반복 필요
90+
for (String key : map.keySet()) {
91+
redisTemplate.expire(key, RedisTTLPolicy.DIARY_TTL);
92+
}
7493
} catch (Exception e) {
75-
log.error("단건 diary 캐시 저장 실패 (key: {})", key, e);
94+
log.warn("diary bulk 캐시 저장 실패", e);
7695
}
7796
}
7897

98+
7999
public void evictDiaryIdFromCache(String geohash, Long diaryId) {
80100
String key = CacheKeyGenerator.diaryIdSetKey(geohash);
81101
try {
82102
redisTemplate.opsForSet().remove(key, diaryId);
83103
} catch (Exception e) {
84-
log.error("diaryId 제거 실패 (key: {}, diaryId: {})", key, diaryId, e);
104+
log.warn("diaryId 제거 실패 (key: {}, diaryId: {})", key, diaryId, e);
85105
}
86106
}
87107

@@ -90,8 +110,7 @@ public void evictDiaryFromCache(Long diaryId) {
90110
try {
91111
redisTemplate.delete(key);
92112
} catch (Exception e) {
93-
log.error("diary 캐시 삭제 실패 (key: {})", key, diaryId, e);
113+
log.warn("diary 캐시 삭제 실패 (key: {})", key, diaryId, e);
94114
}
95115
}
96-
97116
}

src/main/java/com/example/log4u/domain/map/service/MapService.java

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -85,39 +85,32 @@ private List<DiaryClusterResponseDto> loadClustersFromDb(String geohash, int lev
8585
@Transactional(readOnly = true)
8686
public List<DiaryMarkerResponseDto> getDiariesByGeohash(String geohash) {
8787
validateGeohashLength(geohash, 5);
88-
Set<Long> diaryIds = loadDiaryIdsFromGeoCache(geohash);
88+
List<Long> diaryIds = loadDiaryIdsFromGeoCache(geohash);
8989
return loadDiaryDtosFromCache(diaryIds);
9090
}
9191

92-
private Set<Long> loadDiaryIdsFromGeoCache(String geohash) {
92+
private List<Long> loadDiaryIdsFromGeoCache(String geohash) {
9393
Set<Long> cachedIds = diaryCacheDao.getDiaryIdSetFromCache(geohash);
9494
if (!cachedIds.isEmpty())
95-
return cachedIds;
95+
return new ArrayList<>(cachedIds);
9696

9797
List<Long> diaryIds = diaryGeohashService.getDiaryIdsByGeohash(geohash);
9898
diaryCacheDao.cacheDiaryIdSetByGeohash(geohash, diaryIds);
99-
return new HashSet<>(diaryIds);
99+
return diaryIds;
100100
}
101101

102-
private List<DiaryMarkerResponseDto> loadDiaryDtosFromCache(Set<Long> diaryIds) {
103-
List<DiaryMarkerResponseDto> cached = getCachedDiaryDtos(diaryIds);
104-
List<Long> missedIds = getCacheMissedIds(diaryIds, cached);
105-
List<DiaryMarkerResponseDto> loaded = loadDiariesAndCache(missedIds);
106102

107-
return Stream.concat(cached.stream(), loaded.stream())
108-
.toList();
109-
}
103+
private List<DiaryMarkerResponseDto> loadDiaryDtosFromCache(List<Long> diaryIds) {
104+
List<DiaryMarkerResponseDto> cachedDtos = diaryCacheDao.getDiariesFromCacheBulk(diaryIds);
105+
List<Long> missedIds = getCacheMissedIds(diaryIds, cachedDtos);
106+
if (missedIds.isEmpty())
107+
return cachedDtos;
110108

111-
private List<DiaryMarkerResponseDto> getCachedDiaryDtos(Set<Long> ids) {
112-
List<DiaryMarkerResponseDto> cached = new ArrayList<>();
113-
for (Long id : ids) {
114-
DiaryMarkerResponseDto dto = diaryCacheDao.getDiaryFromCache(id);
115-
if (dto != null) cached.add(dto);
116-
}
117-
return cached;
109+
List<DiaryMarkerResponseDto> loaded = loadAndCacheMissedDiaries(missedIds);
110+
return Stream.concat(cachedDtos.stream(), loaded.stream()).toList();
118111
}
119112

120-
private List<Long> getCacheMissedIds(Set<Long> allIds, List<DiaryMarkerResponseDto> cachedDtos) {
113+
private List<Long> getCacheMissedIds(List<Long> allIds, List<DiaryMarkerResponseDto> cachedDtos) {
121114
Set<Long> cachedIds = cachedDtos.stream()
122115
.map(DiaryMarkerResponseDto::diaryId)
123116
.collect(Collectors.toSet());
@@ -127,19 +120,13 @@ private List<Long> getCacheMissedIds(Set<Long> allIds, List<DiaryMarkerResponseD
127120
.collect(Collectors.toList());
128121
}
129122

130-
private List<DiaryMarkerResponseDto> loadDiariesAndCache(List<Long> missedIds) {
131-
if (missedIds.isEmpty())
132-
return Collections.emptyList();
133-
123+
private List<DiaryMarkerResponseDto> loadAndCacheMissedDiaries(List<Long> missedIds) {
134124
List<Diary> diaries = diaryService.getDiaries(missedIds);
135-
List<DiaryMarkerResponseDto> result = new ArrayList<>();
136-
137-
for (Diary diary : diaries) {
138-
DiaryMarkerResponseDto dto = DiaryMarkerResponseDto.of(diary);
139-
diaryCacheDao.cacheDiary(diary.getDiaryId(), dto);
140-
result.add(dto);
141-
}
125+
List<DiaryMarkerResponseDto> result = diaries.stream()
126+
.map(DiaryMarkerResponseDto::of)
127+
.toList();
142128

129+
diaryCacheDao.cacheAllDiaries(result);
143130
return result;
144131
}
145132

src/test/java/com/example/log4u/domain/Map/service/MapServiceTest.java

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -129,19 +129,19 @@ void getDiariesByGeohash_success_allCacheHit() {
129129
DiaryMarkerResponseDto dto1 = DiaryMarkerFixture.createDiaryMarker(1L);
130130
DiaryMarkerResponseDto dto2 = DiaryMarkerFixture.createDiaryMarker(2L);
131131

132-
given(diaryCacheDao.getDiaryIdSetFromCache(geohash)).willReturn(cachedIds);
133-
given(diaryCacheDao.getDiaryFromCache(1L)).willReturn(dto1);
134-
given(diaryCacheDao.getDiaryFromCache(2L)).willReturn(dto2);
132+
given(diaryCacheDao.getDiaryIdSetFromCache("wydmt")).willReturn(cachedIds);
133+
given(diaryCacheDao.getDiariesFromCacheBulk(List.of(1L, 2L))).willReturn(List.of(dto1, dto2));
135134

136135
// when
137-
List<DiaryMarkerResponseDto> result = mapService.getDiariesByGeohash(geohash);
136+
List<DiaryMarkerResponseDto> result = mapService.getDiariesByGeohash("wydmt");
138137

139138
// then
140139
assertThat(result).containsExactlyInAnyOrder(dto1, dto2);
141140
verify(diaryRepository, never()).findAllById(any());
142141
verify(diaryGeohashService, never()).getDiaryIdsByGeohash(any());
143142
}
144143

144+
145145
@DisplayName("성공 : geohash 캐시 HIT + 모든 diary 캐시 MISS")
146146
@Test
147147
void getDiariesByGeohash_success_allDiaryCacheMiss() {
@@ -154,17 +154,15 @@ void getDiariesByGeohash_success_allDiaryCacheMiss() {
154154
DiaryMarkerResponseDto dto2 = DiaryMarkerResponseDto.of(diary2);
155155

156156
given(diaryCacheDao.getDiaryIdSetFromCache("wydmt")).willReturn(cachedIds);
157-
given(diaryCacheDao.getDiaryFromCache(1L)).willReturn(null);
158-
given(diaryCacheDao.getDiaryFromCache(2L)).willReturn(null);
157+
given(diaryCacheDao.getDiariesFromCacheBulk(List.of(1L, 2L))).willReturn(List.of());
159158
given(diaryService.getDiaries(List.of(1L, 2L))).willReturn(List.of(diary1, diary2));
160159

161160
// when
162161
List<DiaryMarkerResponseDto> result = mapService.getDiariesByGeohash("wydmt");
163162

164163
// then
165164
assertThat(result).containsExactlyInAnyOrder(dto1, dto2);
166-
verify(diaryCacheDao).cacheDiary(1L, dto1);
167-
verify(diaryCacheDao).cacheDiary(2L, dto2);
165+
verify(diaryCacheDao).cacheAllDiaries(List.of(dto1, dto2));
168166
}
169167

170168
@DisplayName("성공 : geohash 캐시 HIT + 일부 diary 캐시 MISS")
@@ -177,17 +175,16 @@ void getDiariesByGeohash_success_partialDiaryCacheMiss() {
177175
Diary diary2 = DiaryFixture.createDiaryFixture(2L);
178176
DiaryMarkerResponseDto dto2 = DiaryMarkerResponseDto.of(diary2);
179177

180-
given(diaryCacheDao.getDiaryIdSetFromCache(geohash)).willReturn(cachedIds);
181-
given(diaryCacheDao.getDiaryFromCache(1L)).willReturn(dto1);
182-
given(diaryCacheDao.getDiaryFromCache(2L)).willReturn(null);
178+
given(diaryCacheDao.getDiaryIdSetFromCache("wydmt")).willReturn(cachedIds);
179+
given(diaryCacheDao.getDiariesFromCacheBulk(List.of(1L, 2L))).willReturn(List.of(dto1));
183180
given(diaryService.getDiaries(List.of(2L))).willReturn(List.of(diary2));
184181

185182
// when
186-
List<DiaryMarkerResponseDto> result = mapService.getDiariesByGeohash(geohash);
183+
List<DiaryMarkerResponseDto> result = mapService.getDiariesByGeohash("wydmt");
187184

188185
// then
189186
assertThat(result).containsExactlyInAnyOrder(dto1, dto2);
190-
verify(diaryService).getDiaries(List.of(2L));
187+
verify(diaryCacheDao).cacheAllDiaries(List.of(dto2));
191188
}
192189

193190
@DisplayName("성공 : geohash 캐시 MISS → DB 조회 → 모든 diary 캐시 HIT")
@@ -201,8 +198,7 @@ void getDiariesByGeohash_success_geohashMiss_allDiaryCacheHit() {
201198

202199
given(diaryCacheDao.getDiaryIdSetFromCache(geohash)).willReturn(Collections.emptySet());
203200
given(diaryGeohashService.getDiaryIdsByGeohash(geohash)).willReturn(diaryIdsFromDb);
204-
given(diaryCacheDao.getDiaryFromCache(1L)).willReturn(dto1);
205-
given(diaryCacheDao.getDiaryFromCache(2L)).willReturn(dto2);
201+
given(diaryCacheDao.getDiariesFromCacheBulk(diaryIdsFromDb)).willReturn(List.of(dto1, dto2));
206202

207203
// when
208204
List<DiaryMarkerResponseDto> result = mapService.getDiariesByGeohash(geohash);
@@ -226,18 +222,16 @@ void getDiariesByGeohash_success_geohashMiss_allDiaryCacheMiss() {
226222

227223
given(diaryCacheDao.getDiaryIdSetFromCache(geohash)).willReturn(Collections.emptySet());
228224
given(diaryGeohashService.getDiaryIdsByGeohash(geohash)).willReturn(diaryIdsFromDb);
229-
given(diaryCacheDao.getDiaryFromCache(1L)).willReturn(null);
230-
given(diaryCacheDao.getDiaryFromCache(2L)).willReturn(null);
231-
given(diaryService.getDiaries(List.of(1L, 2L))).willReturn(List.of(diary1, diary2));
225+
given(diaryCacheDao.getDiariesFromCacheBulk(diaryIdsFromDb)).willReturn(List.of());
226+
given(diaryService.getDiaries(diaryIdsFromDb)).willReturn(List.of(diary1, diary2));
232227

233228
// when
234229
List<DiaryMarkerResponseDto> result = mapService.getDiariesByGeohash(geohash);
235230

236231
// then
237232
assertThat(result).containsExactlyInAnyOrder(dto1, dto2);
238233
verify(diaryCacheDao).cacheDiaryIdSetByGeohash(geohash, diaryIdsFromDb);
239-
verify(diaryCacheDao).cacheDiary(1L, dto1);
240-
verify(diaryCacheDao).cacheDiary(2L, dto2);
234+
verify(diaryCacheDao).cacheAllDiaries(List.of(dto1, dto2));
241235
}
242236

243237
@DisplayName("성공 : geohash 캐시 MISS → DB 조회 → diary 일부 캐시 MISS")
@@ -252,8 +246,7 @@ void getDiariesByGeohash_success_geohashCacheMissAndDiaryCacheMiss() {
252246

253247
given(diaryCacheDao.getDiaryIdSetFromCache(geohash)).willReturn(Collections.emptySet());
254248
given(diaryGeohashService.getDiaryIdsByGeohash(geohash)).willReturn(dbIds);
255-
given(diaryCacheDao.getDiaryFromCache(1L)).willReturn(dto1);
256-
given(diaryCacheDao.getDiaryFromCache(2L)).willReturn(null);
249+
given(diaryCacheDao.getDiariesFromCacheBulk(dbIds)).willReturn(List.of(dto1));
257250
given(diaryService.getDiaries(List.of(2L))).willReturn(List.of(diary2));
258251

259252
// when
@@ -262,6 +255,7 @@ void getDiariesByGeohash_success_geohashCacheMissAndDiaryCacheMiss() {
262255
// then
263256
assertThat(result).containsExactlyInAnyOrder(dto1, dto2);
264257
verify(diaryCacheDao).cacheDiaryIdSetByGeohash(geohash, dbIds);
258+
verify(diaryCacheDao).cacheAllDiaries(List.of(dto2));
265259
}
266260

267261
@DisplayName("성공 : Redis 예외 발생 시 fallback 작동: DB에서 조회됨")
@@ -285,14 +279,14 @@ void getDiariesByGeohash_redisFailureHandledInternally() {
285279
verify(diaryService).getDiaries(diaryIds);
286280
}
287281

288-
@DisplayName("예외 : diaryId 존재하나 DB에 diary 없음 ")
282+
@DisplayName("예외 : diaryId 존재하나 DB에 diary 없음")
289283
@Test
290284
void diaryIdExistsButDiaryMissingInDb() {
291285
// given
292286
String geohash = "wydmt";
293287
Set<Long> cachedIds = Set.of(100L);
294288
given(diaryCacheDao.getDiaryIdSetFromCache(geohash)).willReturn(cachedIds);
295-
given(diaryCacheDao.getDiaryFromCache(100L)).willReturn(null); // cache miss
289+
given(diaryCacheDao.getDiariesFromCacheBulk(List.of(100L))).willReturn(List.of());
296290
given(diaryService.getDiaries(List.of(100L))).willReturn(Collections.emptyList());
297291

298292
// when

0 commit comments

Comments
 (0)