Skip to content

Commit 1c714c7

Browse files
authored
Merge pull request #67 from prgrms-web-devcourse-final-project/refactor/maps-clusters-diary
지도 조회 API 성능테스트 위한 리팩토링
2 parents 4bc64ff + b0f53e4 commit 1c714c7

File tree

4 files changed

+80
-95
lines changed

4 files changed

+80
-95
lines changed

docker-compose.perf.yml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@ services:
55
dockerfile: Dockerfile
66
container_name: spring
77
ports:
8-
- "8080:8080" # 외부에서 접속할 경우 필요 (선택)
8+
- "8080:8080"
99
environment:
10-
- SPRING_PROFILES_ACTIVE=docker # profile 설정 (application-docker.yml)
10+
- SPRING_PROFILES_ACTIVE=docker
1111
depends_on:
1212
- mysql
13+
healthcheck:
14+
test: [ "CMD", "curl", "-f", "http://localhost:8080/actuator/health" ]
15+
interval: 10s
16+
timeout: 5s
17+
retries: 5
1318
deploy:
1419
resources:
1520
limits:
@@ -114,6 +119,10 @@ services:
114119
volumes:
115120
- ./performance-test.js:/performance-test.js
116121
command: run /performance-test.js
122+
depends_on:
123+
spring:
124+
condition: service_healthy
125+
117126

118127
volumes:
119128
pgdata:

performance-test.js

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,76 @@
11
import http from 'k6/http';
2-
import { check, sleep, group } from 'k6';
2+
import { check, group, sleep } from 'k6';
33

44
export const options = {
55
stages: [
6-
{ duration: '1m', target: 50 }, // 1분 동안 VU 50명 유지
7-
{ duration: '1m', target: 100 }, // 1분 동안 VU 100명 유지
8-
{ duration: '1m', target: 300 }, // 1분 동안 VU 200명 유지
9-
{ duration: '1m', target: 500 }, // 1분 동안 VU 300명 유지
10-
{ duration: '1m', target: 700 }, // 1분 동안 VU 500명 유지
11-
{ duration: '1m', target: 1000 }, // 1분 동안 VU 500명 유지
12-
{ duration: '1m', target: 0 }, // 점진적 종료
6+
{ duration: '2m', target: 100 }, // stay at 52 VUs
7+
{ duration: '1m', target: 200 }, // ramp-up to 517 VUs (peak time)
8+
{ duration: '2m', target: 300 }, // stay at 517 VUs
9+
{ duration: '1m', target: 500 }, // ramp-down to 52 VUs
10+
{ duration: '2m', target: 700 }, // ramp-down to 52 VUs
11+
{ duration: '2m', target: 900 }, // ramp-down to 52 VUs
12+
{ duration: '1m', target: 0 }, // ramp-down to 0
1313
],
1414
};
1515

1616
const BASE_URL = 'http://spring:8080';
1717

1818
export default function () {
19-
// 강북구 고정 좌표
19+
// 1. 클러스터형 조회 API 먼저 호출
20+
clusterRequest();
21+
22+
// 2. 그 다음 마커형 개별 다이어리 조회 API 호출
23+
markerRequest();
24+
25+
// (optional) sleep 추가
26+
sleep(1);
27+
}
28+
29+
function clusterRequest() {
30+
//서울특별시 전체를 커버할 수 있는 남, 북, 동, 서 좌표 범위
2031
const bounds = {
21-
south: 33.0,
22-
north: 39.5,
23-
west: 124.01,
24-
east: 131.0,
25-
zoom: 1
32+
south: 37.4133,
33+
north: 37.7014,
34+
west: 126.7341,
35+
east: 127.2693,
36+
zoom: 12
2637
};
2738

2839
const url = `${BASE_URL}/maps/diaries/cluster?south=${bounds.south}&north=${bounds.north}&west=${bounds.west}&east=${bounds.east}&zoom=${bounds.zoom}`;
2940

30-
group('Get Diary Clusters (Gangbuk-gu fixed bounds)', () => {
31-
const res = http.get(url, {
32-
headers: {
33-
'Content-Type': 'application/json',
34-
},
35-
});
41+
const res = http.get(url, {
42+
headers: {
43+
'Content-Type': 'application/json',
44+
},
45+
});
46+
47+
check(res, {
48+
'Cluster API 응답 성공': (r) => r.status === 200,
49+
});
50+
51+
console.log(`클러스터 조회 응답 시간: ${res.timings.duration} ms`);
52+
}
3653

54+
function markerRequest() {
55+
// 강동구 중심 좌표를 기준으로 소범위 설정
56+
const bounds = {
57+
south: 37.5459,
58+
north: 37.5559,
59+
west: 127.1644,
60+
east: 127.1744,
61+
};
3762

38-
check(res, {
39-
'status is 200': (r) => r.status === 200,
40-
});
63+
const url = `${BASE_URL}/maps/diaries/marker?south=${bounds.south}&north=${bounds.north}&west=${bounds.west}&east=${bounds.east}`;
4164

42-
sleep(1); // 사용자당 요청 간격
65+
const res = http.get(url, {
66+
headers: {
67+
'Content-Type': 'application/json',
68+
},
4369
});
70+
71+
check(res, {
72+
'Marker API 응답 성공': (r) => r.status === 200,
73+
});
74+
75+
console.log(`마커 조회 응답 시간: ${res.timings.duration} ms`);
4476
}

src/main/java/com/example/log4u/domain/map/controller/MapController.java

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -36,44 +36,6 @@ public class MapController {
3636

3737
private final MapService mapService;
3838

39-
@Value("${naver.api.client-id}")
40-
private String clientId;
41-
42-
@Value("${naver.api.client-secret}")
43-
private String secret;
44-
45-
private final RestTemplate restTemplate;
46-
47-
@GetMapping("/location")
48-
public ResponseEntity<ReverseGeocodingResponseDto> getMyLocation(
49-
@AuthenticationPrincipal CustomOAuth2User customOAuth2User,
50-
@ModelAttribute MyLocationRequestDto request
51-
) {
52-
53-
String naverMapsUrl = UriComponentsBuilder
54-
.fromHttpUrl("https://maps.apigw.ntruss.com/map-reversegeocode/v2/gc")
55-
.queryParam("coords", request.coords())
56-
.queryParam("output", request.output())
57-
.queryParam("orders", "legalcode,admcode,addr,roadaddr")
58-
.toUriString();
59-
60-
HttpHeaders headers = new HttpHeaders();
61-
headers.set("x-ncp-apigw-api-key-id", clientId);
62-
headers.set("x-ncp-apigw-api-key", secret);
63-
64-
HttpEntity<Void> entity = new HttpEntity<>(headers);
65-
66-
ResponseEntity<ReverseGeocodingResponseDto> response = restTemplate.exchange(
67-
naverMapsUrl,
68-
HttpMethod.GET,
69-
entity,
70-
ReverseGeocodingResponseDto.class
71-
);
72-
73-
log.debug("역 지오코딩 결과 : " + String.valueOf(response) + "\n");
74-
return ResponseEntity.ok(response.getBody());
75-
}
76-
7739
@GetMapping("/diaries/cluster")
7840
public ResponseEntity<List<DiaryClusterResponseDto>> getDiaryClusters(
7941
@RequestParam double south,

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

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -30,43 +30,25 @@ public class MapService {
3030
private final DiaryRepository diaryRepository;
3131
private final RedisDao redisDao;
3232

33-
@Transactional(readOnly = true)
34-
public List<DiaryClusterResponseDto> getDiaryClusters(double south, double north, double west, double east, int zoom) {
35-
String redisKey;
36-
List<DiaryClusterResponseDto> clusters;
37-
38-
// 줌 레벨 기준으로 캐시 키 결정 + Redis 조회
33+
public List<DiaryClusterResponseDto> getDiaryClusters(
34+
double south, double north, double west, double east, int zoom) {
3935
if (zoom <= 10) {
40-
redisKey = "cluster:sido";
41-
clusters = redisDao.getList(redisKey, DiaryClusterResponseDto.class);
42-
43-
// 캐시에 없으면 DB 조회 후 저장
44-
if (clusters == null) {
45-
clusters = sidoAreasRepository.findAllWithDiaryCount(); // 시/도 전체 조회
46-
redisDao.setList(redisKey, clusters, Duration.ofMinutes(5));
47-
log.info("[REDIS] 시/도 클러스터 캐시 새로 저장: {}", redisKey);
48-
}
36+
return getSidoAreasClusters(south, north, west, east);
4937
} else {
50-
redisKey = "cluster:sigg";
51-
clusters = redisDao.getList(redisKey, DiaryClusterResponseDto.class);
52-
53-
// 캐시에 없으면 DB 조회 후 저장
54-
if (clusters == null) {
55-
clusters = siggAreasRepository.findAllWithDiaryCount(); // 시/군/구 전체 조회
56-
redisDao.setList(redisKey, clusters, Duration.ofMinutes(5));
57-
log.info("[REDIS] 시/군/구 클러스터 캐시 새로 저장: {}", redisKey);
58-
}
38+
return getSiggAreasClusters(south, north, west, east);
5939
}
40+
}
6041

61-
// 범위 필터링
62-
return clusters.stream()
63-
.filter(cluster ->
64-
cluster.lat() >= south && cluster.lat() <= north &&
65-
cluster.lon() >= west && cluster.lon() <= east)
66-
.toList();
42+
private List<DiaryClusterResponseDto> getSidoAreasClusters(double south, double north, double west, double east) {
43+
return sidoAreasRepository.findSidoAreaClusters(south, north, west, east);
6744
}
6845

69-
@Transactional
46+
private List<DiaryClusterResponseDto> getSiggAreasClusters(double south, double north, double west, double east) {
47+
return siggAreasRepository.findSiggAreaClusters(south, north, west, east);
48+
}
49+
50+
51+
@Transactional
7052
public void increaseRegionDiaryCount(Double lat, Double lon) {
7153
sidoAreasRepository.findRegionByLatLon(lat, lon)
7254
.flatMap(sido -> sidoAreasDiaryCountRepository.findById(sido.getId()))

0 commit comments

Comments
 (0)