[241219] 기능 개선 및 수정 / 게시물 내용 수정

>  뭔가 이상해서 계속 확인하고 공부해보니 현재 상태에서 없애도되는 코드가 존재했으며,

실제 웹사이트라는 가정하에 @Scheduled 기능을 추가하여 지하철을 이용하지 않는 시간인 새벽에 OPEN Api 데이터를 갱신하는것을 추가)

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

이번 프로젝트는 Open API 를 연결하여 데이터를 나타내는 기능을 구현하였다

 

처음에는 그저 Open API 를 연결만하고 데이터만 나타내는것으로 기능을 다 구현하였다고 생각했는데...

 

특정 시간대에 Open API 호출 시 데이터 응답 시간이 오래 걸리는 경우가 있었다. 문의해본 결과 게이트웨이 방식의 API를 동시 호출하는 이용자가 급증할 경우 빈번하게 발생한다고 한다.

(+게이트웨이 방식이란 클라이언트와 여러개의 백엔드 서비스 사이에 두고, API요청을 중앙에서 관리하는 아키텍처 방식이라고한다. 쉽게 말해, 클라이언트와 API 데이터 사이에 유통업자가 있는데 내가 유통업자에게 A라는 데이터를 달라하면 유통업자가 해당 데이터를 찾아서 제공해주는 방식이라 한다)

 

그리고 매번 사용자가 지하철역 데이터를 원할때마다 API 호출을 한다는것은 성능과 비용적인 면에서 안 좋은 결과를 낼수 있다고 생각한다.


불편함 및 성능의 개선을 위한 방법을 찾아보니,

Open API 첫 호출 후 캐시에 저장하여 그 후로는 캐시에 담긴 데이터를 뽑아서 쓸 수 있는 방법이 있어 적용하였다.(서버 캐싱)



기존 api 호출 방법

// 지하철역 검색

String searchUrl = "http://openapi.seoul.go.kr:8088/" + key.getSeoulMetroKey()

+ "/json/subwayStationMaster/1/1000/";

Map resultData = returnApiDataService.api(searchUrl).block();

 

기존에는 사용자가 검색 버튼을 누를때마다 계속해서 api호출이 이뤄졋엇다..

 

 

 

기존 api연결을 했을때는 시간이 엄청오래걸렸다

(로드된 이미지에는 0.4초로 나오지만 그 전까지만해도 체감상 적게는 4초 많게는 8초정도까지 걸렸던 적이 있다)

 

 

 

 

 

수정 api 호출(+캐시에 저장)

 

pom.xml

<!-- Spring Boot Cache starter -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-cache</artifactId>

</dependency>

 

 

우선 xml파일에 cache 의존성을 추가한다

캐시 및 캐시관련 어노테이션 활용하기위함

 

 

 

OPEN Api 불러올 메소드(캐시에 담을 데이터)

 

@Service

@RequiredArgsConstructor

public class CacheService {

 

private final ReturnApiDataService returnApiDataService;

private final ApiKeyConfig apiKeyConfig;

 

 

//모든 지하철역 정보 불러오기

//캐시에 모든 데이터 저장

@Cacheable(value = "stationInfo")

public List<Map> getAllStationInfo(){

 

String searchUrl = "http://openapi.seoul.go.kr:8088/" + apiKeyConfig.getSeoulMetroKey()

+ "/json/subwayStationMaster/1/1000/";

 

 

System.out.println("캐시가 없으므로 호출함");

 

 

Map resultData = returnApiDataService.api(searchUrl).block();

 

Map items = (Map) resultData.get("subwayStationMaster");

 

List<Map> rows = (List<Map>) items.get("row");

 

return rows;

}

 

}

 

우선 캐시에 저장할 데이터를 불러올 메소드를 따로 뺏다
@Cacheable 어노테이션으로 인해

처음 메소드 호출 후 데이터가 캐싱되기 때문

(데이터 보존 및 구분을 위해서 캐싱 전용 서비스 클래스로 만들었다)

 

맨 처음에 @Cacheable 붙은 메소드를 호출하여 데이터를 받았을 때,

그 데이터는 캐시에 저장이 되고 그 다음부터 저 메소드를 부를때는 메소드가 호출되는것이 아닌

캐시에 저장된 데이터값을 불러오게된다.

 

캐시가 되었는지 확인하는 방법으로는

System.out.println("캐시가 없으므로 호출함");

으로 확인하였다.

 

처음에는 "캐시가 없으므로 호출함"이 콘솔창에 떴지만(밑에 스케줄로 해당 메소드를 호출했을 때 뜨고),

그 다음부터 저 메소드를 호출하면 뜨지 않았다 그리고 데이터는 정상적으로 왔다

 

(여기서 불러오는 데이터는 지하철역 기본정보 데이터다)

 

 

캐싱된 데이터 갱신을 위한 서비스 클래스

@Service

@RequiredArgsConstructor

public class CacheRefreshService {

 

private final CacheService cacheService;

 

@Scheduled(cron = "55 59 2 * * *") // 2시 59분 55초 메소드 실행

@CacheEvict(value = "stationInfo", allEntries = true) // 기존 캐시 삭제

public void removeCache() {

 

}

 

@Scheduled(cron = "0 0 3 * * *") // 3시 메소드 실행

public void refreshCache() {

cacheService.getAllStationInfo();// 새로운 데이터를 가져오고 캐시 갱신

 

}

}

 

여기는 캐싱 데이터를 갱신해주는 서비스 클래스이다

 

실제 배포된 웹사이트라 생각해보자

 

OPEN Api 데이터를 받고 캐싱되면 시간이 지날수록 점차 신뢰가  떨어지는 데이터가 될 것이다

 

이를 방지하기 위해 갱신이 필요하다고 판단되어 캐시 클래스를 갱신해주는 캐시 갱신 클래스를 만들어보았다

 

@Scheduled(cron = "55 59 2 * * *")

스케줄을 관리해주는 어노테이션이다

cron = "초 분 시간 일 월 요일" / *은 모든값으로 매일이라고 생각하면 된다

(fixedRate = 초,분,시간마다 실행해주는 방법도 있다 / 10초마다 실행, 10분마다 실행, 1시간마다 실행이 가능하다)

 

그럼 현재 스케쥴 어노테이션은 몇시에 메소드를 실행하게끔 되어있을까

초:55 분:59 시:2 일:매일 월:매월 요일:모든요일

즉, AM 2시 59분 55초 이다

 

 

@CacheEvict(value = "stationInfo", allEntries = true) // 기존 캐시 삭제

캐시를 삭제해주는 어노테이션이다

value = "stationInfo" > 위에서 정한 캐시 이름을 지정해주고

allEntries = true > 해당 캐시에 저장된 모든 항목을 삭제해준다

 

그러면 메소내용은 비어있지만
어노테이션 기능으로 인해

AM 2시 59분 55초에 메소드가 호출되면서

캐시를 삭제해주는 어노테이션이 실행된다

 

 

@Scheduled(cron = "0 0 3 * * *") // 3시 메소드 실행

public void refreshCache() {

cacheService.getAllStationInfo();// 새로운 데이터를 가져오고 캐시 갱신

}

 

다음은 캐시에 저장할 OPEN Api 데이터를 불러오는 메소드를 호출하는 메소드다

스케줄로인해 캐시가 삭제된 후 5초뒤에 실행되니,

다시 캐시에 새롭게 갱신된 데이터가 담기게 된다

(이때는 캐시에 저장되지 않은 상태이며 OPEN Api를 호출하는 과정이니 System.out.println("캐시가 없으므로 호출함");

콘솔창에서 확인된다)

 

 

 

 

여기서 왜 굳이 나눳지란 생각이 들수도있다

@Scheduled(cron = "55 59 2 * * *") // 2시 59분 55초 메소드 실행

@CacheEvict(value = "stationInfo", allEntries = true) // 기존 캐시 삭제

public void removeCache() {

cacheService.getAllStationInfo();// 새로운 데이터를 가져오고 캐시 갱신

}

 

이렇게 한다면 해당시간에 캐시가 지워지고 다시 getAllStationInfo() 메소드를 호출하니 캐싱되지 않을까란 생각에

시도를 해보았지만, 안되길래 나눴다..

(개인적인 생각으로 어노테이션이 밑에 메소드보다 늦게 실현이 되는건지 싶다 이 부분은 한 번 찾아보고 내용을 보충해봐야겠다)

 

 




기능개선 후 결과

 

약 95%의 속도개선이 이루어졌음을 볼 수 있다

 

 

캐시가 주는 장점도 있지만 당연히 단점도 있다

 

1. 데이터 불일치문제(실시간으로 변하는 데이터라면, 기존에 저장된 데이터는 시간이 지날수록 점차 신뢰할 수 없는데이터가 됨) 

> 데이터 신뢰성을 위해 스케줄을 이용하여 시간마다 or 설정된 시간에 데이터를 갱신하여 데이터의 신뢰성을 보존

 

2. 캐시는 서버의 메모리를 사용하기때문에 과도한 사용은 메모리 부족을 초래할 수 있다.

> 그렇다고 만약 캐시를 사용하지 않고 계속해서 API 호출을 통해 데이터를 불러온다면, 불필요한 작업이 반복되어 성능 저하가 발생할 수 있다. 따라서 캐시를 사용하되, 적절한 서버 메모리 사용을 통해 성능을 최적화하는 것이 중요하다고 생각된다.

 

더 나은 경험을 제공하기 위해 새로운 기능을 도입하더라도, 새로운 문제가 발생할 수 있다. 계속해서 더 나은 방법과 그에 따른 문제들을 개선할 방법을 찾고 노력해야겠다..... 

 

 

 

 

'팀프로젝트 > EPLSeoul' 카테고리의 다른 글

서울 지하철 관련 정보 사이트  (2) 2024.12.16

+ Recent posts