포스트

2022-11-09-TIL

2022-11-09-TIL

Today I Learned

GraphQL

GraphQL은 API를 위한 쿼리 언어이자 그 쿼리를 실행하는 런타임이다. 서버는 타입 시스템으로 스키마를 정의하고, 클라이언트는 하나의 엔드포인트에 필요한 필드만 골라 요청한다. 데이터 변경은 mutation, 실시간 구독은 subscription으로 표현하고, 서버에서는 필드마다 값을 가져오는 resolver가 실행된다(GraphQL: Learn).

1
2
3
4
5
6
query {
  user(id: "42") {
    name
    posts(last: 3) { title }
  }
}

REST와 비교하면 장단점이 분명하다.

  • 클라이언트가 필요한 필드만 받으므로 응답이 불필요하게 큰 over-fetching과, 화면 하나를 그리려고 여러 엔드포인트를 호출하는 under-fetching이 줄어든다.
  • 스키마가 곧 문서이고 introspection으로 클라이언트가 스키마를 조회할 수 있다. GitHub도 공개 API를 GraphQL로 제공한다.
  • 대신 모든 요청이 같은 엔드포인트로 가서 URL 단위 HTTP 캐시를 쓰기 어렵고, 중첩 필드마다 resolver가 실행되어 DB 쿼리가 N+1로 늘어나기 쉽다. 그래서 DataLoader 같은 배치 로딩과 쿼리 깊이·복잡도 제한이 함께 필요하다.

Java에서는 graphql-java가 기반 구현이다. Spring 진영에서는 graphql-java-kickstart의 Spring Boot 스타터가 쓰여 왔고, 공식 프로젝트인 Spring for GraphQL은 @Controller 안에 @QueryMapping, @SchemaMapping을 붙인 메서드를 스키마의 필드에 연결하는 방식이다(Baeldung의 Spring GraphQL 글 참고).

  • https://graphql.org/
  • https://graphql.org/code/#java-kotlin
  • https://github.com/graphql-java/graphql-java
  • https://www.graphql-java-kickstart.com/spring-boot/getting-started/
  • https://docs.github.com/en/graphql
  • https://www.howtographql.com/basics/1-graphql-is-the-better-rest/
  • https://tech.kakao.com/2019/08/01/graphql-basic/
  • https://www.redhat.com/en/topics/api/what-is-graphql
  • https://blog.logrocket.com/graphql-vs-grpc-vs-rest-choosing-right-api/
  • https://github.com/graphql-java-kickstart/graphql-spring-boot
  • https://velog.io/@oenomel87/Java%EB%A1%9C-GraphQL
  • https://developer.okta.com/blog/2020/01/31/java-graphql
  • https://www.baeldung.com/java-call-graphql-service
  • https://www.baeldung.com/spring-graphql

AWS On-Demand vs Reserved vs Spot

EC2 인스턴스는 구매 방식에 따라 비용과 조건이 다르다(Amazon EC2 billing and purchasing options).

  • On-Demand: 약정 없이 쓴 만큼(초 단위) 낸다. 가장 유연하고 가장 비싸다.
  • Reserved Instances: 인스턴스 유형과 리전 같은 구성을 1년 또는 3년 약정해서 할인받는다. Savings Plans는 구성 대신 시간당 사용 금액을 약정하는 방식이다.
  • Spot Instances: 쓰이지 않는 EC2 용량을 요청해 크게 할인받는다. 대신 AWS가 용량을 회수하면 인스턴스가 중단될 수 있다.
  • 이 밖에 물리 서버를 전용으로 쓰는 Dedicated Hosts·Instances, 특정 가용 영역에 용량을 확보하는 Capacity Reservations가 있다.

AWS 문서의 안내대로, 상시 켜져 있는 기본 용량은 Reserved나 Savings Plans로, 예측하기 어려운 증가분은 On-Demand로, 중단되어도 다시 시작할 수 있는 배치나 CI 작업은 Spot으로 나누는 것이 일반적인 조합이다.

  • https://blog.boltops.com/2018/07/13/on-demand-vs-reserved-vs-spot-aws-ec2-pricing-comparison/
  • https://www.incredibuild.com/blog/whats-the-difference-between-on-demand-reserved-and-spot-instances
  • https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-purchasing-options.html
  • https://www.virtana.com/blog/demystifying-terminology-aws-instances/

Filter Logging

서블릿 필터에서 요청·응답을 로깅하는 방법과 Filter, Interceptor, AOP 중 어디서 로깅할지는 8월 26일 TIL에 코드와 함께 정리했다. 링크들이 공통으로 다루는 문제는 요청 본문이다. HttpServletRequest의 입력 스트림은 한 번만 읽을 수 있어서, 필터에서 읽어 버리면 컨트롤러의 @RequestBody가 빈 본문을 받는다. 해결책은 두 가지다. HttpServletRequestWrapper를 상속해 본문을 바이트 배열로 미리 읽어 두고 getInputStream()이 그 배열로 새 스트림을 돌려주게 하거나, Spring의 ContentCachingRequestWrapper를 쓰는 것이다.

  • https://velog.io/@sixhustle/log
  • https://goddaehee.tistory.com/154
  • https://supawer0728.github.io/2018/04/04/spring-filter-interceptor/
  • https://frandorado.github.io/spring/2018/11/15/log-request-response-with-body-spring.html
  • https://amagrammer91.tistory.com/179
  • https://gist.github.com/michael-pratt/89eb8800be8ad47e79fe9edab8945c69
  • https://oops4u.tistory.com/2602
  • https://ok-vamos.tistory.com/m/79
  • https://veneas.tistory.com/entry/Spring-Boot-Filter-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-Request-Body-%ED%95%B8%EB%93%A4%EB%A7%81-HttpServletRequestWrapper

ContentCachingRequestWrapper

ContentCachingRequestWrapper는 요청의 입력 스트림과 reader에서 읽힌 내용을 캐시해 두었다가 getContentAsByteArray()로 꺼낼 수 있게 한다. Javadoc이 강조하는 점은 이 래퍼가 내용을 “읽히는 대로만” 캐시하고 스스로 읽지는 않는다는 것이다. 요청 본문이 소비되지 않았다면 캐시도 비어 있다. 그래서 필터에서는 chain.doFilter()로 컨트롤러가 본문을 읽게 한 뒤에 꺼내야 한다. 생성자의 cacheLimit으로 요청당 캐시할 최대 바이트를 정할 수 있고, 문서는 메모리 과다 사용을 막기 위해 구체적인 한도를 두라고 권한다. Spring의 AbstractRequestLoggingFilter도 이 래퍼를 쓴다(Javadoc).

  • https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/ContentCachingRequestWrapper.html

JMX and @ManagedResource

Spring에서는 빈을 JMX MBean으로 쉽게 노출할 수 있다. 클래스에 @ManagedResource를 붙여 JMX 서버에 등록할 대상임을 표시하고, 노출할 getter·setter에는 @ManagedAttribute, 원격에서 호출할 메서드에는 @ManagedOperation을 붙인다. 등록은 MBeanExporter가 하고, Java 설정에서는 @EnableMBeanExport로 켠다. objectName으로 MBean 이름을 지정하는데, 여러 하위 클래스가 상속하는 기반 클래스에는 이름 충돌을 피하기 위해 지정하지 말라고 Javadoc에 적혀 있다(ManagedResource Javadoc).

1
2
3
4
5
6
7
8
9
10
11
@Component
@ManagedResource(objectName = "app:name=FeatureToggle")
public class FeatureToggle {
    private volatile boolean newCheckout;

    @ManagedAttribute
    public boolean isNewCheckout() { return newCheckout; }

    @ManagedOperation
    public void enableNewCheckout() { this.newCheckout = true; }
}

이렇게 해 두면 재배포 없이 JConsole 같은 JMX 클라이언트에서 값을 보거나 바꿀 수 있다. JMX 일반은 11월 5일 TIL에 정리했다.

  • https://blog.outsider.ne.kr/1026
  • https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jmx/export/annotation/ManagedResource.html

Liveness and Readiness

Kubernetes는 두 종류의 프로브로 컨테이너 상태를 판단한다. liveness 프로브가 실패하면 컨테이너를 재시작하고, readiness 프로브가 실패하면 재시작하지 않고 서비스의 트래픽 대상에서만 뺀다. 둘을 구분하지 않고 DB 연결 같은 외부 의존성 검사를 liveness에 넣으면, DB 장애 때 모든 파드가 재시작을 반복하게 된다.

Spring Boot는 애플리케이션 가용성 상태를 LivenessState(CORRECT, BROKEN)와 ReadinessState(ACCEPTING_TRAFFIC, REFUSING_TRAFFIC)로 관리하고, Actuator가 이를 /actuator/health/liveness, /actuator/health/readiness로 노출한다. 이 그룹은 Kubernetes 환경에서 자동으로 켜지고, 다른 환경에서는 management.endpoint.health.probes.enabled=true로 켠다. 기동이 끝나도 준비가 완료되기 전까지는 트래픽을 거부하고, graceful shutdown이 시작되면 readiness를 REFUSING_TRAFFIC으로 바꿔 새 요청을 받지 않는다(Spring Boot: Kubernetes Probes).

  • https://www.baeldung.com/spring-liveness-readiness-probes
  • https://cloud.ibm.com/docs/java?topic=java-spring-healthcheck&locale=ko

JsonView.render()

링크의 글은 컨트롤러 안에서 JSP 뷰를 HTML 문자열로 렌더링한 뒤, 그 문자열을 인코딩해 JSON 뷰(jsonView)의 필드로 담아 반환하는 방법을 다룬다. Spring MVC의 View.render(model, request, response)는 결과를 응답 스트림에 직접 쓰기 때문에, 문자열로 받으려면 HttpServletResponseWrapper로 응답의 writer를 가로채 버퍼에 쓰게 만들어야 한다. 서버에서 그린 HTML 조각을 AJAX 응답 JSON에 넣어 보내야 할 때 쓰는 방식이다.

  • https://m.cafe.daum.net/hilsoft/KmdH/27
참고한 자료외부 출처 5

외부 출처

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

댓글

아직 댓글이 없습니다