perf: API 응답 크기 최적화 — gzip 압축, NON_NULL, 정밀도 제한, Swagger 최신화
- application.yml: gzip 압축 활성화 (1KB 이상 JSON 자동 압축, 70~85% 감소) - JacksonConfig: NON_NULL 전역 설정 (null 필드 직렬화 제거, 5~15% 감소) - VesselPositionService: sog/cog 소수점 1자리, lon/lat 6자리 제한 (3~5% 감소) - MapTileController: @Tag, @Operation, @Parameter Swagger 문서 추가 - LockMonitorController: @Tag, @Operation Swagger 문서 추가 - BatchAdminController daily-stats: @ApiResponse 응답 스키마 예시 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
부모
a90662b2f0
커밋
0249a1fb90
@ -1,5 +1,9 @@
|
||||
package gc.mda.signal_batch.domain.gis.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@ -17,16 +21,20 @@ import java.nio.file.Paths;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/tiles")
|
||||
@Tag(name = "지도 타일 API", description = "WebP 형식의 배경 지도 타일 서비스 (World/ENC)")
|
||||
public class MapTileController {
|
||||
|
||||
private static final String TILE_BASE_PATH = "/devdata/MAPS/WORLD_webp";
|
||||
private static final String TILE_ENC_PATH = "/devdata/MAPS/ENC_RAS_webp";
|
||||
|
||||
@GetMapping("/world/{z}/{x}/{y}.webp")
|
||||
@Operation(summary = "세계지도 타일 조회", description = "ZXY 좌표 기반 세계지도 WebP 타일을 반환합니다")
|
||||
@ApiResponse(responseCode = "200", description = "WebP 이미지 타일")
|
||||
@ApiResponse(responseCode = "404", description = "타일 없음")
|
||||
public ResponseEntity<Resource> getWorldTile(
|
||||
@PathVariable int z,
|
||||
@PathVariable int x,
|
||||
@PathVariable int y) {
|
||||
@Parameter(description = "줌 레벨 (0~18)") @PathVariable int z,
|
||||
@Parameter(description = "X 좌표 (열)") @PathVariable int x,
|
||||
@Parameter(description = "Y 좌표 (행)") @PathVariable int y) {
|
||||
|
||||
try {
|
||||
// 안전한 경로 생성
|
||||
@ -59,10 +67,13 @@ public class MapTileController {
|
||||
}
|
||||
|
||||
@GetMapping("/enc/{z}/{x}/{y}.webp")
|
||||
@Operation(summary = "ENC 해도 타일 조회", description = "ZXY 좌표 기반 전자해도(ENC) WebP 타일을 반환합니다")
|
||||
@ApiResponse(responseCode = "200", description = "WebP 이미지 타일")
|
||||
@ApiResponse(responseCode = "404", description = "타일 없음")
|
||||
public ResponseEntity<Resource> getEncTile(
|
||||
@PathVariable int z,
|
||||
@PathVariable int x,
|
||||
@PathVariable int y) {
|
||||
@Parameter(description = "줌 레벨 (0~18)") @PathVariable int z,
|
||||
@Parameter(description = "X 좌표 (열)") @PathVariable int x,
|
||||
@Parameter(description = "Y 좌표 (행)") @PathVariable int y) {
|
||||
|
||||
try {
|
||||
// 안전한 경로 생성
|
||||
@ -95,6 +106,7 @@ public class MapTileController {
|
||||
}
|
||||
|
||||
@GetMapping("/health")
|
||||
@Operation(summary = "타일 서비스 상태 확인", description = "타일 디렉토리 존재 여부로 서비스 상태를 확인합니다")
|
||||
public ResponseEntity<String> checkTileService() {
|
||||
File baseDir = new File(TILE_BASE_PATH);
|
||||
if (baseDir.exists() && baseDir.isDirectory()) {
|
||||
|
||||
@ -12,6 +12,8 @@ import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
@ -155,10 +157,10 @@ public class VesselPositionService {
|
||||
return RecentVesselPositionDto.builder()
|
||||
.mmsi(mmsi)
|
||||
.imo(imo > 0 ? imo : null)
|
||||
.lon(rs.getDouble("lon"))
|
||||
.lat(rs.getDouble("lat"))
|
||||
.sog(rs.getBigDecimal("sog"))
|
||||
.cog(rs.getBigDecimal("cog"))
|
||||
.lon(Math.round(rs.getDouble("lon") * 1_000_000) / 1_000_000.0)
|
||||
.lat(Math.round(rs.getDouble("lat") * 1_000_000) / 1_000_000.0)
|
||||
.sog(scaleDecimal(rs.getBigDecimal("sog"), 1))
|
||||
.cog(scaleDecimal(rs.getBigDecimal("cog"), 1))
|
||||
.shipNm(rs.getString("ship_nm"))
|
||||
.shipTy(shipTy)
|
||||
.shipKindCode(shipKindCode)
|
||||
@ -168,4 +170,8 @@ public class VesselPositionService {
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private static BigDecimal scaleDecimal(BigDecimal value, int scale) {
|
||||
return value != null ? value.setScale(scale, RoundingMode.HALF_UP) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package gc.mda.signal_batch.global.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
@ -36,6 +37,7 @@ public class JacksonConfig {
|
||||
return Jackson2ObjectMapperBuilder.json()
|
||||
.modules(javaTimeModule)
|
||||
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
||||
.serializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,10 @@ package gc.mda.signal_batch.monitoring.controller;
|
||||
import gc.mda.signal_batch.monitoring.service.BatchMetadataCleanupService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -641,7 +645,35 @@ public class BatchAdminController {
|
||||
* 프론트엔드에서 Stacked Bar (처리 건수) + Duration Line (소요시간) 차트 표시.
|
||||
*/
|
||||
@GetMapping("/daily-stats")
|
||||
@Operation(summary = "일별 처리 통계", description = "최근 7일간 일별 Job별 배치 처리 통계를 조회합니다 (대시보드 차트용)")
|
||||
@Operation(
|
||||
summary = "일별 처리 통계",
|
||||
description = "최근 7일간 일별 Job별 배치 처리 통계를 조회합니다 (대시보드 차트용)"
|
||||
)
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "일별 통계 + 24시간 상태 요약",
|
||||
content = @Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = Object.class),
|
||||
examples = @ExampleObject(value = """
|
||||
{
|
||||
"dailyStats": [
|
||||
{
|
||||
"date": "2026-02-28",
|
||||
"totalWrite": 285000,
|
||||
"jobs": {
|
||||
"incrementalVesselJob": { "writeCount": 120000, "execCount": 288, "avgDuration": 45 },
|
||||
"trackBuildJob": { "writeCount": 95000, "execCount": 288, "avgDuration": 30 },
|
||||
"hourlyAggregationJob": { "writeCount": 50000, "execCount": 24, "avgDuration": 15 },
|
||||
"dailyAggregationJob": { "writeCount": 20000, "execCount": 1, "avgDuration": 40 }
|
||||
}
|
||||
}
|
||||
],
|
||||
"statusSummary": { "completed": 601, "failed": 0, "stopped": 0 }
|
||||
}
|
||||
""")
|
||||
)
|
||||
)
|
||||
public ResponseEntity<Map<String, Object>> getDailyStatistics() {
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package gc.mda.signal_batch.monitoring.controller;
|
||||
|
||||
import gc.mda.signal_batch.global.util.ConcurrentUpdateManager;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@ -13,11 +15,13 @@ import java.util.Map;
|
||||
@RestController
|
||||
@RequestMapping("/admin/locks")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "Lock 모니터링 API", description = "배치 동시성 Lock 상태 및 Deadlock 정보 조회")
|
||||
public class LockMonitorController {
|
||||
|
||||
private final ConcurrentUpdateManager concurrentUpdateManager;
|
||||
|
||||
@GetMapping("/statistics")
|
||||
@Operation(summary = "Lock 통계 조회", description = "현재 활성 Lock 및 Lock 사용 통계를 조회합니다")
|
||||
public ResponseEntity<Map<String, Object>> getLockStatistics() {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("lockStats", concurrentUpdateManager.getLockStatistics());
|
||||
@ -28,6 +32,7 @@ public class LockMonitorController {
|
||||
}
|
||||
|
||||
@GetMapping("/deadlocks")
|
||||
@Operation(summary = "Deadlock 정보 조회", description = "감지된 Deadlock 이력 정보를 조회합니다")
|
||||
public ResponseEntity<Map<String, Object>> getDeadlockInfo() {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("deadlocks", concurrentUpdateManager.getDeadlockInfo());
|
||||
|
||||
@ -2,6 +2,10 @@ server:
|
||||
shutdown: graceful
|
||||
servlet:
|
||||
context-path: /signal-batch
|
||||
compression:
|
||||
enabled: true
|
||||
mime-types: application/json,application/xml,text/html,text/xml,text/plain
|
||||
min-response-size: 1024
|
||||
|
||||
spring:
|
||||
application:
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user