파일 서버에 1GB 파일 패치 요청 실패 (간단한 JVM 튜닝으로 해결하기)

주싱·2023년 4월 12일
0

Network Programming

목록 보기
16/21

1GB 파일 패치 실패

네티 프레임워크를 사용해 간단한 학습용 파일 서버를 구현하고 있습니다. 파일 서버의 성능을 테스트하기 위해 다양한 크기의 파일 패치(Fetch)를 서버에 요청하고 패치된 파일을 로컬 스토리지에 저장하기 까지의 시간을 측정했습니다. 테스트는 서버와 클라이언트를 동일한 컴퓨터에 하나의 프로세스에 띄우고 loopback(127.0.0.1) 주소로 통신했습니다.

전체 코드는 GitHub에서 확인할 수 있습니다.

@Test
void serviceFile() throws Exception {
    // Given: 서버 측, 서비스 파일 생성
    int megaBytes = 1_000;
    File remoteFile = FileUtils.newRandomContentsFile(remoteFilePath, megaToByte(megaBytes));

    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // When: 클라이언트 측, 파일 패치 요청
    client.remoteFileAccessor()
            .remote(remoteFilePath)
            .local(localFilePath)
            .fetch().addListener(future -> {
                // 파일 패치 시간 측정
                stopWatch.stop();
                System.out.printf("File(%,d MB)fetch time: %.3f sec\n", megaBytes, stopWatch.getTotalTimeSeconds());
            }).sync();

    // Then: 패치된 파일이 서버 측 파일과 일치하는지 확인
    File localFile = new File(localFilePath);
    assertTrue(localFile.exists());
    assertTrue(org.apache.commons.io.FileUtils.contentEquals(remoteFile, localFile));
}

파일 크기를 바꿔 가면서 테스트를 진행했는데 5MB(약 0.06초), 100MB(약 1.2초) 크기의 파일을 테스트한 후 마지막으로 1GB 크기의 대용량 파일 패치를 테스트해 보는데 OutOfMemoryError가 발생했습니다.

오류 메시지를 살펴보면 1,040,187,392 바이트의 다이렉트 버퍼 메모리를 예약하려고 했는데 실패했다고 합니다. 그리고 이미 1,069,563,905 바이트가 할당되었고, 최대 할당 가능한 다이렉트 버퍼 메모리 크기는 2,107,637,760 바이트(약 1.9GB)라고 합니다.

다이렉트 버퍼 메모리

JVM은 생성하는 객체를 힙 메모리에 할당한다고 알고 있는데 오류 메시지에 등장한 다이렉트 버퍼 메모리는 생소해서 간단히 조사해 보았습니다.

힙 메모리

우선 힙 메모리는 우리가 익히 아는 JVM이 사용하는 기본적인 메모리입니다. 가비지 컬렉터에 의해 관리를 받기 때문에 프로그램에서 사용하지 않는 메모리는 자동으로 반환되어 편리합니다.

다이렉트 메모리

다이렉트 메모리는 운영체제에 요청해서 할당 받는 JVM 힙 메모리 외부의 네이티브 메모리 영역입니다. 가비지 컬렉터의 관리를 받지 못하기 때문에 C, C++ 처럼 동적으로 할당한 메모리를 명시적으로 해제해 주어야 할 책임을 가집니다. 네티에서 제공하는 ByteBuf는 내부 버퍼를 힙 메모리 또는 다이렉트 메모리에 선택적으로 할당할 수 있습니다. 뿐만아니라 ByteBuf는 ReferenceCounted 라는 인터페이스를 구현하고 있어 참조 카운터를 관리하여 네티에 여러 모듈들에 의해 메모리 해제를 자동으로 수행할 수 있도록 합니다. 다이렉트 버퍼는 GC의 영향을 받지 않기 때문에 I/O를 위한 대용량 데이터를 다룰 때 좋은 성능을 낸다고 합니다. 반면에 작은 데이터의 경우에는 힙 메모리를 사용할 것을 권합니다. 다이렉트 메모리 활용과 관련된 부분은 확실히 이해하지 못해서 다음에 조금 더 공부해 보아야겠습니다.

JVM 옵션 조정

정리해보면 위 문제는 제한된 다이렉트 메모리 이상의 공간을 애플리케이션에서 예약하려 했기 때문에 발생했습니다. 간단히 JVM 옵션을 조정해 주면 문제가 해결됩니다. 문제 해결 후 1GB 파일 패치 후 저장까지 시간을 측정해 보니 약 73.9초 시간이 소요되었습니다. 생각보다 시간이 오래 걸렸습니다. 다른 파일 서버들과 성능 비교를 한번 해보고 개선할 것들을 개선해 봐야겠습니다.

-XX:MaxDirectMemorySize=2g

만약 서비스에 적용한다면

구현 중인 파일 서버는 네티의 파일 처리 기능과 성능을 확인하기 위한 학습용 코드입니다. 만약 서비스 코드라면 무작정 JVM 옵션을 조정하는 것은 적절한 해결책이 아닙니다. 왜냐하면 몇 명의 클라이언트가 동시에 1GB 파일 처리를 요청하면 금새 다시 메모리 제한 양을 초과할 것이기 때문입니다. 따라서 1GB 파일을 한번에 처리하는 것 보다는 파일들을 작은 조각들로 나누어 처리하도록 프로토콜을 설계하고 처리하는 것이 좋겠습니다.

profile
소프트웨어 엔지니어, 일상

0개의 댓글