R2DBC
R2DBC 정의
R2DBC (Reactive Relational Database Connectivity)는 이름 그대로 관계형 DB에서 reactive programming을 가능하게 해주는 데이터베이스 접근 API다.
R2DBC의 등장 배경 그리고 장점
R2DBC는 적은 스레드로 동시성을 처리하고 더 적은 하드웨어 리소스로 확장할 수 있는 논블로킹(non-blocking) 어플리케이션 스택이 필요해지면서 등장했다.
기존에 많이 쓰이던 관계형 데이터베이스 접근 API는 주로 JDBC였다. 그러나 JDBC는 블로킹(blocking) API 였고, ThreadPool로 블로킹 동작을 절충하려고 해도 완전한 논블로킹 서비스를 구축할 수 없었다.
또한 몇몇 NoSQL 드라이버가 자체 Reactive Database Clients를 제공하고 있지만, 대다수의 어플리케이션이 관계형 데이터베이스에 데이터를 저장하고 있고, NoSQL로의 마이그레이션은 프로젝트에서 쉽지 않은 선택이었다.
이는 결국 논블로킹 데이터베이스 드라이버와도 잘 동작하는 새로운 공통 API를 만드는 계기가 되었고, 적은 스레드와 하드웨어로 더 많은 동시 처리를 할 수 있는 R2DBC가 탄생했다
R2DBC를 사용할 때의 단점
1. JDBC나 JPA에서 쉽게 사용했던 여러 기능들을 제공하지 않는다.
R2DBC는 개념적으로 쉬운 것을 목표로 하고 있다. 이를 달성하기 위해, R2DBC는 caching, lazy loading, write-behind 등 ORM 프레임워크의 많은 기능들을 제공하지 않는다. 이는 R2DBC를 단순하고, 제한적이고, 독단적인(유연하지 않은) object mapper가 되도록 만들었다.
2. 몇몇 관계형 DB는 아직 공식적인 R2DBC driver implementation(드라이버 구현체)이 없다.
R2DBC를 사용하기 위해선 데이터베이스에 맞는 R2DBC driver와 그 구현체를 의존성 추가해줘야 하는데, 아직 모든 관계형 DB가 reactive driver를 지원하는 것이 아니다.
(또한 다른 reactive 드라이버들이 있다 해도(예 : Quarkus Reactive Postgres 클라이언트), 이는 R2DBC를 사용하지 않으며 성능적으로 다른 특성을 갖는다.)
공식적인 드라이버 구현체가 적어서 R2DBC를 사용하려면 비공식 드라이버 구현체를 써야 하고, 이는 가용성을 저하시킬 수밖에 없다.
(+ 가용성(Availability, 可用性) : 서버, 네트워크 등의 정보 시스템이 장애 없이 정상적으로 요청된 서비스를 수행할 수 있는 능력)
즉, R2DBC는 '가용성'이 떨어진다.
아래의 데이터베이스 드라이브는 현재 R2DBC와 함께 사용될 수 있으며, 논블로킹으로 처리된다.
- H2
- MariaDB
- MySQL
- Postgres
- Oracle
- Microsoft SQL Server (MS SQL)
- jasync
- sql MySQL
Reactive Programming
- ‘리액티브 프로그래밍’은 무엇인가?
리액티브 프로그래밍에서 중요한 키워드 4가지, non-blocking, streaming, push, back pressure
streaming
왼쪽의 전통적인 데이터 처리 방식에서는, 데이터 처리 요청이 오면 페이로드(payload)를 모두 애플리케이션의 메모리에 저장한 후에 다음 처리를 해야 합니다. 이 방식의 문제점은 전달된 데이터는 물론 저장소에서 조회한 데이터까지 모든 데이터가 애플리케이션의 메모리에 적재되어야만 응답 메시지를 만들 수 있다는 것입니다.
순간적으로 많은 요청이 몰리면서 다량의 GC(Garbage Collection)가 발생, 서버가 정상적으로 응답하지 못하는 경우가 종종 나타납니다.
그런데 많은 양의 데이터를 처리하는 애플리케이션에 스트림 처리 방식을 적용하면, 크기가 작은 시스템 메모리로도 많은 양의 데이터를 처리할 수 있습니다. 입력 데이터에 대한 파이프 라인을 만들어 데이터가 들어오는 대로 물 흐르듯이 구독(subscribe)하고, 처리한 뒤, 발행(publish)까지 한 번에 연결하여 처리할 수 있습니다. 이렇게 하면 서버는 많은 양의 데이터도 탄력적으로 처리할 수 있습니다.
non-blocking
- 우리는 주로 알고리즘 문제와 같이 절차를 명시하여 순서대로 실행되는 명령형 프로그래밍(Imperative Programming)을 한다. 기존의 명령형 프로그래밍은 데이터의 소비자가 데이터를 요청한 후 받은 결과값을 일회성으로 수신하는 방식이다. 즉, 데이터가 필요할 때마다 결과값을 매번 요청하는 점에서 비효율적이다.
- 반면 리액티브 프로그래밍(Reactive Programming)에는 하나의 데이터를 발행하는 publisher(발행자)가 있고, 그 데이터를 구독하는 subscriber(소비자)가 있다. 즉, 해당 publisher는 새로운 데이터가 들어오면 데이터의 subscriber에게 지속적으로 데이터를 전달(발행)한다. (이를 ‘데이터 스트림’이라고 한다.)
즉, Reactive Programming이란 데이터의 흐름을 먼저 정의하고 데이터가 변경되었을 때 연관된 작업이 실행되는 방식을 말한다. 프로그래머가 어떠한 기능을 직접 정해서 실행하는 것이 아닌, 시스템에 이벤트가 발생했을 때 알아서 처리되는 것이다.
- 그렇다면 리액티브 프로그래밍이 왜 non-blocking일까?
마찬가지로 논블로킹(non-blocking)은 데이터가 변경될 때 이벤트를 발생시켜 데이터를 계속해서 전달하도록 하는 프로그래밍 방식이다.
즉, 작업을 기다리기보단 완료되거나 데이터를 사용할 수 있게 되면 반응한다는 점에서, 리액티브 = 논블로킹이라고 할 수 있겠다.
Push 방식
기존의 프로그래밍 방식(명령형 프로그래밍)을 Pull 방식, Reactive 프로그래밍 방식을 Push 방식이라고도 한다. Pull 방식은 데이터를 사용하는 곳(Consumer)에서 데이터를 직접 가져와서 사용한다면, Push 방식은 데이터의 변화가 발생한 곳에서 새로운 데이터를 Consumer에게 전달한다.
- pull 방식 (동기 방식)
- 변경된 데이터가 있는지 요청을 보내 질의하고 변경된 데이터를 가져오는(pull) 방식
- client 요청 & server 응답 방식의 애플리케이션
- 절차형 프로그래밍 언어
- push 방식 (비동기 방식)
- 데이터의 변화가 발생했을 때 변경이 발생한 곳에서 데이터를 보내주는(push) 방식
- 소켓 프로그래밍
- RTC (Real Time Communication)
- DB Trigger
- Spring의 ApplicationEvent
- 스마트폰 Push 메시지
back pressure
그럼 리액티브가 push 방식이라고 하는데, 만약 발행자가 구독자의 처리 상태를 고려하지 않고 데이터를 무작정 push해서 밀어넣는다면? 만약 서버라면 오버 플로(overflow)가 발생할 수도 있다.
그래서 리액티브와 관련한 중요한 메커니즘이 하나 더 있는데, 바로 ‘논블로킹 백프레셔(back pressure)’이다.
- (먼저 짚고 넘어갈 점) 백프레셔는 '블로킹 백프레셔', '논블로킹 백프레셔'가 있다.
블로킹 백프레셔는 동기식 명령형 코드에서 호출자를 강제로 기다리게 하는 블로킹 호출이고,
논블로킹 백프레셔는 비동기식 리액티브 코드에서 발행자 속도가 구독자 속도를 넘지 않도록 이벤트 속도(데이터 생산 속도)를 제어하는 것을 말한다.
Java에서 많이 사용되는 List가 가변 길이 자료 구조형입니다. 예를 들어 SQL로 많은 양의 데이터를 질의하면 DBMS는 발행자가 되고 여러분의 서버가 구독자가 되어 List자료 구조형에 데이터를 전부 담으려고 하다가 다량의 GC가 발생하면서 서버가 정상적으로 응답할 수 없는 상태에 이를 수 있습니다.
이 문제를 어떻게 해결할 수 있을까요? 발행자가 데이터를 전달할 때 구독자가 필요한 만큼만 전달하면 해결할 수 있지 않을까요? 이게 바로 백 프레셔의 기본 원리입니다.
결국 위와 같은 문제를 해결할 수 있는 ‘논블로킹 백프레셔’가 곧 리액티브를 쓰는 주 목적인 셈이다.
리액티브 프로그래밍의 목적
그렇다면 리액티브 프로그래밍은 어떤 목적에서 생겨났을까요?
서버에서의 리액티브 프로그래밍의 탄생은 리소스의 효율적 사용을 위함에 있었습니다. 리액트 이전에는 멀티쓰레드로써 병렬처리를 했지만, 쓰레드의 확장만으로는 CPU와 메모리의 제한이 있기에 비동기와 논블로킹의 새로운 프로그래밍 모델이 제안되었습니다.
논블로킹은 일반적인 쓰레드를 통한 비동기 작업과는 다르게, 쓰레드를 점유하지 않고 작업을 수행하여 하나의 쓰레드 내에서 동시에 많은 작업을 수행 할 수 있습니다. 이와 같이 작업을 하는데 있어서 너무 많은 트래픽이 몰릴경우 문제가 발생 하거나 성능이 제대로 나오지 않을수 있기에 Back-pressure (배압, 역압)을 통해 요청의 갯수를 제한하여 ‘고가용성’을 보장해 줍니다.
좋은 성능을 보인다기 보다는 ‘고가용성’의 단어를 쓴 이유는 non-blocking을 통해 애플리케이션의 실행속도의 퍼포먼스가 좋아진다는 아니기 때문입니다. 오히려 속도적인 성능은 더 나빠질 수도 있습니다. 다만, 작은 고정된 수의 스레드와 적은 메모리로 최대한의 효율을 내면서 확장 할수 있다는 의미입니다.
여기서 고가용성(高可用性, HA, High Availability)이란 서버와 네트워크, 프로그램 등의 정보 시스템이 상당히 오랜 기간 동안 지속적으로 정상 운영이 가능한 성질을 말한다.
정리하자면,
- 리액티브 프로그래밍은 non-blocking Back-pressure를 통해 '고가용성'을 보장할 수 있다.
- 그러나 실행속도의 퍼포먼스가 좋다는 보장은 없다. (오히려 속도가 나빠질 수도 있다)
- 다만, 작은 고정된 수의 스레드와 적은 메모리로 최대 효율을 내면서 확장할 수 있다.
R2DBC와 리액티브 프로그래밍
'R2DBC는 관계형 DB에서 reactive programming을 가능하게 해주는 데이터베이스 접근 API다.'
즉, 위에서 언급한 핵심 메커니즘과 고가용성 때문에 리액티브 프로그래밍을 구현하고자 할 때, 선택할 수 있는 데이터베이스 접근 API 중 하나인 셈이다.
그렇다면 어떻게 reactive programming을 가능하게 해주는가?
- R2DBC는 '리액티브 라이브러리'를 사용하여 리액티브 스트림을 구현해 리액티브 프로그래밍을 가능하게 한다.
-> 대표적으로 스프링 데이터 R2DBC가 선택한 핵심 리액티브 라이브러리 '리액터'가 있으며, 다른 리액티브 라이브러리를 사용해도 리액티브 스트림 스펙으로 상호작용할 수 있다. - 다만, 스프링 데이터 R2DBC 레포지토리의 일반적인 룰은, 순수한 Publisher를 입력으로 받아 내부적으로 리액터 타입으로 맞추고, 이걸 사용해서 Mono나 Flux를 반환한다.
-> 따라서 어떤 Publisher든 입력으로 전달하고 연산할 수 있지만, 다른 리액티브 라이브러리를 사용하려면 출력 형식을 맞춰줘야 한다.
연관된 글 :
참고:
https://taes-k.github.io/2019/05/21/about-spring-reactive/
https://engineering.linecorp.com/ko/blog/reactive-streams-with-armeria-1
https://velog.io/@effirin/R2DBC-2.-Reactive-Programming%EC%9D%B4%EB%9E%80-zh843mp2
https://spring.io/projects/spring-data-r2dbc
https://velog.io/@effirin/R2DBC-R2DBC%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80
'개발 > back-end' 카테고리의 다른 글
JPA, Hibernate, Spring Data JPA 차이점 정리 (0) | 2025.03.03 |
---|---|
[네트워크]스토리지(Storage) (0) | 2023.05.24 |
[Apache] Kafka (0) | 2023.03.08 |