새로운 Java SDK가 완전히 반응형 및 비동기 컴포넌트를 기반으로 한다고 발표한 이후, 사용자들은 기존의 동기식 액세스 패턴에 비해 어떤 이점이 있는지 계속 질문하고 있습니다. 흔히 '성능'이 주요 요인 중 하나로 꼽히지만, 고려해야 할 사항은 훨씬 더 많습니다. 오류 처리(별도의 포스팅에서 다룰 예정) 외에도 다음과 같은 기능이 눈에 띕니다:
- 데이터 스트림으로 데이터 흐름 구축
- 골치 아픈 문제 없이 리소스 활용도 향상
이 블로그 게시물의 목적은 일반적인 데이터 액세스 패턴을 동기식에서 반응형으로 전환하고 그 결과 이 새로운 접근 방식을 통해 어떤 것이 가능한지 엿볼 수 있도록 하는 것입니다. 아직 '반응형' 코드를 작성해 본 적이 없더라도 걱정하지 마세요. 이 과정에서 몇 가지 기본 사항을 익히게 될 것입니다. 또한 동기식 API는 여전히 지원된다는 점도 기억하세요. 동기식 API는 리액티브 API를 얇게 감싸는 역할을 하므로 당장 심층적으로 뛰어들지 않고도 애플리케이션을 점진적으로 더 강력한 접근 방식으로 마이그레이션할 수 있습니다.
참고로, 저희는 웨비나 예정 1월 22일에는 SDK 엔지니어 중 한 명인 Simon Basle이 새로운 Java SDK를 소개하고 리액티브 비트도 선보일 예정입니다. 함께 참여하여 질문해 보세요!
조회 패턴
데이터베이스나 키/값 저장소에 액세스할 때 매우 일반적인 패턴은 조회 패턴입니다. 문서를 로드하고 그 문서 내용에 따라 더 많은 문서를 로드하는 것입니다. 전형적인 "블로그 게시물과 댓글"의 예를 생각해 봅시다. 다음과 같은 블로그 게시물이 Couchbase에 저장되어 있다고 가정해 보겠습니다:
여기에는 필요에 따라 각 콘텐츠의 전체 세부 정보를 로드하는 데 사용할 수 있는 댓글 ID 목록이 포함되어 있습니다. 댓글은 다음과 같이 보일 수 있습니다:
이제 블로그 글이 로드될 때 게시되는 처음 두 개의 댓글을 로드하여 실제 글 아래에 표시되도록 하려고 한다고 가정해 보겠습니다. 다음은 동기식 액세스와 새로운 SDK를 사용하여 이를 달성하는 한 가지 방법입니다:
오류 처리는 제쳐두고 이 접근 방식에는 몇 가지 단점이 있습니다:
- 네트워크가 응답할 때까지 최소 3번 이상 기다려야 하므로 스레드가 유휴 상태로 유지되는 동안 잠재적으로 가치 있는 작업을 대신 수행할 수 있습니다.
- 모든 작업에는 개별적인 타임아웃이 있기 때문에 전체 프로세스에 글로벌 타임아웃을 적용하는 것은 매우 어렵습니다.
이를 Java 8 람다를 사용하여 반응형 접근 방식으로 변환해 보겠습니다(Java 6/7도 지원되며, 콜백 대신 익명 클래스를 사용하면 됩니다):
운영자의 이름을 보면 무슨 일이 일어나고 있는지 명확하게 알 수 있습니다. 이 코드는 블로그 게시물을 로드합니다. 코드가 도착하면 댓글 ID 목록을 추출하여 실제로 댓글 문서를 로드하는 다음 메서드로 전달합니다. 필터 메서드는 게시된 댓글만 통과시킵니다. 그 후에는 처음 2개의 댓글만 가져온 다음 구독을 취소하여 추가 작업(이미 충분한데 더 많은 문서를 로드하는 등)을 피합니다. 마지막으로 발견된 모든 댓글을 목록으로 집계하고 글로벌 타임아웃을 적용한 다음 차단합니다.
이 코드는 필요한 모든 코멘트를 병렬로 로드하기 위해 "팬 아웃"하여 클러스터의 더 많은 노드를 동시에 호출하고 결과적으로 원하는 결과를 더 빨리 반환합니다. 또한 이 코드는 전체 "작업"에 글로벌 타임아웃을 적용하는데, 이는 동기식 코드에서는 제대로 적용하기가 매우 어렵습니다. 마지막으로 전체 코드를 컴포저블할 수 있습니다. 타임아웃을 적용하거나 "취할" 주석의 양을 지정하지 않고도 로직을 메서드에 캡슐화할 수 있습니다. 그런 다음 상위 계층은 원하는 대로 작업을 수행하는 데 필요한 연산자를 체인으로 연결할 수 있습니다.
또한 더 강력한 개념(비동기, 반응형)에서 덜 강력한 개념(동기화)으로 전환하는 것이 매우 쉽다는 것을 알 수 있습니다. 그 반대의 경우는 불가능합니다.
여전히 맨 마지막에 차단되지만 괜찮습니다. 대부분의 애플리케이션은 어느 시점(사용자에게 응답을 반환하기 직전일 수도 있음)에 차단합니다. 전체 스택을 '반응형'으로 사용하면 성능과 리소스 활용도가 가장 좋지만, 코드의 많은 부분을 비동기적으로 실행하여 큰 이점을 얻을 수 있습니다.
쿼리 실행
당연히 모든 데이터베이스에서는 몇 가지 기준에 따라 저장된 문서를 쿼리할 수 있습니다. 대부분의 경우 하나 이상의 레코드가 반환되며, 때로는 수천 개의 레코드가 한 번에 반환되기도 합니다. 데이터가 반환된 후에는 애플리케이션 요구 사항에 따라 콘텐츠를 수정, 결합 또는 필터링해야 하는 경우가 많습니다.
통신 서비스 제공업체에서 버킷에 사용자 기록을 저장하고 있다고 가정해 보겠습니다. 월말에 이번 달에 가입한 모든 신규 고객이 실제로 새 휴대폰을 배송받았는지 확인하고 싶다고 가정해 보겠습니다. 계약한 택배 회사는 배송 상태를 조회할 수 있는 웹 서비스를 제공합니다.
다음은 공급자의 모의 구현입니다:
소포 ID를 나타내는 UUID가 주어지면 소포가 이미 배송되었는지 여부를 무작위로 반환합니다.
사용자 기록의 모습은 다음과 같습니다:
N1QL을 사용하여 지난 달의 모든 사용자를 가져온 다음 소포 ID를 제공업체에 전달합니다. 다음은 동기식 버전입니다:
이 코드는 그렇게 나쁘지 않죠? 하지만 택배 업체의 웹 서버가 엄청나게 느려서 가끔 500밀리초가 아닌 10초 만에 쿼리를 반환한다고 하면 어떨까요? 또한 결과를 스트리밍한 다음 결과가 도착하는 즉시 작업을 수행할 수 있는 가능성을 잃게 됩니다.
운이 좋아서 2,000명의 신규 고객이 가입했다고 가정해 보겠습니다. 먼저 2,000명의 사용자가 쿼리될 때까지 기다린 다음 택배 업체의 웹 서버에 대해 2,000개의 직렬 쿼리를 수행해야 합니다. 물론 실행자 서비스로 분산시킬 수도 있지만, 오케스트레이션과 집계는 자체적으로 수행해야 합니다.
반응형 버전으로 훨씬 더 잘할 수 있습니다:
여기서는 서버에서 도착하는 대로 결과를 스트리밍하고 있습니다. 결과가 나오면 소포 ID를 추출하여 소포 서버로 전송합니다. 응답이 도착하면 이를 가져와 배송 상태별로 그룹화합니다. 마지막으로 멋진 서식을 적용하고 인쇄합니다. 우리는 절대 차단하지 않기 때문에 모든 요청을 택배 서버로 보내고 요청이 돌아오면 순서와 상관없이 그룹화합니다. 일부 요청이 더 오래 걸리더라도 다른 요청을 먼저 처리할 수 있고 막히지 않기 때문에 크게 신경 쓰지 않습니다. 전체 시간 제한을 적용할 수도 있습니다.
요약
이 두 가지 예는 오늘날 애플리케이션이 비동기 및 반응형 실행을 통해 어떤 이점을 얻을 수 있는지 명확하게 보여줍니다. 애플리케이션 서버와 응답 시간을 늦추는 리소스를 많이 차지하는 데이터베이스 드라이버를 차단하는 것에서 벗어날 수 있는 명확한 경로를 제공합니다.
이제 겨우 시작 단계에 불과합니다. 저희는 이 새로운 데이터베이스 액세스 애플리케이션 작성 방식에 대한 명확한 안내를 제공하기 위해 샘플 프로젝트와 확장된 설명서를 준비 중입니다. 사용 사례를 살펴보고 싶거나 어떻게 혁신할 수 있는지 알고 싶으시다면 알려주세요.
마지막으로, 며칠 후 다시 한 번 알려드리자면, 며칠 내에 웨비나 를 통해 새로운 Java SDK에 대해 알아보세요. 참여하여 소개를 듣고 궁금한 점이 있으면 주저하지 말고 질문하세요!
매우 유용한 블로그 게시물입니다. 전체 데이터 흐름이 비동기적이고 차단되지 않도록 각 메서드가 내부에서 어떻게 작동하는지에 대한 자세한 설명이 있었으면 좋겠습니다. 행/레코드가 도착하면 어떻게 다음 메서드 호출로 넘어가게 되나요? 저는 거의 상상할 수 없었고, 따라서 SDK의 내부에 대한 질문이 생겼습니다.