데이터 연계 트러블 슈팅

jy.YOON·2023년 5월 24일

트러블슈팅

목록 보기
4/4
post-thumbnail

목적

하루 평균 2천만건 발생되는 데이터 연계(항공 데이터)

내부 폐쇠망에서 발생되는 데이터를 가공 파싱하여 외부 데이터 서버로 연계하는 작업을 하고있다.

그림에서 연계모듈 부분이 내가 담당하고 있는 파트이다.

문제점 발생


연계 모듈을 상세히 그려보면 이와같은 구조로 나온다.
DB는 논리적 테이블로 나눠눈것이다.

로직설명

연계 모듈은 3가지로 나눠진다(수신모듈, 파싱모듈, 송신모듈)
간단히 로직을 설명하자면.

1. FILE의 ROW 데이터를 읽어 원문테이블로 저장한다.
2. flag를 검사하여 DB의 ROW 데이터를 파싱하여 파싱테이블에 저장한다. format은 JSON이다. 
3. 파싱이 된 데이터는 flag를 변경한다.
4. 원문테이블과 파싱테이블을 1:1 매핑시켜 데이터를 HTTP POST로 외부로 전송한다.
5. 
4. 전송이 완료된 데이터(원문,파싱)들은 삭제한다.

1. 성능문제

대략적으로 하루 평균 1천5백만개의 데이터를 처리해야 한다.
db커넥션풀, 각종 인덱싱 설정을 통한 쿼리 개선을 하였지만
현재의 처리량은 60/s 이였다.
그러면 20시간 기준 약 420만개 처리량이다.

목표치에 비에 턱없이 부족한 성능이였다.

결국 프로파일을 진행했고
문제는 데이터를 건별로 select 하여 parsing 하고 insert하고 flag를 변경하고 다시 delete를 하는 등 DB의 I/O가 너무 많이 발생한다는 것이였다.

이 FLOW를 유지하면서 비슷한것이 뭐가 있을까?? 생각하다 떠오른 것이 RABBIT MQ였다.

오픈소스 이면서도 사이드 프로젝트에서 사용한 경험도 있을뿐더러
가장 중요한건 현재 FLOW를 유지할 수 있는 것이였다.

기존에는 외부로 데이터를 송신하기 위해서는 원문/파싱 2개의 테이블을 리스닝해서 송신하고 후처리를 delete로 해주어야 했지만
MQ는 MQ에서 꺼내면 auto delete 같은 옵션을 제공해주기 때문에 후처리가 편하며 DB I/O를 발생시키는것에 비해 빠르다.

결국 RABBIT MQ를 도입하여 DB의 I/O를 줄일수 있었고 60/s -> 150/s 로 약 2배넘는 극적인 성능개선을 경험했다.

1. 서버의 Full disk로 인한 장애 발생

연계서버의 가용 용량은 TOTAL 800GB 이다.


하루기준 발생되는 데이터를 연계하기 위해 필요한 가용용량은 약70GB 이다.

그러면 연계서버는 최대 10일치의 데이터가 쌓여도 문제가 없다는 계산이 나오는데 현재 연계서버는 국가보안시설의 내부시설안에 존재하며 1년에 2,3번씩 서버별로 점검기간이 1~2주 정도 하며 점검을 할땐 서버를 SHUTDOWN 시킨다.

여기서 문제가 발생했다.

연계모듈을 운영하면서 서버점검이란 변수를 예상하지 못했던것이다.

2주동안 서버점검으로 인해 연계서버가 감당할 수 있는 데이터의 최대치를 훨씬 넘어버려 DISK FULL 이 발생하여 연계모듈이 STOP 된것이다.

여기서 간단한 해결법은 첫째, 서버의 디스크 용량을 늘린다. 둘째, 송신 모듈의 속도를 좀더 개선한다.

나는 일단 두번째 방법을 시도해보고 그래도 해결이 안되면 첫번째 방법을 택하겠다고 생각했다.


이전 DB구조에서 MQ를 적용하여 최종적으로 운영중인 구조의 모습이다.

송신모듈은 MQ에서 데이터를 꺼내서 KAFKA AGENT 쪽으로 HTTP POST 로 데이터를 전송하는 역할을 한다.

문제는 이전 DB방식에 비해서 송신모듈의 속도는 빨라졌지만 150/s 이라는 성능은 아직도
불안전한 성능이다. 하루 평균 2천만건의 데이터가 발생하는데 지금의 성능이라면 24시간 최고의 성능을 낸다고 가정했을때도 약1300만개 정도이다. 턱없이 부족하다.

HTTP POST 하는 부분의 코드를 확인해보자

@Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

일부 코드 발췌

    @Override
    public HttpStatus apiCall(String msg, String url) {
        HttpEntity<String> request =
                new HttpEntity<String>(Base64.getEncoder().encodeToString(msg.getBytes()));

        return restTemplate.postForEntity(url,request,String.class)
                .getStatusCode();
    }

RestTemplate을 이용하여 HTTP POST를 사용하고 있다.

보통 HTTP를 이용할때 HTTP Connection을 연결하고 생성하는 부분에서 비용이 많이 발생한다.

구글과 StackOverFlow를 뒤져본 결과

현재 사용중인 RestTemplate은 따로 설정을 해주지 않으면 매번 새 HTTP 연결을 만들고 완료되면 연결을 닫는다고 한다.

이부분에서 오버헤드가 있을것으로 추정하고 RestTemplate에커넥션풀을 적용한뒤 테스트를 진행해봤다.

@Bean
public RestTemplate restTemplate() {
    PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
    connectionManager.setMaxTotal(200);
    connectionManager.setDefaultMaxPerRoute(200);

    HttpClient httpClient = HttpClientBuilder.create()
            .setConnectionManager(connectionManager)
            .build();

    return new RestTemplateBuilder()
            .setConnectTimeout(Duration.ofMillis(1000))
            .setReadTimeout(Duration.ofMillis(1000))
            .messageConverters(new StringHttpMessageConverter(), new MappingJackson2HttpMessageConverter())
            .requestFactory(() -> new HttpComponentsClientHttpRequestFactory(httpClient))
            .build();
}

커넥션 풀을 적용한뒤 테스트를 해보니 2배이상의 성능향상이 있었다.

350/s

profile
5 Seconds rule

0개의 댓글