포스트

2022-05-03-TIL

Today I Learned

Elasticsearch는 Apache Lucene 기반의 분산 검색 및 분석 엔진이다. 문서를 JSON으로 저장하고, 저장할 때 텍스트를 분석기(analyzer)로 토큰으로 나눠 역색인(inverted index)을 만든다. 역색인은 단어에서 그 단어가 들어 있는 문서 목록으로 가는 색인이라, 본문 검색과 관련도 순위 계산이 빠르다. 인덱스는 여러 샤드로 나뉘어 노드에 분산되고, 레플리카 샤드로 가용성과 읽기 성능을 높인다. 모든 기능은 REST API로 다루며, Kibana, Logstash, Beats와 함께 Elastic Stack을 이룬다.

자바에서 접근하는 클라이언트는 바뀌는 중이다. Elastic 문서에 따르면 기존 High Level REST Client는 7.15.0에서 deprecated되었고, 타입이 지정된 요청/응답 객체를 쓰는 새 Java API Client(elasticsearch-java)가 이를 대체한다. 한국어 검색은 기본 분석기로는 형태소 단위로 나뉘지 않으므로 Nori 같은 한국어 형태소 분석기 플러그인을 쓴다.

  • https://m.blog.naver.com/rickman2/221478983246
  • https://www.elastic.co/kr/
  • https://www.elastic.co/guide/en/elasticsearch/reference/current/starting-elasticsearch.html
  • https://esbook.kimjmin.net/01-overview/1.1-elastic-stack/1.1.1-elasticsearch
  • https://www.elastic.co/kr/learn?ultron=B-Stack-Trials-APJ-BMM&gambit=Stack-Tutorial&blade=adwords-s&hulk=paid&Device=c&thor=elasticsearch%20tutorial&gclid=Cj0KCQjwpcOTBhCZARIsAEAYLuWvZblvMJC7v4DBYE0ErRFo09gQyR95y4fZLsTWpQR6P1mGMZAEBMAaAr9gEALw_wcB
  • https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html
  • https://www.baeldung.com/elasticsearch-java
  • https://github.com/elastic/elasticsearch-java
  • https://shanepark.tistory.com/139
  • https://www.youtube.com/watch?v=dlChXjE7IHw

Java MDC

MDC(Mapped Diagnostic Context)는 로그 메시지에 요청 ID, 사용자 ID 같은 문맥 정보를 자동으로 붙이기 위한 기능이다. SLF4J/Logback의 MDC.put("requestId", id)로 값을 넣으면, 로그 패턴의 %X{requestId}가 그 스레드에서 찍히는 모든 로그에 값을 채워 준다. 여러 요청이 섞여 찍히는 서버 로그에서 한 요청의 흐름만 골라 보기 쉬워진다.

Logback 문서에 따르면 MDC는 스레드별로 관리되며, 자식 스레드는 현재 스레드의 MDC를 자동으로 물려받지 않는다. 그래서 다음을 지켜야 한다.

  • 필터나 인터셉터에서 요청 시작 시 값을 넣고, 요청이 끝나면 MDC.clear()로 지운다. 스레드 풀이 스레드를 재사용하므로 지우지 않으면 다음 요청 로그에 이전 값이 남는다.
  • @Async나 별도 스레드 풀로 작업을 넘길 때는 MDC.getCopyOfContextMap()으로 복사해 작업 스레드에서 setContextMap()으로 넣어 준다. Spring에서는 TaskDecorator로 이를 일괄 처리할 수 있다.

  • https://www.baeldung.com/mdc-in-log4j-2-logback
  • https://bcho.tistory.com/1316
  • https://jehuipark.github.io/java/java-logging-mdc

REST API Design

REST API 설계 가이드들이 공통으로 권하는 원칙은 다음과 같다.

  • URI는 동사가 아니라 자원을 나타내는 명사로, 컬렉션은 복수형으로 쓴다(/orders, /orders/{id}). 행위는 HTTP 메서드(GET, POST, PUT, PATCH, DELETE)로 표현한다.
  • 관계는 경로 계층으로 표현하되 너무 깊게 중첩하지 않는다(/customers/{id}/orders 정도).
  • 상태 코드를 의미에 맞게 쓴다. 생성은 201과 Location 헤더, 잘못된 요청은 400, 인증 실패는 401, 권한 없음은 403, 없는 자원은 404다.
  • 필터링, 정렬, 페이징은 쿼리 파라미터로 표현한다(?status=paid&sort=createdAt,desc&page=2).
  • 에러 응답 형식을 통일하고, 버전 관리 방식(경로의 /v1, 헤더 등)을 미리 정한다.

Microsoft의 API 설계 가이드는 자원 중심 설계와 HTTP 메서드 의미, 비동기 작업, 페이징, HATEOAS까지 다루고, Google API 설계 가이드는 표준 메서드(List, Get, Create, Update, Delete)와 사용자 정의 메서드를 중심으로 자원 지향 설계를 설명한다.

  • https://www.mulesoft.com/resources/api/what-is-rest-api-design#:~:text=REST%20or%20RESTful%20API%20design,when%20used%20for%20Web%20APIs.
  • https://stackoverflow.blog/2020/03/02/best-practices-for-rest-api-design/
  • https://restfulapi.net/rest-api-design-tutorial-with-example/
  • https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design
  • https://wayhome25.github.io/etc/2017/11/26/restful-api-designing-guidelines/
  • https://www.freecodecamp.org/news/rest-api-best-practices-rest-endpoint-design-examples/
  • https://betterprogramming.pub/10-best-practices-for-naming-rest-api-endpoints-442ae592a3a0
  • https://cloud.google.com/apis/design

Conditional Querying with Spring Data JPA

검색 조건이 들어온 것만 쿼리에 반영하는 동적 쿼리를 Spring Data JPA에서 만드는 방법은 여러 가지다.

  • 메서드 이름 쿼리를 조건 조합마다 만드는 방법은 조건이 늘면 메서드가 폭발적으로 늘어난다.
  • Specification(JPA Criteria API 기반)으로 조건을 하나씩 정의하고 where(a).and(b)처럼 조합한다. 리포지토리가 JpaSpecificationExecutor를 상속해야 한다.
  • Query by Example은 조회 조건을 담은 예시 엔티티를 넘겨 null이 아닌 필드만 조건으로 쓴다. 간단하지만 범위 조건이나 OR 조합에는 약하다.
  • QueryDSL로 조건마다 BooleanExpression을 반환하고 값이 없으면 null을 넘긴다. 타입 안전하고 가독성이 좋아 실무에서 많이 쓴다.

  • https://medium.com/codex/how-to-easily-do-conditional-querying-with-spring-data-jpa-123079aa09a6

Graceful Shutdown

Graceful shutdown은 종료 신호를 받았을 때 새 요청은 받지 않고, 처리 중인 요청은 끝까지 마친 뒤 종료하는 것이다. 배포 중에 진행 중이던 요청이 끊겨 오류가 나는 것을 막는다. Spring Boot 2.3에서 도입되었고, 문서에 따르면 Jetty, Reactor Netty, Tomcat, Undertow 네 내장 서버와 서블릿, 리액티브 애플리케이션 모두에서 지원된다. Tomcat은 9.0.33 이상이 필요하다.

1
2
3
4
5
server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 20s

server.shutdown의 기본값은 immediate라 따로 켜야 하고, 대기 시간인 spring.lifecycle.timeout-per-shutdown-phase의 기본값은 30초다. 이 시간이 지나도 끝나지 않은 요청은 강제로 종료된다. 쿠버네티스에서는 파드 종료 유예 시간(terminationGracePeriodSeconds)이 이 값보다 길어야 의미가 있다.

  • https://www.baeldung.com/spring-boot-web-server-shutdown
  • https://bravenamme.github.io/2020/10/06/graceful-shutdown/
  • https://www.amitph.com/spring-boot-graceful-shutdown/#:~:text=During%20a%20graceful%20shutdown%20Spring,30%20seconds%20graceful%20shutdown%20timeout.
참고한 자료외부 출처 3

외부 출처

이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

댓글

아직 댓글이 없습니다