파일 시스템을 모니터링하는 방법으로 lastmodifiedTime과 WatcherService가 있습니다.
이를 이해하기 위해서는 먼저 Java I/O와 Java NIO의 차이를 이해하는 것이 중요합니다. Java I/O와 NIO는 파일과 데이터를 처리하는 방법에서 본질적인 차이가 있으며, 이 차이가 lastmodifiedTime과 WatcherService의 성능 차이와 관계가 있습니다. 이번 글에서는 이를 설명하고, 각 방법의 장단점과 성능 특성에 대해 논의하겠습니다.
Java는 파일과 네트워크 I/O 작업을 처리하는 두 가지 주요 기술을 제공합니다. Java I/O와 Java NIO. 이들은 서로 다른 방식으로 I/O 작업을 처리하며, 각 기술의 특성에 따라 성능과 사용 방법에서 큰 차이를 보입니다.
Java I/O와 NIO의 차이점
Java I/O (Input/Output)
Java의 전통적인 I/O는 스트림(Stream) 기반의 방식으로 파일을 읽거나 쓰는 방식입니다. 여기서 I/O는 블로킹(blocking) 방식으로 동작합니다. 즉, 데이터를 읽거나 쓸 때 해당 작업이 완료될 때까지 프로그램의 흐름이 멈춥니다.
- 블로킹 I/O: 파일을 읽을 때, 데이터가 준비될 때까지 기다리므로 I/O 작업이 완료될 때까지 다른 작업을 할 수 없습니다.
- 단방향 스트림: 데이터를 읽거나 쓸 때 한 방향으로만 동작합니다.
- 스트림(Stream): 파일이나 데이터 소스를 입력 스트림 또는 출력 스트림을 통해 처리합니다. FileInputStream, FileOutputStream, BufferedReader, PrintWriter 등이 대표적인 클래스입니다.
- 장점
- 단순한 사용법: Java I/O는 상대적으로 구현이 간단하며, 스트림을 통해 직관적으로 데이터를 읽고 쓸 수 있습니다.
- 호환성: Java I/O는 대부분의 플랫폼에서 기본적으로 지원되며, 운영 체제에 의존하지 않는 일관된 동작을 보입니다.
- 안정성: 전통적인 방식으로 안정적이고 예측 가능한 동작을 하므로, 많은 기존 코드에서 여전히 사용되고 있습니다.
- 단점
- 성능 저하: I/O 작업이 블로킹 방식으로 이루어지므로, 데이터를 읽거나 쓸 때마다 해당 작업이 완료될 때까지 기다려야 합니다. 이로 인해 병목 현상이 발생할 수 있으며, 많은 파일이나 네트워크 연결을 다룰 때 성능 저하가 일어날 수 있습니다.
- 비효율적인 멀티태스킹: 동기적 작업 방식으로 인해 여러 작업을 동시에 처리하기 어려운 구조입니다. 여러 파일을 처리하거나 비동기 처리가 필요한 경우, 자원을 비효율적으로 사용할 수 있습니다.
Java NIO (New I/O)
Java NIO는 논블로킹(non-blocking) 방식의 I/O 작업을 제공합니다. 즉, NIO는 데이터가 준비되지 않았더라도 프로그램의 흐름을 계속해서 진행할 수 있게 합니다. NIO는 Channel, Buffer, Selector와 같은 객체를 사용하여 효율적인 파일 및 네트워크 I/O 작업을 수행합니다.
- 논블로킹 I/O: I/O 작업을 요청하고, 데이터가 준비되면 알려주는 방식으로, 작업은 비동기적으로 처리되며, 이벤트 기반으로 결과를 받을 수 있습니다.
- 버퍼(Buffer): 데이터를 읽거나 쓸 때 직접 메모리 버퍼에 접근하여 더 빠르고 효율적인 데이터 처리가 가능합니다.
- 멀티플렉싱(Selector): 여러 I/O 채널을 하나의 스레드에서 처리할 수 있어, 여러 파일이나 네트워크 연결을 효율적으로 관리할 수 있습니다.
- 장점
- 비동기적이고 논블로킹: 여러 I/O 작업을 동시에 처리할 수 있어, 병목 현상을 줄이고 성능을 향상시킬 수 있습니다. 특히, 대량의 데이터나 네트워크 연결을 다룰 때 뛰어난 성능을 발휘합니다.
- 멀티플렉싱: Selector를 사용하여 여러 채널을 하나의 스레드에서 동시에 처리할 수 있으므로, 많은 파일이나 네트워크 소켓을 관리할 때 효율적입니다.
- 높은 성능: 버퍼를 사용하여 데이터를 직접 읽고 쓸 수 있기 때문에, 메모리 효율적이고 더 빠르게 데이터를 처리할 수 있습니다.
- 확장성: 비동기 처리와 이벤트 기반 구조 덕분에 대규모 시스템에서 많은 I/O 작업을 효율적으로 처리할 수 있습니다.
- 단점
- 복잡한 사용법: Java NIO는 Java I/O보다 구현이 복잡합니다. Channel, Buffer, Selector 등 여러 새로운 개념을 이해하고 사용해야 하므로, 학습 곡선이 가파를 수 있습니다.
- 호환성 문제: 일부 오래된 시스템에서는 Java NIO가 완전히 호환되지 않을 수 있습니다. 예를 들어, 특정 운영 체제에서 비동기 I/O가 제대로 동작하지 않거나, Java NIO의 일부 기능이 제한될 수 있습니다.
즉, I/O와 NIO의 사용시기는 아래와 같습니다.
I/O
- 간단한 파일 처리나 스트림 기반 작업에 적합합니다.
- 성능이 중요하지 않거나, 상대적으로 적은 양의 데이터를 처리할 때 적합합니다.
- 단순성이 중요한 경우, 예를 들어 로그 파일 처리, 작은 파일 처리 등에 사용하기 좋습니다.
NIO
- 고성능 시스템에서 대량의 데이터를 처리하거나, 네트워크 연결을 효율적으로 관리할 때 유리합니다.
- 비동기적이고 효율적인 작업 처리가 필요할 때 선택합니다. 예를 들어, 웹 서버, 대용량 파일 처리, 실시간 데이터 처리 시스템에서 효과적입니다.
- 멀티태스킹 처리가 필요한 경우, 하나의 스레드에서 여러 I/O 작업을 병렬로 처리해야 할 때 매우 유용합니다.
lastModifiedTime과 WatcherService는 모두 파일 시스템에서 파일이나 디렉토리의 변경을 감지하고 추적하는 데 사용되는 방식입니다. 이 두 가지 방법을 비교하고 선택하는 이유는 파일 변경 감지 작업에서의 성능, 리소스 낭비를 최소화하고, 실시간 처리 요구사항에 맞는 방식을 선택하기 위함입니다.
lastModifiedTime와 WatcherService 비교
각 방식의 작동 원리와 특성
- lastModifiedTime
- 파일의 마지막 수정 시간을 확인하는 방식으로, 주기적인 폴링(polling)으로 작동합니다.
- 파일 변경을 감지하는 방식이라기보다 파일을 일정 간격으로 확인하는 방식입니다.
- 장점
- 간단하게 파일의 수정 시점을 추적할 수 있습니다.
- 구현이 간단하고 추가적인 리소스를 요구하지 않습니다.
- 단점
- 폴링(polling) 방식으로 사용되므로 주기적으로 파일의 수정 시간을 확인해야 합니다.
- 폴링 방식은 주기적으로 또는 정해진 시간 간격으로 어떤 작업이나 이벤트의 상태를 확인하는 방식
- 주로 파일 시스템 감지, 네트워크 요청 확인, 시스템 상태 점검 등에 사용
- 이로 인해 불필요한 I/O가 발생할 수 있으며, 실시간으로 변경 사항을 추적하기 어렵습니다.
- 폴링(polling) 방식으로 사용되므로 주기적으로 파일의 수정 시간을 확인해야 합니다.
- WatcherService
- 이벤트 기반으로 동작하며, 특정 디렉토리의 파일 시스템 이벤트(파일 생성, 수정, 삭제 등)를 실시간으로 감지합니다.
- 이로 인해 실시간 감지가 필요할 때 적합하지만, 구현이 복잡하고 자원 소모가 클 수 있습니다.
- 장점
- 실시간으로 파일 시스템의 변경 사항을 감지할 수 있습니다.
- 비동기적으로 동작하기 때문에 성능이 향상되고, 리소스 소비가 적습니다.
- 단점
- WatchService는 특정 파일 시스템에서 지원되지 않을 수 있습니다.
- 디렉토리 내의 하위 디렉토리나 파일에 대한 감지가 제한될 수 있습니다.
- I/O와 NIO는 파일 시스템과 상호작용하는 방식에서 중요한 차이를 보입니다. 예를 들어, 파일의 마지막 수정 시간을 확인하는 경우, java.io.File 클래스를 사용하면 간단히 접근할 수 있습니다. 반면, NIO에서는 java.nio.file.Files 클래스를 활용하여 수정 시간을 얻을 수 있습니다. 따라서, 파일의 수정 시간 확인 및 파일 시스템 감시와 같은 작업에서 I/O와 NIO의 선택은 사용자의 요구사항에 맞춰 적절히 결정해야 합니다.
- 또한, WatchService는 파일 시스템의 변화를 감지할 수 있는 기능으로, java.nio.file.WatchService 클래스에 포함되어 있습니다. 이처럼 NIO는 파일 시스템을 처리할 때 더 높은 성능과 효율성을 제공하며, 대규모 파일 처리나 비동기 처리가 필요한 경우에 더 적합합니다.
정리
- IO 라이브러리 : java.io.File 또는 java.nio.file.Files 클래스를 사용하여 lastModifiedTime을 확인하는 방식은 전통적인 블로킹 IO 방식입니다.
- NIO 라이브러리 : java.nio.file.WatchService는 비동기적이고 논블로킹 방식으로 파일 시스템 이벤트를 실시간으로 감지하는 기능을 제공합니다.
성능 비교 실험
성능 비교 테스트 코드
두 방식의 성능을 VisualVM을 통해 비교합니다.
lastModifiedTime 테스트 코드
public class LastModifiedPerformanceTest {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
Path path = Paths.get("example.txt");
try {
Files.getLastModifiedTime(path);
} catch (IOException e) {
e.printStackTrace();
}
}
long endTime = System.currentTimeMillis();
System.out.println("Execution Time: " + (endTime - startTime) + " ms");
}
}
WatcherService 테스트 코드
public class WatcherServicePerformanceTest {
public static void main(String[] args) throws IOException {
WatchService watchService = FileSystems.getDefault().newWatchService();
Path path = Paths.get("./");
path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
WatchKey key = watchService.poll();
if (key != null) {
key.pollEvents();
key.reset();
}
}
long endTime = System.currentTimeMillis();
System.out.println("Execution Time: " + (endTime - startTime) + " ms");
}
}
결과 분석
VisualVM 결과를 바탕으로 두 방식의 성능을 비교하면 다음과 같은 결론을 얻을 수 있습니다
- lastModifiedTime
- Execution Time: 1309 ms
- VisualVM으로 확인한 결과, 파일 I/O 작업의 빈번한 호출로 CPU와 메모리 사용량이 급격히 증가했습니다. 이는 블로킹 I/O의 단점이 드러나는 지점입니다.
- WatcherService: 초기에 높은 자원 사용량을 보이지만, 대규모 디렉터리 감지에는 효율적입니다.
- Execution Time: 3 ms
- VisualVM으로 확인한 결과, WatcherService는 변경 이벤트만 감시하는 구조로 설계되어, CPU 사용량은 감소하지만 이벤트 큐와 내부 구조로 인해 메모리 사용량은 LastModifiedTime 방식보다 다소 높을 수 있습니다.
참고
'JAVA & Spring > JAVA' 카테고리의 다른 글
[JAVA] 프로젝트의 토대, 자바 선택 이유 (0) | 2024.03.03 |
---|---|
[JAVA] Mock이란? (0) | 2022.04.15 |
[JAVA] 문자열 선택: String과 StringBuilder/StringBuffer 사용 방법과 차이 (0) | 2022.04.06 |