pile·
백엔드·11번가11번가·

Java CompletableFuture로 비동기 적용하기

11번가에서 레거시 코드를 이관하며 as-is 와 to-be 로직을 나란히 실행해 결과를 비교하는데, 순차 실행이라 전체 실행 시간이 두 배가 되고 1초 차이로 조회 결과가 달라지는 문제를 겪었다. 이를 CompletableFuture 병렬 호출로 해결한 뒤, 학습하며 정리한 주요 메서드와 예외 처리, 타임아웃 옵션을 예제와 함께 공유한다.

핵심 포인트
  • 문제는 이관 전후 로직을 순차 실행해 생긴 실행 시간 두 배 증가와, 실행 시점 차이로 조회 결과가 어긋나 비교 자체가 틀어지는 것이었다.
  • Future 인터페이스는 비동기 연산을 위해 추가됐지만 연산 결합이나 완료 후 후속 처리를 붙일 수 없다는 한계가 있고, CompletableFuture 가 이를 메우며 50여 가지 메서드를 제공한다.
  • 연산 연결은 반환 타입에 따라 갈린다. 값을 반환하면 thenApply, 소비만 하면 thenAccept, 결과가 필요 없으면 thenRun, 이전 결과를 다음 Future 안에서 쓰면 thenCompose 다.
  • 독립적인 두 Future 는 thenCombine 으로 결과를 결합하고, 여러 개를 병렬로 다룰 때는 allOf 를 쓴다.
  • Async 접미사가 붙은 메서드는 별도 스레드에서 실행된다. Executor 를 넘기지 않으면 ForkJoinPool 의 common pool 을 쓴다.
상세 정리
  • get() 은 비동기 작업이 끝날 때까지 기다렸다가 결과를 반환하는 블로킹 호출이다.
  • thenApply 는 Function 을 받아 다음 연산을 처리하고 그 반환값을 담은 CompletableFuture 를 돌려준다. 주문 정보 조회 후 결제 정보를 조회하는 예로 설명한다.
  • thenAccept 는 Consumer 를 받아 CompletableFuture 를 Void 로 반환한다. 조회 후 업데이트처럼 반환값이 없는 로직에 쓴다.
  • thenRun 은 Runnable 을 받아 결과를 쓰지 않는 후처리, 예컨대 로그 남기기에 적합하다.
  • thenCompose 는 이전 단계 결과를 다음 CompletableFuture 안에서 사용하도록 이어 붙인다.
  • 순차 연결이면 비동기가 무의미하지 않냐는 의문에 대해, 단순 순차 실행이 아니라 복잡한 연산을 비동기로 이어가야 할 때 쓰임이 있다고 답한다.
  • thenCombine 은 두 Future 결과를 결합해 새 값을 만들고, thenAcceptBoth 는 결합 결과를 소비만 한다. 조회 후 정제가 필요하면 앞쪽, 인서트나 업데이트면 뒤쪽이 맞는다고 정리한다.
  • allOf 는 여러 Future 를 병렬로 처리하지만 결합된 결과값을 반환하지 못한다. 각 Future 에서 join 으로 결과를 꺼내면 그 한계를 넘을 수 있으나, 정상 완료되지 않은 경우 확인되지 않은 예외가 발생할 수 있다.
  • 병렬 조회 예제에서 주문, 옵션, 배송 정보 조회가 ForkJoinPool.commonPool 의 서로 다른 worker 스레드에서 도는 것을 로그로 보여준다.
  • Async 메서드는 Executor 인수가 없으면 common pool 사용 여부를 따져 Executor 를 얻고, 인수가 있으면 넘겨받은 Executor 로 비동기 단계를 실행한다.
  • 실제 적용은 asIs 와 toBe 로직을 각각 비동기로 호출하고 두 Future 가 모두 완료되면 결과를 비교하는 형태다. 스레드 정보를 찍어 서로 다른 스레드에서 처리되는 것을 확인했다.
  • 예외 처리는 두 갈래다. exceptionally 는 예외를 받아 별도 메서드에서 처리하고, handle 은 정상 결과와 발생한 예외를 함께 매개변수로 받는다.
  • completeExceptionally 는 특정 상황에서 Future 를 예외로 완료시킬 수 있어, 동적으로 동작이나 예외를 지정해야 할 때 쓸 수 있다.
  • 타임아웃은 세 가지다. get 에 시간을 넘기면 TimeoutException 이 나고, orTimeout 은 지정 시간까지 미완료 시 예외를 발생시키며, completeOnTimeout 은 지정한 기본값으로 Future 를 완료시킨다.
왜 읽나Java 로 여러 조회를 동시에 돌려야 하는 백엔드 개발자에게 CompletableFuture 메서드 선택 기준과 예외, 타임아웃 처리를 예제 단위로 정리해주는 입문 정리.
11번가
11번가 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 백엔드·twilio-engTwilio Engineering·

    Programmable Messaging에서 Verify API로 마이그레이션하기

    Twilio의 Programmable Messaging API로 자체 OTP 솔루션을 운영하던 서비스가 Verify API로 전환하는 방법을 코드 예시와 함께 설명한다. Verify는 OTP 전송·검증을 위한 전용 API로, 전화번호 구매, 토큰 생성, DB 저장·만료 관리를 내부에서 처리해 개발자가 직접 구현할 코드를 크게 줄인다.

    요약 이어보기
    #authentication#twilio#sms+2
  2. 백엔드·포스타입포스타입·

    포스타입이 개인화 추천을 하는 방법 2부

    포스타입 백엔드 엔지니어가 벡터 기반 개인화 추천 시스템을 실제 운영하며 맞닥뜨린 성능 장애와 용량 문제를 해결한 과정을 담은 2부다. 수백만 개의 벡터 KNN 검색이 피크 시간대에 전체 Elasticsearch 검색 성능을 흔드는 문제부터 클러스터 OOM 사태까지, 쿼리 최적화와 인프라 분리 두 가지 경로로 근본 해결에 이른다.

    요약 이어보기
    #elasticsearch#vector-search#recommendation-system+2
  3. 백엔드·포스타입포스타입·

    포스타입이 개인화 추천을 하는 방법 1부

    포스타입이 태그 기반 추천의 한계를 극복하고 벡터 임베딩 기반 개인화 추천 시스템을 구축한 과정을 담은 1부다. 유사한 콘텐츠가 다른 용어를 쓰거나 동일한 태그가 전혀 다른 톤의 콘텐츠를 가리키는 문제를 임베딩 벡터로 해결하고, OpenSearch의 HNSW ANN 검색으로 수백만 벡터를 실시간 검색하는 시스템을 구축해 구매율 15% 향상을 달성했다.

    요약 이어보기
    #opensearch#vector-search#recommendation-system+2