728x90

현재 작업중인 리소스 리팩토링 하는 과정에서 monggoDB 트랜잭션이 작동하지 않는다는 사실을 전달받았다.

이에 실제로 지원을 하지 않는 것인지 추가로 지원이 되는지 부터 확인했다.

아래는 참조 문서이다.

 

https://www.baeldung.com/spring-data-mongodb-transactions

 

문서를 보니 spring boot transaction 이 적용될려면 몇가지 조건이 필요하다.

 

  1. MongoDB 버전이 4.0 이상
  2. 레플리카 셋 적용

 

회사 몽고디비를 확인하니 4.0 버전이 설치가 되어 있지만 레플리카 셋이 적용 되지 않았다.

직접 작업에 참여한것도 아니고 이전에 일하는 사람이 하고 퇴사했다는 애기만 들었다.

회사에서는 AWS documentDB를 이용하기 떄문에 아마 비용문제로 하나만 했을거로 예상이 된다.

그래도 권장 사양이 있는데 좀 의아하긴하다.

 

일단 몽고디비의 레플리카 셋구조를 보면

 

 

첫번째는 하나의 primary DB에 2개의 복제본인 secondary DB 가 붙는 구조

 

 

 

두번 째는 하나의 secondary에 arbiter 가 붙는 구조

 

 

 

  1. primary - 단 하나만 존재 가능하며 실제 클라이언트와 정보를 주고 받음.
  2. secondary - 장애시 프라이머리로 전환이 가능하며 클라이언트 일기 작업 분담 가능 (slave db 처럼)
  3. arbiter - 레플리카셋을 3대 이상의 홀수로 구성할 수 없을시, 투표권만을 가지고 레플리카 셋을 모니터링 역할.

개념적으로 이해 했을때는 위와같이 복제 디비가 존재해야지만 트랜잭션 롤백이 가능한것 으로 보인다.

실제 설치형 DB 의 경우 추가 설정을 할게 추가로 존재하지만 AWS document DB 의 경우 추가로 생성만 해주면 된다.

아래는 실제 적용 코드이다.

 

 

@Configuration
@EnableTransactionManagement
@EnableMongoRepositories(basePackages = "kr.howser.partner.repository.mongo")
public class MongoConfig extends AbstractMongoClientConfiguration {

    @Value("${spring.data.mongodb.uri}")
    private String uri;

    @Value("${spring.data.mongodb.data-base}")
    private String dataBaseName;

    @Override
    @NonNull
    protected String getDatabaseName() {
        return this.dataBaseName;
    }

    @Override
    @NonNull
    public MongoClient mongoClient() {
        final ConnectionString connectionString = new ConnectionString(this.uri);
        final MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
                .applyConnectionString(connectionString)
                .build();
        return MongoClients.create(mongoClientSettings);
    }

    @Primary
    @Bean(name = "mongoTransaction")
    public MongoTransactionManager transactionManager(MongoDatabaseFactory dbFactory) {
        return new MongoTransactionManager(dbFactory);
    }

    @Primary
    @Bean
    public MongoTemplate mongoTemplate() {
        MongoDatabaseFactory mongoDbFactory = new SimpleMongoClientDatabaseFactory(mongoClient(), this.dataBaseName);
        DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);
        MongoMappingContext mongoMappingContext = new MongoMappingContext();
        mongoMappingContext.setFieldNamingStrategy(new SnakeCaseFieldNamingStrategy());
        MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
        converter.setTypeMapper(new DefaultMongoTypeMapper(null));//_class delete
        return new MongoTemplate(mongoDbFactory, converter);
    }

}

 

 

위와같이 트랜잭션을 걸어주면 된다. 트랜잭션 빈 설정 오류시는 이름을 꼭 추가로 해주어야한다.

MongoDatabaseFactory mongoDbFactory = new SimpleMongoClientDatabaseFactory(mongoClient(), this.dataBaseName);
        DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);
        MongoMappingContext mongoMappingContext = new MongoMappingContext();
        mongoMappingContext.setFieldNamingStrategy(new SnakeCaseFieldNamingStrategy());
        MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
        converter.setTypeMapper(new DefaultMongoTypeMapper(null));//_class delete

 

 

 

이 부분의 경우 _class 를 지워줄때 사용한다. 필요한 부분이라 굳이 지워나 싶기는 하나 일단은 삭제 요청을 받아서 삭제가 되도록 ..

이번에 작업을 하면서 느끼지만 추가로 작업을 한다면 이론적인 부분 부터 꼭 하는 습관을 가지도록 더 정진해야겠다..

728x90
728x90

 

현재 재직중인 회사에서는 레거시 프로젝트의 기술부채가 심각하여 로그를 쌓고 있지만 해당 로그를 잘 활용하여 찾기는 어렵게되어 있다.

 

이에 내가 주로 작업하는 파트너 리소스에 traceId 를 생성하여 빠르게 로그를 추적하고자 한다.

 

이전에 다녔던 회사중 한곳은 쿠버네티스 환경의 모든 리소스의 로그가 트레이스 단위로 추적이 가능한 환경이 였는데 현재 회사에서는 기술부채로 인하여 새로운 시스템 도입이 어려운 상황이다.

이에 현재 내가 담당하고 주로 작업을 진행하는 파트너리소스에 접근하는 호출들에 대하여 요청 아이디를 생성하기로 했다. 내가 담당하는 부분말고도 다른부분도 모두 적용하여 trace를 생성하면 좋겠지만 일단 해당 리소스에만 작업하기로 한다.

일단 내가 원하는이상적인 방법은 spring cloud sleuth와 zipkin을 통하여 흩어 져 있는 리소스를 모두 취합하고

trace추적 및 span 단위 작업 등을 확인하는 이상적인 구조..

아래는 2가지 기능들의 참조 사이트 이다.

 

https://docs.spring.io/spring-cloud-sleuth/docs/2.2.x-SNAPSHOT/reference/html/

 

Spring Cloud Sleuth

Spring Cloud Sleuth automatically instruments all your Spring applications, so you should not have to do anything to activate it. The instrumentation is added by using a variety of technologies according to the stack that is available. For example, for a s

docs.spring.io

 

https://zipkin.io/

 

OpenZipkin · A distributed tracing system

Zipkin Zipkin is a distributed tracing system. It helps gather timing data needed to troubleshoot latency problems in service architectures. Features include both the collection and lookup of this data. If you have a trace ID in a log file, you can jump di

zipkin.io

 

일단 급한대로 다른 방법이 없는지 찾아보니 MDC((Mapped Diagnostic Contex) 라는 게 있어 이를 활용하기로 했다.

 

 

MDC는 로깅 프레임워크에서 사용되는 기능이며 현재 실행중인 컨텍스트(혹은 쓰레드)에 메타 정보를 넣고 관리할 수 있는 공간입니다.

각각의 로그 메시지에 대한 컨텍스트 정보를 추가하여 제공하고, 로그 메시지를 보다 구체적이고 유용하게 만들어줍니다.

내부적으로 쓰레드 로컬을 사용하며 Map 형태로 되어있어, Key Value 형태로 값을 보관하고 꺼낼 수 있어 관리에 용이합니다.

  • 즉 스레드 단위로 관리되는 Map을 사용합니다.

MDC를 사용하면 로깅 프레임워크에서 제공하는 로그 출력 형식에 특정 데이터를 쉽게 추가할 수 있습니다.

주로 다음과 같은 정보를 MDC에 설정하여 사용합니다:

  • 요청 ID: 각각의 웹 요청이나 작업에 대해 고유한 식별자를 설정
  • 세션 ID: 사용자 세션을 식별하기 위한 정보를 설정
  • 사용자 정보: 현재 사용자의 ID, 이름 등과 같은 정보를 설정
  • 트랜잭션 ID: 트랜잭션 처리에 대한 고유한 식별자를 설정
  • 로깅 수준: 특정 로그 메시지에 대한 로깅 수준을 설정

MDC는 주로 다중 스레드 환경에서 스레드 로컬 (ThreadLocal) 변수를 사용하여 각각의 스레드에 대해 독립적인 MDC 값을 설정하고 사용할 수 있습니다. 이를 통해 멀티스레드 환경에서도 로그 메시지에 대한 정확한 컨텍스트 정보를 유지할 수 있습니다.

예를 들어, 웹 애플리케이션에서 각각의 HTTP 요청에 대해 고유한 요청 ID를 MDC에 설정하면, 해당 요청과 관련된 모든 로그 메시지에 자동으로 요청 ID가 포함됩니다. 이를 통해 로그 메시지를 필터링하거나 추적하는 등의 작업을 보다 쉽게 수행할 수 있습니다.

MDC는 Java에서는 로깅 프레임워크인 SLF4J (Simple Logging Facade for Java)와 함께 사용됩니다.

 

 

실제 적용 코드

 

일단 요청이 들어오는 시점에 최초로 MDC 요청아이디와 스레드 아이디를 달아주고자 doFilter를 활용했다.

 

 

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MDCRequestFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        final UUID uuid = UUID.randomUUID();
        MDC.put("request_id", uuid.toString());
        MDC.put("thread_id", Thread.currentThread().getName());
        chain.doFilter(request, response);
        MDC.clear();

    }
}

 

 

logback 에서는 추가된 요청 아이디와 스레드 아이드를 추가해준다.

 

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
		<layout class="ch.qos.logback.classic.PatternLayout">
			<pattern>[%d{yyyy.MM.dd HH:mm:ss.SSS}] - [%-5level] - [%X{request_id}] - [%logger{5}] - [%X{thread_id}] [%method:%line] - %msg%n</pattern>
		</layout>
	</appender>

 

 

실제 생성된 로그

 

 

 

생각보다 간단하게 적용 되었다. 현재 내용에는 빠져 있지만 completeFuture 비동기 처리 시 요청 아이디를 찾지 못하여 해당 값은 추가 처리를 통하여 처리하였다.

 

추가로 멀티 쓰레드 처리 시 추가 설정및 확인이 필요하다.

728x90

'코드 > dev' 카테고리의 다른 글

spring Rest Docs  (0) 2024.03.03
Spring boot MongoDB transaction  (0) 2024.02.28
NoHttpResponseException 해결 방법  (3) 2024.02.26
Hikari pool맥스 사이즈 문제  (1) 2024.02.26
form 요청 PATCH , PUT, DELETE는 불가능한 이유  (2) 2024.02.26
728x90

외부 연동 리소스 작업중 간헐적으로 443 NoHttpResponseException 에러가 떨어졌다.

타겟 서버에서 유효하지 않은 HTTP 응답으로 제대로 응답하기를 실패했다는 신호라고 나오는데

더 알아보면 HTTP 1.1 / KEEP -ALIVE가 보장될떄 나올 수 있는 오류이다.

즉 , 커넥션이 유지될떄 호출이 되면 해당 타겟 서버에서는 이미 응답을 줬다는 의미로 해당 exception 이 떨어진다.

최초에는 커넥션 을 빠르게 끊고 다시 맺는 옵션을 추가하였습니다.

 

 

final int INVALID_CONNECTION_CLOSE_TIME = 5 * 1000;
        manager.setValidateAfterInactivity(INVALID_CONNECTION_CLOSE_TIME);
//위 옵션을 restTemplate bean 생성이 추가

 

 

위와 같은 조치를 했지만 여전히 발생을 했습니다.

여러 레퍼런스와 스택오버플로우를 보니 위와 같은 문제 발생시 자제 재 호출하는 방법 밖에 없다는 결론에 도달했습니다.

 

 

 private static HttpRequestRetryHandler retryHandler() {
        return (exception, executionCount, context) -> {
            if (executionCount > 10) {//5
                return false;
            }

            HttpClientContext clientContext = HttpClientContext.adapt(context);
            HttpRequest request = clientContext.getRequest();

            if (exception instanceof java.net.SocketException && exception.getMessage()
                    .equals("Connection reset") || exception instanceof NoHttpResponseException) {
                log.info(
                        "RetryHandler Connection reset executionCount - {}, totalCount - {}, request - {}",
                        executionCount, 10,
                        request);

                // 2번 요청 이후부터는 2초 후 재요청
                if (executionCount >= 2) {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        log.warn("Interrupted {} ", e.getMessage(), e);
//                        LOGGER.log(Level.WARN, "Interrupted!", e);
                        /* Clean up whatever needs to be handled before interrupting  */
                        Thread.currentThread().interrupt();
                    }
                }
                return true;
            }

            return false;
        };
    }

 

 

위와 같이 retryHandler에 NoHttpResponseException 재호출 되도록 설정 했습니다.

설정 이후에는 해당 오류는 발생하지 않았습니다.

 

참고 레퍼런스

https://stackoverflow.com/questions/26111331/org-apache-http-nohttpresponseexception-xx-xx-xx-xx443-failed-to-respond

 

org.apache.http.NoHttpResponseException: XX.XX.XX.XX:443 failed to respond

Currently I am using Apache http components client V4.3.5. In my case, I can upload small file(1kb), but it is not working on large file(100kb) when I run the code and get the exception "org.apache...

stackoverflow.com

 

https://hc.apache.org/httpclient-legacy/exception-handling.html

728x90

'코드 > dev' 카테고리의 다른 글

Spring boot MongoDB transaction  (0) 2024.02.28
traceId 로그 생성  (0) 2024.02.28
Hikari pool맥스 사이즈 문제  (1) 2024.02.26
form 요청 PATCH , PUT, DELETE는 불가능한 이유  (2) 2024.02.26
Swagger UI  (2) 2023.11.27
728x90

API 구현시 적은 트래픽에서는 문제가 없지만 트래픽이 늘어나면 hikari pool connection timeout 발생으로 요청을 처리하지 못하는 문제를 겪은 적이 있다.

모니터링을 하다보면 hikariPool 에서 connection을 얻을 수 없다고 한다.

이런 경우 나를 포함해서 통상 이렇게 해결을 했을 것이다.

  • pool 사이즈를 늘려서 해결

사이즈를 늘려서 해결 할 수 있지만 이게 과연 정답인지에 대하여 생각을 해볼 필요가 있다.

Deadlock 발생으로 인하여 생기는 문제

트래픽이 몰리는 상황에서는 Threand간 커넥션을 차지 하기위해서 레이스 커넥션이 발생

한 트랜잭션에 2개의 커넥션이? 필요

우리가 생각할때 하나의 트랜잭션당 하나의 풀만 있으면 될것 같지만 그렇지 않기떄문에 풀사이즈를 일단 늘리면 해결이 되지만 정확한 숫자는 하단의 공식을 참조하는게 좋다.

 

 

 

우리가 생각할때 하나의 트랜잭션당 하나의 풀만 있으면 될것 같지만 그렇지 않기떄문에 풀사이즈를 일단 늘리면 해결이 되지만 정확한 숫자는 하단의 공식을 참조하는게 좋다

 

 

같이 일을 했었던 분이 알려준 참고하라는 공식


우형에서 사용하는 hikaricp 풀사이즈 공식
Tn : cpu 쓰레드 갯수
cm : 하나의 task에서 동시에 필요한 커넥션 갯수

pool size = Tm * (Cm-1) + (Tn/2)

히카리에서 제안하는 계산공식
Tm * (Cm-1) + 1

 

 

일을 하고 문제를 확인하고 해결을 할떄마다 항상 새로운걸 배우고 공부하게되는데 이런걸 먼저 확인하고 해결하는사람들은 참 존경스럽다..

 

참고했던 포스팅 

https://techblog.woowahan.com/2664/

 

HikariCP Dead lock에서 벗어나기 (이론편) | 우아한형제들 기술블로그

{{item.name}} 안녕하세요! 공통시스템개발팀에서 메세지 플랫폼 개발을 하고 있는 이재훈입니다. 메세지 플랫폼 운영 장애를 바탕으로 HikariCP에서 Dead lock이 발생할 수 있는 case와 Dead lock을 회피할

techblog.woowahan.com

 

https://soongjamm.tistory.com/156

 

(성능테스트) Hikari pool connection 데드락 해결

안녕하세요. 최근 진행중인 프로젝트에서 최근 주문 API를 구현하고, JMeter를 이용해 부하 테스트를 진행한 적이 있습니다. 적은 트래픽에서는 문제가 없었는데, 부하를 일으키니 Hikari pool connectio

soongjamm.tistory.com

 

728x90

'코드 > dev' 카테고리의 다른 글

traceId 로그 생성  (0) 2024.02.28
NoHttpResponseException 해결 방법  (3) 2024.02.26
form 요청 PATCH , PUT, DELETE는 불가능한 이유  (2) 2024.02.26
Swagger UI  (2) 2023.11.27
슬로우 쿼리 문제 (mariadb boolean column index)  (0) 2023.11.24
728x90

재직중인 회사에서 팡리이 포함된 기능이 있는 엔드포인트를 새로 추가 및 수정을 진행하게 되었다.

 

 

이때 수정 시 PUT이나 PATCH를 사용할때 폼 요청이 지원되지 않는 부분을 정리 할겸 작성을 했다.

불가능한 이유는 일단 지원을 하지 않기 떄문인데 지원을 하지 않는 이유에 대해서는 깊게 알아보기는 어려울 것 같다.

통상 작업을 하게되면 조회(GET), 등록(POST), 수정(PUT OR PATCH), 삭제는(DELETE)를 사용하느데 이미지와 같이 엮어서 사용하게될때 등록할떄는 상관 없지만 수정시에는 폼데이터를 사용할 수 없다.

이번에 요청 받은 작업을 진행 할때 폼데이터를 리스트로 받아 처리를하게 되어서 어쩔 수 없이 수정도 Post를 통해서 작업을 했는데 이게 꼭 이렇게만 처리를 해야하는지 에 대해서는 고민이 든다.

구글링 및 참고 블로그 포스팅을 보면 GET / POST 만 가능한 이유는 다음과 같다.

 

폼 요청이 GET / POST 만 가능한 이유..

 

/**
하단 내용은 다른 게시글을 참고하여 작성하였다. 
HTML5.초안에 나왔던 사안이지만 Ian에 의해 무산되버리고 이는 스레드로 나와 있다.
https://www.w3.org/Bugs/Public/show_bug.cgi?id=10671#c0

이 논쟁이후 추가 드래프트도 나온다 
(https://www.w3.org/TR/2015/NOTE-form-http-extensions-20150929/#http-put-form)


Delete는 없어도 되지만, Put의 경우 payload를 통해 헤더를 구성하는 방식을 제안하기 때문이라고 한다.

또한 Form은 서버에 정보를 제출하기 위해 존재하는데 Delete와 Put은 아니다. (Get같은 경우는 URL에 폼 데이터를 추가해 서버로 전달하는방식이다.)

Post 와 Put은 전달해주는 payload를 처리하는 요청 대상 리소스가 주체가 되냐, 아니면 대체가되냐의 차이가 크기때문에 요청대상 주체가 될수 없는 Put은 쓰일수 없다는 이유도 있다 한다.

즉 Get은 form 정보를 줄게 리소르를 줘!!
post는 form정보를 처리해줘!
Delete는 URI는 리소르를 정확하게 식별하고 요청하기 때문에 form을 통해 값을 전달할게 없고 (header로 토큰 등등정도)
Put은 대상 리소르를 대체하는 payload를 보내라는 것인데,

Form의 정의를 보면 The

HTML element represents a document section containing interactive controls for submitting
interactive control 형태의 라는 것이 있다.
즉 뭔가를 요청하기 위해 필요한데이터를 보내는 것인데
이 관점에서 리소스 요청 대상 그 자체가 Put으로 담기에는 의미를 위반한다는 관점이라 한다. (확실히 form자체의 뜻을 보니 이해가 더 간다)

**/

 

 

 

728x90

'코드 > dev' 카테고리의 다른 글

NoHttpResponseException 해결 방법  (3) 2024.02.26
Hikari pool맥스 사이즈 문제  (1) 2024.02.26
Swagger UI  (2) 2023.11.27
슬로우 쿼리 문제 (mariadb boolean column index)  (0) 2023.11.24
ec2 네트워크 연결 되지 않는 오류  (2) 2023.11.22
728x90

신규 API를 작성하여 배포할 일이 생겨 복습겸 스웨거 어노테이션을 정리하고자 한다.

@ApiOperation(
        value = "유저 정보 조회"
        , notes = "유저의 ID 를 통해 해당 아이디의 상세 정보 조회 한다.")
@ApiImplicitParams(
        {
            @ApiImplicitParam(
                name = "id"
                , value = "자격증 아이디"
                , required = true
                , dataType = "string"
                , paramType = "path"
                , defaultValue = "None"
            )
        ,
            @ApiImplicitParam(
                name = "fields"
                , value = "응답 필드 종류"
                , required = false
                , dataType = "string"
                , paramType = "query"
                , defaultValue = ""
            )
        })
		@ApiResponses({
        @ApiResponse(code = 200, message = "성공입니다.")
        , @ApiResponse(code = 400, message = "접근이 올바르지 않습니다.")
    })
    @GetMapping("/user/{id}")
    public  ResponseEntity<UserResponse> getUser(@PathVariable(name = "id") String id) {
        return userService.findUserInfoById(id);
    }

 

@ApiOperation = Method 설명

  • Controlloer 안에 method 의 설명을 추가할 수 있다.

 

@ApiImplicitParam = RequestParaemter 설명

  • 해당 API Method 호출에 필요한 Paraemter들의 설명르 추가할 수 있다.
  • ApiImplicitParams을 통해 복수개의 정보를 사용 할 수 있다.

 

@ApiResponse = Response 설명

  • 해당method의 Response에 대한 설명을 작성할 수 있다.
  • 복수개의 Response 에 대한 설명을 추가하고 싶다면, ApiResponse를 통해 사용 가능하다.

 

 

스웨거의 기본 Response message 삭제방법

 

  • 위와 같이 기본 response 가 노출이 되는데 이를 제거 하기 위헤서는 Swagger config에 Docket에 useDefaultResponseMessages(false)를 설정해주면 된다. (설정 시 401, 403, 404 응답이 사라진다.)
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class UserRequest {
    
    @ApiModelProperty(
        name = "id"
        , example = "gillog"
    )
    @ApiParam(value = "사용자 ID", required = true)
    private String id;

		@ApiModelProperty(example = "teter문")
    @ApiParam(value = "사용자 이름")
    private String name;

    @ApiParam(value = "token 갱신 값")
    private String tokenRefresh;
}

 

@ApiModelProperty = DTO 예제 설명

  • ApiModelProperty를 DTO Class Field에 추가하면, 해당 DTO Field의 예제 Data를 추가할 수 있다.
  • property의 name은 생략할 수 있다.

@ApiParam = DTO field 설명

  • DTO field에 대한 설명을 추가할 수 있다.
  • Controlloer PathVarialbe 기타 파라메터에 대한 설명도 추가 가능
    @ApiIgonre
		@GetMapping("/user/{id}")
    public  ResponseEntity<UserResponse> getUser(@PathVariable(name = "id") String id) {
        return userService.findUserInfoById(id);
    }

 

@ApiIgnore Swagger UI 표기제외

  • Controller 나 메소드를 스웨거 상 비노출 처리 할 수 있다.
  • ApiOperation 옵션 hidden을 true로 만들어서 엔드포인트를 숨길 수 있다.
728x90

+ Recent posts