리액터 패턴 / 프로액터 패턴

2025. 7. 19. 20:00·Side Tech Notes

들어가며

리액터 패턴과 프로액터 패턴을 알아보기 위해 간단한 요구사항을 정의하겠습니다. 지금 미디어 처리 서비스를 개발하고 있습니다. 이 서비스에서는 사용자가 업로드한 미디어에 대해 여러가지 편집 기능을 제공해야 합니다. 썸네일 생성 기능, 워터마크 추가 기능, 포멧 변경 기능과 같은 기능들이 말이죠. 또한 설명의 용이함을 위해 싱글 스레드로 동작한다고 가정하겠습니다.

1차 해결책: switch

위의 요구사항을 반영하는 방법으로 깊은 고민 없이 switch문을 사용하기로 했습니다. 아래의 코드와 같이 말이죠.

while (true) {
	MediaRequest request = getNextRequest();
    switch (request.getType()) {
    	case "THUMBNAIL_GENERATION": // 썸네일 생성
            generateThumbnail(request);
            break;
        case "WATERMARK_ADDITION": // 워터마크 추가
            addWatermark(request);
            break;
        case "FORMAT_CONVERSION": // 포멧 변경
            convertFormat(request);
            break;
    }
}

처음에는 잘 동작했습니다. 그런데 서비스가 확장될수록 문제점이 드러나기 시작했습니다.

첫번째는 기능을 추가할 때마다 switch문에 계속 추가된다는 것이었습니다.

while (true) {
	MediaRequest request = getNextRequest();
    switch (request.getType()) {
    	case "THUMBNAIL_GENERATION": // 썸네일 생성(3초)
            generateThumbnail(request);
            break;
        case "WATERMARK_ADDITION": // 워터마크 추가(5초)
            addWatermark(request);
            break;
        case "FORMAT_CONVERSION": // 포멧 변경(15초)
            convertFormat(request);
            break;
        case "VIDEO_COMPRESSION": // 비디오 압축(60초)
            compressVideo(request);
            break;
            
        ...
    }
}

이는 SOLID의 OCP에 어긋나는 코드입니다.

 

두번째로 성능 상 문제가 발생했습니다. 예를 들어 사용자들의 요청이 비디오 압축 -> 포멧 변경 -> 워터마크 추가의 순서로 들어왔다고 가정하겠습니다. 지금 이 코드는 싱글 스레드로 돌아가기 때문에 5초밖에 걸리지 않는 워터마크 추가가 앞의 비디오 압축(60초) + 포멧 변경(15초) 총 75초 뒤에서야 처리가 됩니다. 이는 사용자들에게 좋은 경험은 아닐 겁니다.

2차 해결책: 리액터 패턴

그래서 위 문제점들을 개선하기 위한 방법으로 리액터 패턴을 도입하기로 했습니다.

리액터 패턴은 간단히 말하면 switch문에 있는 "THUMBNAIL_GENERATION"과 같은 것들을 추상화했다고 보면 좋을 것 같습니다. 이벤트 핸들러라는 인터페이스를 만들고 이벤트 핸들러의 구현체들은 각각 ThumbnailHandler, VideoCompressionHandler와 같이 하나의 작업을 담당합니다. 그리고 리액터라는 곳에서 이런 이벤트 핸들러들을 관리하면서 적절한 이벤트 핸들러 구현체를 호출하는 식으로 동작합니다. 이런 리액터 패턴을 사용하는 것 중 대표적으로 Netty가 있습니다.

 

public class ThumbnailHandler implements EventHandler {
    @Override
    public String getHandler() {
        return "THUMBNAIL_GENERATION";
    }
    
    @Override
    public void handleEvent(InputStream inputStream) {
        try {
            BufferedImage image = ImageIO.read(inputStream);
            BufferedImage thumbnail = resizeImage(image, 150, 150);
            saveImage(thumbnail, "thumbnail_" + generateId());
            sendResponse("썸네일 생성 완료");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class VideoCompressionHandler implements EventHandler {
    @Override
    public String getHandler() {
        return "VIDEO_COMPRESSION";
    }
    
    @Override
    public void handleEvent(InputStream inputStream) {
        try {
            VideoFile video = VideoLoader.load(inputStream);
            VideoFile compressed = VideoCompressor.compress(video);
            saveVideo(compressed);
            sendResponse("영상 압축 완료");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

이제 기능이 새로 추가되어도 EventHandler의 구현체를 만들고 해당 구현체를 리액터에 동적으로 등록하면 기능을 손쉽게 확장할 수 있습니다. switch문의 첫번째 문제점이었던 OCP에 위반된다는 점을 해결했습니다.

하지만 두번째 문제점은 아직도 해결하지 못했습니다. 여전히 썸네일 생성이나 영상 압축과 같은 기능들을 순차적으로 실행하기에 비디오 압축 -> 포멧 변경 -> 워터마크 추가의 순으로 오게 되면 여전히 워터마크 추가는 75초를 대기해야 실행할 수 있습니다.

3차 해결책: 프로액터 패턴

두번째 문제점은 결국 작업을 동기적으로 수행하기 때문에 발생한 문제점이었습니다. 이를 반대로 말하면 작업들을 비동기적으로 수행하면 해결할 수 있다는 뜻입니다. 그렇기에 작업들을 비동기로 실행시켜 놓고 바로 작업을 실행했다고 리턴한 뒤 나중에 작업이 완료되고 나면 그 결과를 받아 전달해주는 방식을 사용하기로 했습니다. 이것이 프로액터 패턴입니다. 프로액터 패턴에서는 리액터 패턴과 달리 이벤트로 받는 것은 작업이 완료됐을 때입니다. 실제 작업은 어플리케이션이 아닌 운영체제가 담당합니다. 그렇기에 운영체제에 의존적이라는 단점이 존재하지만 앞에 정의했던 문제점을 모두 해결할 수 있게 됩니다.

public class VideoCompressionCompletionHandler implements CompletionHandler {
    @Override
    public void handleCompletion(String operation, ByteBuffer result, Object attachment) {
        System.out.println("영상 압축 완료!");
        String requestId = (String) attachment;
        
        saveCompressedVideo(result, requestId);
        notifyUser(requestId, "영상 압축이 완료되었습니다");
        updateDatabase(requestId, "COMPLETED");
    }
    
    @Override
    public void handleError(Exception error, Object attachment) {
        String requestId = (String) attachment;
        notifyUser(requestId, "영상 압축 중 오류 발생: " + error.getMessage());
        updateDatabase(requestId, "FAILED");
    }
}

public class AIObjectDetectionCompletionHandler implements CompletionHandler {
    @Override
    public void handleCompletion(String operation, ByteBuffer result, Object attachment) {
        System.out.println("AI 객체 인식 완료!");
        String requestId = (String) attachment;
        
        List<DetectedObject> objects = parseDetectionResults(result);
        saveDetectionResults(requestId, objects);
        generateAutoTags(requestId, objects);
        notifyUser(requestId, objects.size() + "개 객체가 발견되었습니다");
    }
}

마무리

지금까지 하나의 문제점을 바탕으로 리액터 패턴과 프로액터 패턴을 사용하게 된 흐름을 확인해봤습니다. 더 자세히 알아보면 굉장히 복잡하지만 간단히 어떤 문제점을 해결하기 위해 사용하고 어떤 방식으로 사용하는 지 알아보는 게 더 중요하다고 판단해 최대한 간단하고 쉽게 설명하려고 했습니다. 결국 리액터 패턴과 프로액터 패턴은 다양한 기능들을 손쉽게 추가하기 위해 만들어진 디자인 패턴입니다. 프로젝트나 오픈소스에서 비슷한 상황을 봤을때 이게 리액터, 프로액터 패턴이구나를 알면 코드를 이해하기 더 쉬울 것 같습니다.

'Side Tech Notes' 카테고리의 다른 글

분산 트랜잭션과 합의  (0) 2025.07.21
AWS CloudFormation  (0) 2025.05.27
@Transactional과 동시성 제어를 위한 Lock의 관계  (0) 2025.05.23
빌더 패턴이 정말로 좋을까  (0) 2025.03.24
좋은 코드란 무엇일까 (feat. 객체지향)  (0) 2025.02.21
'Side Tech Notes' 카테고리의 다른 글
  • 분산 트랜잭션과 합의
  • AWS CloudFormation
  • @Transactional과 동시성 제어를 위한 Lock의 관계
  • 빌더 패턴이 정말로 좋을까
ggio
ggio
개발 공부를 하며 배운 내용을 기록합니다.
  • ggio
    기록을 하자
    ggio
  • 전체
    오늘
    어제
    • 분류 전체보기 (41)
      • SW마에스트로 (5)
      • System Architecture (8)
      • Algorithm (15)
      • Side Tech Notes (7)
      • CS (5)
      • 취준 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    다중화
    토스 NEXT
    리트코드
    코테
    Programming
    객체지향
    3PC
    SW마에스트로
    Algorithm
    fail over
    코딩테스트
    멀티 코어
    알고리즘
    부트캠프
    분산락
    leetcode
    리액터 패턴
    소프트웨어 마에스트로
    지리적 분산
    시스템 설계
    프로그래밍
    fail back
    프로액터 패턴
    ha 아키텍처
    매일메일
    at-least-once
    비관락
    시스템 아키텍쳐
    소마
    메시지 큐
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
ggio
리액터 패턴 / 프로액터 패턴
상단으로

티스토리툴바