728x90

실무환경에서 DataBase 성능 최적화 및 ORM에 대해 이야기 할때 커넥션풀이라는 단어가 자주 등장하여 한번 정리가 필요할 것 같아 정리한 포스트 입니다.

🍌 JDBC란?

Hikari CP(히카리 커넥션풀)을 알아보기에 앞서 JDBC의 개념을 정리하자면,
JDBC는 Java Database Connectivity의 약자로 자바에서 데이터베이스에 접속할 수 있도록 하는 자바 API다.

JDBC는 데이터베이스에서 자료를 쿼리하거나 업데이트하는 방법을 제공한다.


🍑 DB 커넥션 풀이란

일반적인 데이터 연동과정은 웹 어플리케이션이 필요할 때마다 데이터베이스에 연결하여 작업하는 방식입니다.
하지만 이런 식으로 필요할 때마다 연동해서 작업할 경우 데이터베이스 연결에 시간이 많이 걸리는 문제가 발생합니다.

예를들어 거래소의 경우, 동시에 몇천명이 동시에 거래 및 조회 기능을 사용하는데 매번 데이터베이스와 커넥션을 맺고 푸는 작업을 한다면 굉장히 비효율적일 것입니다.

이 문제를 해결하기 위해 현재는 웹 어플리케이션이 실행됨과 동시에 연동할 데이터베이스와의 연결을 미리 설정해 둡니다.

그리고 필요할 때마다 미리 연결해 놓은 상태를 이용해 빠르게 데이터베이스와 연동하여 작업을 합니다.

이렇게 미리 데이터베이스와 연결시킨 상태를 유지하는 기술을 커넥션 풀
(Connection Pool, CP)라고 합니다.


🍊 스프링에서의 커넥션 풀

자바에서는 기본적으로 DataSource 인터페이스를 사용하여 커넥션풀을 관리한다.

Spring에서는 사용자가 직접 커넥션을 관리할 필요없이 자동화된 기법들을 제공하는데

SpringBoot 2.0 이전에는 tomcat-jdbc를 사용하다,
현재 2.0이후 부터는 HikariCP를 기본옵션으로 채택 하고있다.


🍓 왜 Hikari Cp일까?

히카리 벤치마킹 페이지를 참고하면 아래와 같이 월등한 성능을 보인다는 것을 알 수있다.


HikariCp가 다른 커넥션풀 관리 프레임워크보다 빠른 성능을 보여주는 이유는
커넥션풀의 관리 방법에 있다.

히카리는 Connection 객체를 한번 Wrappring한 PoolEntry로 Connection을 관리하며,
이를 관리하는 ConcurrentBag이라는 구조체를 사용하고 있다.

ConcurrentBag은 HikariPool.getConnection() -> ConcurrentBag.borrow()라는 메서드를 통해 사용 가능한(idle) Connection을 리턴하도록 되어있다.

이 과정에서 커넥션생성을 요청한 스레드의 정보를 저장해두고 다음에 접근시 저장된 정보를 이용해 빠르게 반환을 해준다.

이러한 방법 때문에 속도에 이점이 있으며 해당 방법의 자세한 설명은 아래 블로그를 참조하면 좋을 것 같다.
HikariCP Dead lock에서 벗어나기 (이론편)


🍎 Hikari CP 사용법

build.gradle에 따로 추가할 필요 없이
"org.springframework.boot:spring-boot-starter-jdbc"
를 추가하면 자동으로 추가된다.

이후 application.yml에 설정값을 추가하면 되는데

spring:
 datasource:
   url: jdbc:mysql://localhost:3306/world?serverTimeZone=UTC&CharacterEncoding=UTF-8
   username: root
   password: your_password
   hikari:
     maximum-pool-size: 10
     connection-timeout: 5000
     connection-init-sql: SELECT 1
     validation-timeout: 2000
     minimum-idle: 10
     idle-timeout: 600000
     max-lifetime: 1800000

server:
 port: 8000

options

  • maximum-pool-size: 최대 pool size (defailt 10)
  • connection-timeout: (말 그대로)
  • connection-init-sql: SELECT 1
  • validation-timeout: 2000
  • minimum-idle: 연결 풀에서 HikariCP가 유지 관리하는 최소 유휴 연결 수
  • idle-timeout: 연결을위한 최대 유휴 시간
  • max-lifetime: 닫힌 후 pool 에있는 connection의 최대 수명 (ms)입니다.
  • auto-commit: auto commit 여부 (default true)

🍋 DeadLock 피하기

이론적으로 필요한 최소한의 커넥션 풀 사이즈를 알아보면 다음과 같다.

PoolSize = Tn × ( Cm -1 ) + 1

  • Tn : 전체 Thread 갯수
  • Cm : 하나의 Task에서 동시에 필요한 Connection 수

위와 같은 식으로 설정을 한다면 데드락을 피할 수는 있겠지만 여유 커넥션풀이 하나 뿐이라 성능상 좋지 못하다.
따라서 커넥션풀의 여유를 주기위해 아래와 같은 식을 사용하는것을 권장한다.

PoolSize = Tn × ( Cm - 1 ) + ( Tn / 2 )

  • thread count : 16
  • simultaneous connection count : 2
  • pool size : 16 * ( 2 – 1 ) + (16 / 2) = 24

더 자세히 알아보고 싶으면 다음 블로그에서 확인하면 좋을듯 하다.
HikariCP Dead lock에서 벗어나기 (실전편)

 

https://velog.io/@miot2j/Spring-DB%EC%BB%A4%EB%84%A5%EC%85%98%ED%92%80%EA%B3%BC-Hikari-CP-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0

 

[Spring] DB커넥션풀과 Hikari CP 알아보기

실무환경에서 DataBase 성능 최적화 및 ORM에 대해 이야기 할때 커넥션풀이라는 단어가 자주 등장하여 한번 정리가 필요할 것 같아 정리한 포스트 입니다.

velog.io

 

728x90

이 부분해서 Slack 에서 대화생성하기 이후에 던져주는 부분을 활용하려면 아래와 같이 Slack 에서 Oauth Permission 을 허용시켜주어야 한다.

 

conversations.create

 
 

Initiates a public or private channel-based conversation

https://api.slack.com/methods/conversations.create

 

conversations.create API method

Initiates a public or private channel-based conversation

api.slack.com

 

Bot token 에 대해서 제대로 설정해주고, 해당 부분에 대한 코드를 작성하면 된다.

(물론 샘플 코드도 있더라)

 

import com.slack.api.bolt.App;
import com.slack.api.bolt.AppConfig;
import com.slack.api.bolt.jetty.SlackAppServer;
import com.slack.api.methods.SlackApiException;

import java.io.IOException;

public class ConversationsCreate {

    public static void main(String[] args) throws Exception {
        var config = new AppConfig();
        config.setSingleTeamBotToken(System.getenv("SLACK_BOT_TOKEN"));
        config.setSigningSecret(System.getenv("SLACK_SIGNING_SECRET"));
        var app = new App(config); // `new App()` does the same

        // Listen for a message shortcut with the callback_id "sample_message_action"
        // Message shortcuts require the commands scope
        app.messageShortcut("sample_message_action", (req, ctx) -> {
            var logger = ctx.logger;
            try {
                var payload = req.getPayload();
                // Call the conversations.create method using the built-in WebClient
                var result = ctx.client().conversationsCreate(r -> r
                    // The token you used to initialize your app
                    .token(System.getenv("SLACK_BOT_TOKEN"))
                    // The name of the conversation
                    .name("pretend-channel")
                );
                // Print result
                logger.info("result: {}", result);
            } catch (IOException | SlackApiException e) {
                logger.error("error: {}", e.getMessage(), e);
            }
            // Acknowledge incoming command event
            return ctx.ack();
        });

        var server = new SlackAppServer(app);
        server.start();
    }

}

 

하지만 여기서 단순하게 이렇게 설정하게 만들어주면 망하는 결과가 ... (난 Back-end API 에서 중계단을 만들거니까 이렇게 만들면 안되고 Service 부터 DTO 등까지 죄다 세세하게 분배해주었다.)

728x90
2023-08-11 04:14:46.858  3948-4126  NaverOAuth...Connection com.atlas.moyamo                     I  response status code:200
2023-08-11 04:14:46.927  3948-3948  System.out              com.atlas.moyamo                     I  accessToken is AAAAN5Z6D5+Ji+VL6Ef577RT9H6S9d1X0fHf6QJx6PpcduBu48urbqxg3x0vz6aaO4tEoc8O4fnbD6lsEDs/wnjcOzI=
2023-08-11 04:14:46.927  3948-3948  System.out              com.atlas.moyamo                     I  refreshToken is TlejyY5Q9cbuZTooIxFrn41qvEoLipBKT1g8JiiUUis01C9pngEkWsgoMisjoiiYSeisMlJisE32UE2cQeL18cQ2jKtxip53JmN9Pq4zHuJl8pKxQWf0GOz6UVyPca7Q8xq3yIar
2023-08-11 04:14:46.927  3948-3948  System.out              com.atlas.moyamo                     I  expiresAt is AAAAN5Z6D5+Ji+VL6Ef577RT9H6S9d1X0fHf6QJx6PpcduBu48urbqxg3x0vz6aaO4tEoc8O4fnbD6lsEDs/wnjcOzI=
2023-08-11 04:14:46.927  3948-3948  System.out              com.atlas.moyamo                     I  tokenType is bearer

Logcat 에서 주어지는 accessToken 은 굉장히 긴 반면에 spring 단에서 인식되는 accessToken 은 굉장히 짧은 부분을 보이고 있다.

 

아마 세션간의 문제라면 이 부분에서 spring 이 가져다 쓰는 부분에 문제가 생기거나 뭔가 착오가 생기는 모양인데, 추적 포인트를 바꿔야 할 것 같다

 

2023-08-11 04:14:47.412  3948-3948  System.out              com.atlas.moyamo                     I  main setup userId is 1799455,  user accessToken is 87e96dfbfe8d4d7d98d186720b6be80e, provider is naver
2023-08-11 04:14:47.440  3948-4954  System.out              com.atlas.moyamo                     I  okhttp ResponseInterceptor pre synchronized - http://v2/users/me
2023-08-11 04:14:47.440  3948-4954  System.out              com.atlas.moyamo                     I  ResponseInterceptor isExpiredTime 2023-09-09 19:14:47 - 1 - http://v2/users/me
2023-08-11 04:14:47.443  3948-4954  System.out              com.atlas.moyamo                     I  call bearer token Bearer 87e96dfbfe8d4d7d98d186720b6be80e

여기서 인식되는건 87 기반의 id 인데 위에는 왜 저렇게 되어있는걸까

 


추가적인 증상 발견

naver, OAuth 기반의 Id 에서 로그인이 해제되어 있을 때 shop에서 프로필 조회 end-point 를 불러오면 이후 로그인이 되는 현상이 발견되었다.

 

{{HOST}}/v2/shops/users/me
728x90

Shop 상에서 Login 이 지속적으로 풀리는 현상이 발생

해당 부분에서 내 Android 기기로 접근했을 때의 Bearer 를 땃고, 이걸 기반으로 accessToken 값으로 활용해서 End-Point에 던져보기로 했다

 

맨 처음에는 유효하지 않은 토큰이라고 resultMsg 가 return 되길래 나자신 뭘잘못했는가 생각하다가, end-point 주소가 잘못됬었다는 것을 기억하고 때려박으니 던져서 납치해오는 방법에 대해서 문제가 생긴것으로 생각된다.

 

Android Side 에서 request  -> Spring 에서 hook -> Shop 의 API 단에 때려박고 -> Shop 내부의 DB 에 접근하는 것으로 보이는데, 신기하게도 Spring 서버를 재시작 시키면 4~6시간은 정상적으로 작동되는 것으로 보아 뭔가가 있다고 판단함

+ Recent posts