티스토리 뷰
728x90
리액티브 프로그래밍 (Reactive Programming)
개요
리액티브 프로그래밍은 데이터 스트림과 변화의 전파에 중점을 둔 프로그래밍 패러다임입니다. 데이터 처리를 위한 파이프라인을 구축하고, 이를 통해 지속적으로 들어오는 데이터를 자동으로 처리하는 방식을 채택합니다.
기존 프로그래밍과의 비교
명령형 프로그래밍
- 순차적인 프로세스 실행
- 현재 시점의 상태 처리
- 데이터 변경 시 수동 업데이트 필요
- "요리 레시피"와 유사한 접근 방식
// 명령형 프로그래밍 예시
let price = 100;
let quantity = 5;
let total = price * quantity;
console.log(total); // 500
price = 200; // 가격이 변경됨
// total은 자동으로 업데이트되지 않음 - 여전히 500
// 비동기 처리 예시
fetch('api/data')
.then(response => response.json())
.then(data => {
// 데이터 처리
})
.catch(error => {
// 에러 처리
});
리액티브 프로그래밍
- 데이터 스트림 기반 처리
- 시간에 따른 데이터 흐름 처리
- 자동화된 변경 전파
- "자동화된 생산 라인"과 유사한 접근 방식
명령형은 현재 시점 상태의 프로세스를 정의한다면 리액티브 프로그래밍은 데이터를 처리하는 파이프라인들을 만들어두고 이후 지속해서 들어오는 데이터들을 지속적으로 처리
// 리액티브 프로그래밍 예시 (RxJS 사용)
import { BehaviorSubject, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
const price = new BehaviorSubject(100);
const quantity = new BehaviorSubject(5);
const total = combineLatest([price, quantity]).pipe(
map(([p, q]) => p * q)
);
total.subscribe(result => console.log(result)); // 처음에 500 출력
price.next(200); // 가격이 변경되면 자동으로 새로운 total(1000) 계산되어 출력
// 비동기 처리 예시
from(fetch('api/data')).pipe(
switchMap(response => response.json()),
retry(3), // 자동 재시도
share() // 여러 구독자간 공유
).subscribe(
data => { /* 데이터 처리 */ },
error => { /* 에러 처리 */ }
);
주요 차이점 분석
1. 데이터 흐름과 상태 관리
- 명령형:
- 상태 변경을 직접 관리
- 데이터 흐름이 명시적이고 순차적
- 상태 업데이트를 수동으로 처리
- 리액티브:
- 상태 변경이 자동으로 전파
- 데이터 흐름이 선언적이고 반응적
- 의존성 있는 모든 값이 자동 업데이트
2. 비동기 처리
- 명령형:
- Promise 체인으로 처리
- 에러 처리가 각 단계별로 필요
- 복잡한 비동기 흐름 제어가 어려움
- 리액티브:
- 스트림으로 통합 처리
- 선언적 에러 처리 및 재시도 로직
- 복잡한 비동기 흐름을 연산자로 제어
3. 코드 구조와 유지보수
- 명령형:
- 절차적인 코드 구조
- 상태 변경 추적이 어려움
- 기능 확장 시 코드 수정 필요
- 리액티브:
- 선언적인 코드 구조
- 상태 변경 흐름이 명확
- 기존 스트림에 연산자 추가로 확장
4. 리소스 관리
- 명령형:
- 수동적인 리소스 정리
- 메모리 관리를 직접 처리
- 리액티브:
- 구독 기반의 자동 리소스 정리
- 메모리 누수 방지를 위한 내장 매커니즘
주요 특징
1. 데이터 스트림 중심
- 모든 데이터를 스트림으로 처리
- 연속적인 데이터 흐름 관리
- 비동기 데이터 처리 용이
2. 자동 전파
- 데이터 변경 시 자동으로 관련 처리 실행
- 의존성 있는 모든 컴포넌트에 변경사항 전파
- Push 기반의 데이터 전달
3. 선언적 프로그래밍
- 데이터 처리 파이프라인 정의
- 복잡한 데이터 흐름을 명확하게 표현
- 코드의 가독성과 유지보수성 향상
활용 사례
1. 실시간 데이터 처리
const priceStream = new Subject();
const analysisStream = priceStream.pipe(
bufferTime(1000),
map(prices => ({
average: calculateAverage(prices),
volatility: calculateVolatility(prices)
})),
filter(analysis => analysis.volatility > threshold)
);
analysisStream.subscribe(analysis => sendAlert(analysis));
2. 사용자 입력 처리
const searchPipeline = fromEvent(searchInput, 'input').pipe(
map(e => e.target.value),
debounceTime(300),
distinctUntilChanged(),
switchMap(term => fetchSearchResults(term))
);
searchPipeline.subscribe(results => updateUI(results));
3. 실시간 데이터 집계와 처리
const salesStream = new Subject();
const analyticsPipeline = salesStream.pipe(
bufferTime(60000),
map(sales => ({
total: calculateTotal(sales),
average: calculateAverage(sales)
}))
);
analyticsPipeline.subscribe(stats => updateDashboard(stats));
장단점
장점
- 자동화된 데이터 흐름 관리
- 데이터 변경에 따른 자동 업데이트
- 일관된 데이터 처리 보장
- 비동기 처리 간소화
- 복잡한 비동기 로직을 선언적으로 처리
- 에러 처리와 재시도 로직 통합 용이
- 코드 품질 향상
- 높은 가독성의 선언적 코드
- 유지보수가 용이한 모듈화된 구조
단점
- 학습 곡선
- 새로운 패러다임 적응에 시간 소요
- 복잡한 연산자와 개념 이해 필요
- 디버깅 어려움
- 비동기 실행으로 인한 디버깅 복잡성
- 스택 트레이스 추적이 어려울 수 있음
- 오버헤드
- 단순한 로직에 대한 과도한 복잡성 가능성
- 초기 설정과 구조화에 시간 소요
구현 예시
기본적인 데이터 스트림 처리
// 데이터 스트림 생성
const dataStream = new Subject();
// 처리 파이프라인 구성
const processedStream = dataStream.pipe(
filter(data => isValid(data)),
map(data => transform(data)),
debounceTime(100)
);
// 결과 구독
processedStream.subscribe(
result => console.log(result),
error => console.error(error)
);
// 데이터 입력
dataStream.next(newData);
에러 처리와 재시도
const robustStream = dataStream.pipe(
retry(3),
catchError(error => {
console.error(error);
return of(defaultValue);
}),
finalize(() => cleanup())
);
전통적인 프로그래밍 방식 VS 리액티브 프로그래밍 방식
1. 데이터 흐름 및 실행 방식
기준 | 전통적인 프로그래밍 방식 | 리액티브 프로그래밍 방식 |
---|---|---|
데이터 처리 흐름 | 요청과 응답을 순차적으로 처리 (동기 블로킹) | 데이터 스트림을 통해 이벤트가 발생할 때마다 처리 (비동기) |
흐름 제어 방식 | 한 요청이 완료되면 다음 요청을 처리하는 방식 | 데이터가 준비되면 즉시 처리하며 다른 작업은 계속 진행 |
자원 활용 | 각 요청마다 스레드나 자원을 할당하여 동작 | 하나의 스레드를 여러 요청이 공유하여 효율적 자원 사용 |
2. 블로킹과 논블로킹 방식의 차이
기준 | 전통적인 프로그래밍 방식 (블로킹) | 리액티브 프로그래밍 방식 (논블로킹) |
---|---|---|
블로킹 방식 | 요청이 완료될 때까지 스레드가 대기함 | 요청과 응답이 비동기적으로 처리되며, 다른 작업을 계속 진행 |
장점 | 코드가 직관적이고 이해하기 쉬움 | 높은 성능과 자원 최적화 가능 |
단점 | 많은 스레드가 동시에 대기 상태로 자원 낭비 가능 | 코드가 복잡해질 수 있으며 디버깅이 어려울 수 있음 |
3. 성능과 확장성
기준 | 전통적인 프로그래밍 방식 | 리액티브 프로그래밍 방식 |
---|---|---|
성능 | 대량의 동시 요청 처리 시 성능 저하 가능 | 고성능, 다수의 요청을 논블로킹 방식으로 처리 |
확장성 | 수직적 확장 필요 (더 많은 스레드와 자원 필요) | 수평적 확장 가능 (스레드와 자원을 효과적으로 활용) |
백프레셔 지원 여부 | 지원하지 않음 | 백프레셔로 데이터 처리 속도 조절 가능 |
4. 주로 사용되는 상황
기준 | 전통적인 프로그래밍 방식 | 리액티브 프로그래밍 방식 |
---|---|---|
적합한 상황 | 단일 요청/응답 처리 또는 비교적 간단한 동작을 수행하는 경우 | 대량의 요청을 처리하거나 실시간 데이터 흐름을 다루는 경우 |
응용 예시 | 일반적인 웹 애플리케이션이나 DB 요청 등 순차 처리 시 유용 | 채팅 시스템, 실시간 알림, IoT 센서 데이터 수집 등 |
5. 에러 처리 및 백프레셔
리액티브 프로그래밍에서는 에러 처리와 백프레셔가 중요한 요소로 작용합니다. 리액티브 시스템에서는 이벤트가 발생할 때마다 반응해야 하기 때문에 에러가 발생했을 때 비동기적으로 처리해야 하며, 백프레셔를 통해 데이터가 너무 빨리 들어올 경우 조절할 수 있습니다.
전통적인 방식에서는 한 번에 데이터를 요청하고 받아오는 구조이기 때문에 백프레셔가 필요하지 않지만, 리액티브 방식에서는 데이터 흐름을 제어하기 위해 필수적인 요소입니다.
정리
- 전통적인 방식: 동기적이고 블로킹 방식으로, 각 요청이 완료될 때까지 기다리며 다음 요청을 처리합니다. 이 방식은 코드가 직관적이지만, 성능 저하와 자원 낭비가 발생할 수 있습니다.
- 리액티브 방식: 비동기 논블로킹 방식으로, 요청이 완료될 때까지 기다리지 않고 다른 작업을 진행하면서 데이터가 준비되면 바로 처리합니다. 백프레셔 등을 활용하여 성능과 자원 사용을 최적화하며, 높은 동시성 요청을 처리하는 데 적합합니다.
728x90
'개발 언어 > 기타 웹개발 지식' 카테고리의 다른 글
02. Spring WebFlux 기본 개념 (0) | 2024.11.14 |
---|---|
리액티브 프로그래밍 - 부록1. (`map` vs `flatMap`) (0) | 2024.11.14 |
기타 > Node.js와 Kotlin 코루틴의 비동기 처리 방식과 차이점 (0) | 2024.10.14 |
스레드 05. 비동기 프로그래밍과 컨텍스트 스위칭 (0) | 2024.09.15 |
스레드 04. 컨텍스트 스위칭(Context Switching) (0) | 2024.09.15 |
댓글