티스토리 뷰

JVM/Spring

[Hikari CP] 光 살펴보기 - 2

글을 쓰는 개발자 2022. 4. 27. 08:04
반응형

HikariDataSource

getConnection()

isClosed

if (isClosed()) {
    throw new SQLException("HikariDataSource " + this + " has been closed.");
}
private final AtomicBoolean isShutdown = new AtomicBoolean();

Determine whether the HikariDataSource has been closed.
Returns:
true if the HikariDataSource has been closed, false otherwise

public boolean isClosed()
{
    return isShutdown.get();
}

isClosed는 커넥션 풀이 닫혔는지 안 닫혔는 지를 나타내는 메소드로서 boolean 값으로 조절한다. 다만 유심히 볼 점은 단순한 boolean이 아니라 AtomicBoolean 이라는 점이다.

AtomicBoolean

public class AtomicBoolean implements java.io.Serializable {
    private static final long serialVersionUID = 4654671469794556979L;
    private static final VarHandle VALUE;
    static {
        try {
            MethodHandles.Lookup l = MethodHandles.lookup();
            VALUE = l.findVarHandle(AtomicBoolean.class, "value", int.class);
        } catch (ReflectiveOperationException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    private volatile int value;

여기서 봐야할 키워드는 VarHandle , volatile 이 두 가지다.

  • VarHandle
    • immutable and have no visible state
    • 일반 읽기와 쓰기는 32비트 미만의 참조 및 primitive에 대한 비트 단위 원자성을 보장한다. 또한 다른 특성과 관련하여 순서 제약을 부과하지 않는다.
    • Opaque 작업은 비트 단위 원자적이며 동일한 변수에 대한 액세스와 관련하여 일관성 있게 정렬
    • 획득 및 해제 작업은 불투명 속성을 따른다. 또한 획득 읽기는 릴리스 모드 쓰기가 일치한 후에만 정렬
    • 휘발성 작업은 서로에 대해 완전히 순서가 지정
    • Access to such variables is supported under various access modes, including plain read/write access, volatile read/write access, and compare-and-swap.
  • volatile
    • java 변수를 CPU 캐시가 아닌 메인 메모리에 저장
    • 멀티 쓰레드 환경에서 thread가 변수 값을 읽어 올 때 CPU Cache 저장된 값이 다르기 때문에 변수 값 불일치 문제를 최소화 시킴
    • 하나의 스레드가 read & write하고 나머지가 read를 할 때 적합한 상황

double-checked locking

      if (fastPathPool != null) {
         return fastPathPool.getConnection();
      }

      // See http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java
      HikariPool result = pool;
      if (result == null) {
         synchronized (this) {
            result = pool;
            if (result == null) {
               validate();
               LOGGER.info("{} - Starting...", getPoolName());
               try {
                  pool = result = new HikariPool(this);
                  this.seal();
               }
               catch (PoolInitializationException pie) {
                  if (pie.getCause() instanceof SQLException) {
                     throw (SQLException) pie.getCause();
                  }
                  else {
                     throw pie;
                  }
               }
               LOGGER.info("{} - Start completed.", getPoolName());
            }
         }
      }

      return result.getConnection();

지역변수로 왜 다시 명시?

Java Effective 동시성 관련 파트에서 말하기로는 지역변수로 다시 선언했을 때 그렇지 않았을 때보다 1.4배 정도 더 빠른 퍼포먼스를 보였다라고 언급이 되어 있습니다.

그럼 왜 double-checked locking을 할까?
위에 wiki 링크를 누르시면 자세하게 설명하고 있지만 여기에서 간략하게 설명하도록 하겠습니다.

싱글 스레드 환경이라면?
      if (fastPathPool != null) {
         return fastPathPool.getConnection();
      }

      if (result == null) {
            result = pool;
            pool = result = new HikariPool(this);
      }
      return result.getConnection();

위와 같이 조건문 하나로 처리가 가능합니다.

하지만 멀티쓰레드 환경 에서는?

동시에 조건문으로 들어오게 되면 HikariPool 인스턴스가 여러개 생기는 문제가 발생합니다.

그렇다면 어떻게?

getConnection() 메소드 자체를 synchronized 제어자를 걸어서 원자적으로 처리하는 방안이 있습니다.

하지만 이렇게 했을 때 단점이 존재하게 됩니다.
바로 퍼포먼스가 현저히 떨어지게 된다는 점이죠...

위키에서는 한 단계 더 설명은 wiki에서 확인해주시면 좋을 것 같습니다.

그래서 위와 같은 방식으로 처리함으로써 멀티쓰레드 환경에서 처리가 가능하고, 퍼포먼스의 이점도 어느정도 챙길 수 있는 환경을 구성하게 된 것입니다.

seal() ?

   public HikariDataSource(HikariConfig configuration)
   {
      configuration.validate();
      configuration.copyStateTo(this);

      LOGGER.info("{} - Starting...", configuration.getPoolName());
      pool = fastPathPool = new HikariPool(this);
      LOGGER.info("{} - Start completed.", configuration.getPoolName());

      this.seal();
   }
   public void copyStateTo(HikariConfig other)
   {
      for (Field field : HikariConfig.class.getDeclaredFields()) {
         if (!Modifier.isFinal(field.getModifiers())) {
            field.setAccessible(true);
            try {
               field.set(other, field.get(this));
            }
            catch (Exception e) {
               throw new RuntimeException("Failed to copy HikariConfig state: " + e.getMessage(), e);
            }
         }
      }

      other.sealed = false;
   }
   private void checkIfSealed()
   {
      if (sealed) throw new IllegalStateException("The configuration of the pool is sealed once started. Use HikariConfigMXBean for runtime changes.");
   }
   public void setAutoCommit(boolean isAutoCommit)
   {
      checkIfSealed();
      this.isAutoCommit = isAutoCommit;
   }

seal의 역할은 런타임 때 변경할 수 없게끔 설정하는 boolean 값입니다.

   // Properties NOT changeable at runtime
   //
   private long initializationFailTimeout;
   private String connectionInitSql;
   private String connectionTestQuery;
   private String dataSourceClassName;
   private String dataSourceJndiName;
   private String driverClassName;
   private String exceptionOverrideClassName;
   private String jdbcUrl;
   private String poolName;
   private String schema;
   private String transactionIsolationName;
   private boolean isAutoCommit;
   private boolean isReadOnly;
   private boolean isIsolateInternalQueries;
   private boolean isRegisterMbeans;
   private boolean isAllowPoolSuspension;
   private DataSource dataSource;
   private Properties dataSourceProperties;
   private ThreadFactory threadFactory;
   private ScheduledExecutorService scheduledExecutor;
   private MetricsTrackerFactory metricsTrackerFactory;
   private Object metricRegistry;
   private Object healthCheckRegistry;
   private Properties healthCheckProperties;

그래서 해당 값들 setXXX 로 시작하는 녀석들은 다 checkIfSealed() 메소드가 선언되어 있습니다.

HikariPool의 getConnection은 나중에 알아보도록 하겠습니다. (워낙 거대해서...)

close()

   @Override
   public void close()
   {
      if (isShutdown.getAndSet(true)) {
         return;
      }

      HikariPool p = pool;
      if (p != null) {
         try {
            LOGGER.info("{} - Shutdown initiated...", getPoolName());
            p.shutdown();
            LOGGER.info("{} - Shutdown completed.", getPoolName());
         }
         catch (InterruptedException e) {
            LOGGER.warn("{} - Interrupted during closing", getPoolName(), e);
            Thread.currentThread().interrupt();
         }
      }
   }

DataSource 또는 관련된 pool이 shutdown 했을 때 일어납니다.

여기에서도 지역변수를 다시 선언하여 사용하는 모습을 볼 수 있습니다.

자세히 보면 인터럽트가 일어날 때를 고려한 세심함도 볼 수 있습니다.

getAndSet()

해당 메서드는 atomic하게 처리하기 위해 사용되는 메서드로서 멀티쓰레드 환경에서 주로 사용됩니다.

 

오늘은 여기까지 살펴보겠습니다.

 

 

반응형

'JVM > Spring' 카테고리의 다른 글

[Hikari CP] 光 살펴보기 - 4  (0) 2022.04.29
[Hikari CP] 光 살펴보기 - 3  (0) 2022.04.28
[Hikari CP] 光 살펴보기 - 1  (0) 2022.04.26
[Spring] AOP  (0) 2022.04.05
[Spring] @Transactional  (0) 2022.04.02
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함