Theme:

리액티브 시스템(Reactive System)과 리액티브 프로그래밍(Reactive Programming)

리액티브 시스템(Reactive System)리액티브 프로그래밍(Reactive Programming) 을 직역하면 각각 ‘반응성 있는 시스템’, ‘반응성 있게 프로그래밍하는 방식’ 정도로 이해할 수 있습니다.

전통적인 Spring Web MVC 기반의 서버요청 하나당 하나의 스레드를 할당하여 처리하는 방식이기 때문에 동시 요청 수가 적은 경우에는 빠른 응답 속도를 보이지만 동시 요청 수가 매우 많은 경우에는 응답 시간이 길어질 수 있습니다. 만약 외부 I/O 가 활발히 일어나는 요청의 경우, 응답 시간이 더욱 길어질 수 있겠죠.

반응성 있는 시스템(Reactive System) 이란 요청을 무조건 빠르게 처리하는 시스템이 아닌, 동시에 많은 요청이 몰리더라도 응답시간을 일정하게 유지하도록 설계한 시스템 을 의미합니다. 이러한 반응성 있는 시스템 을 구현하기 위해 리액티브 프로그래밍 이라는 개념이 등장하게 되었습니다.

>> 리액티브 시스템 설계 원칙 보러가기

리액티브 프로그래밍(Reactive Programming)

위키백과 / 리액티브 프로그래밍 에서는 리액티브 프로그래밍을 아래와 같이 정의하고 있습니다.

Reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change.

위 정의를 보면 리액티브 프로그래밍이란 선언형 프로그래밍 패러다임 으로 데이터 스트림(Data Stream), 변경 전파(The propagation of change) 를 주로 다루는 프로그래밍 패러다임이라는 것을 알 수 있습니다. 그렇다면 각각이 무엇을 의미하는지 알아보도록 하겠습니다.


선언형 프로그래밍(Declarative Programming)이란 ?

선언형 프로그래밍 이란, 명령형 프로그래밍(Imperative Programming) 방식과는 다르게 어떻게 하는 것이 아닌 어떤 결과를 얻고자 하는지 선언하는 방식을 의미합니다. 이해를 위해 명령형 프로그래밍 방식과 선언형 프로그래밍 방식을 사용해서 1부터 100까지의 숫자 중 짝수를 출력하는 코드를 작성해보겠습니다.

  • 1부터 100까지 짝수 출력하기 / 명령형 프로그래밍
    JAVA
    for (int i = 1; i <= 100; i++) {
        if (i % 2 == 0) {
            System.out.println(i);
        }
    }
    

선언형 프로그래밍 방식은 아래와 같습니다.

  • 1부터 100까지 짝수 출력하기 / 선언형 프로그래밍
    JAVA
    IntStream.rangeClosed(1, 100)
        .filter(i -> i % 2 == 0)
        .forEach(System.out::println);
    

예시 코드에서 알 수 있듯이 명령형 프로그래밍 에서는 forif 를 사용하여 반복과 분기 흐름을 직접 제어하면서 1부터 100까지의 숫자를 하나씩 짝수인지 검사하고 출력하는 절차 자체를 상세하게 명시해야합니다.

반면 선언형 프로그래밍 에서는 1부터 100까지의 숫자 중 짝수만 골라서 출력하기 위해 rangeClosed, filter, forEach 와 같은 연산자를 조합해 "무엇을 할지"만 선언하고, 실제 반복을 어떻게 수행할지는 라이브러리에 위임합니다.

자바 8부터 도입된 람다를 비롯한 함수형 프로그래밍 개념이 아직 낯설게 느껴지신다면, 모던 자바 인 액션(Modern Java in Action) 을 한 번 읽어보는 것을 추천드립니다.


데이터 스트림(Data Stream) 이란 ?

리액티브 프로그래밍에서 이야기하는 데이터 스트림(Data Stream) 이란, 값 하나를 다루는 것이 아니라 시간에 따라 순서대로 흘러오는 데이터(이벤트)의 연속적인 흐름 을 의미합니다.

  • 데이터 스트림 예시
    1. 초당 수백 수천 건씩 발생하는 HTTP 요청들
    2. 메세지 큐(Kafka, RabbitMQ 등)에 전달되는 메세지들

위의 데이터 스트림 예시에서 알 수 있듯, 데이터 스트림은 한 번에 값 하나가 아니라 시간에 따라 순서대로 흘러오는 데이터들의 흐름 전체를 의미하며 이러한 데이터 스트림 위에 map, flatMap, filter, collect 등의 연산자를 선언적으로 사용하여 데이터를 처리하고, 처리된 데이터를 다른 데이터 스트림으로 전달하는 방식을 리액티브 프로그래밍 이라고 합니다.


변경 전파(The propagation of change) 란 ?

변경 전파(The propagation of change) 란, 어떤 값이나 데이터 흐름이 변경되었을 때 그 값에 의존하고 있는 다른 값이나 연산까지 변화가 자동으로 전달·반영되는 것을 의미합니다. 예를 들어 상위 메서드 체인에서 값을 변환하면, 그 변환된 값이 그대로 하위 메서드 체인으로 흘러가면서 다시 처리되는 동작이 변경 전파의 한 형태라고 볼 수 있습니다.

  • 변경 전파 예시
    JAVA
    IntStream.rangeClosed(1, 10)
      .map(i -> i * i) // 제곱: 1,4,9,...,100
      .mapToObj(i -> "제곱한 값은 " + i + "입니다.") // 위에서 바뀐 값을 문자열로 변환
      .forEach(System.out::println); // 최종 출력
    

위 코드에서 일어나는 일을 단계별로 보면 다음과 같습니다.

  1. rangeClosed(1, 10) 에서 1부터 10까지의 숫자가 순서대로 스트림으로 흘러옵니다.
  2. 첫 번째 map 에서 각 숫자는 제곱되어 1, 4, 9, ..., 100 으로 값이 변경됩니다.
  3. 두 번째 map 은 이 변경된 값들을 입력으로 받아 "제곱한 값은 4입니다." 같은 문자열로 다시 변환합니다.

위의 예시 코드의 핵심은 앞 단계의 map 에서 값이 한 번 바뀌면, 그 “바뀐 값”이 자동으로 다음 map 으로 전달되고, 그 다음 연산들도 모두 이 변경된 값을 기준으로 다시 수행된다는 점으로 이와 같이 상위 연산에서의 변화가 하위 연산들로 자연스럽게 흘러 내려가며 전체 처리 과정이 갱신되는 것을 리액티브 프로그래밍에서는 변경 전파라고 합니다.


비동기 · 논블로킹 I/O

비동기·논블로킹 I/O 는 I/O 작업을 요청해 놓고 결과를 기다리며 스레드가 멈춰있지 않고, 그 사이에 다른 요청을 계속 처리할 수 있게 해 주는 방식을 말합니다. 기존의 블로킹 I/O 방식 은 처리 지연 또는 외부 I/O 응답을 기다리는 동안 스레드가 대기 상태가 되기 때문에 자원을 효율적으로 사용하여 좋은 성능을 내기 어렵지만, 논블로킹 I/O 방식 은 요청 직후 바로 제어권이 돌아와 동일한 스레드로 여러 I/O 작업을 번갈아 처리할 수 있습니다.

리액티브 프로그래밍은 이러한 논블로킹 I/O 위에서 데이터 흐름을 이벤트 스트림으로 다루면서, 적은 스레드로 많은 동시 요청을 처리하는 것을 목표로 하며, 비동기 · 논블로킹 I/O 와 관련된 자세한 내용은 추후에 따로 알아보도록 하겠습니다.

댓글 로딩 중...