メむンコンテンツぞスキップ
芋出し画像

【劔"Tsurugi" Tips集】⌀量のトランザクションを捌く堎合はセッションプヌルの䜜成を怜蚎する【プログラミングTips】

    *****
    こちらの蚘事は、
    Tsurugi Advent Calendar 2024https://adventar.org/calendars/10853
    に参加しおいたす。

    劔"Tsurugi"関連の゚ンゞニアが12月25日たでの期間、さたざたな蚘事を掲茉しおいきたす。

    是非、こちらもご芧ください
    *****

    ⌀量のトランザクションを捌くようなオンラむンWebアプリケヌションの堎合、トランザクションのたびにDBぞの接続を⟏うず、そのオヌバヌヘッドが無芖できなくなる可胜性がありたす。

    たたDBずしおも、クラむアントからの接続数が無制限に増えおいくこずは避けたいものです。そのためオンラむンWebアプリケヌションでは、アプリケヌションサヌバヌ偎でDBぞの接続プヌルを✀意しおおき、アプリケヌションはこの接続プヌルからDB接続を取埗しお凊理を⟏う、ずいうスタむルが⌀般的です。

    Spring Framework や Jakarta EE では、接続プヌルを管理する仕組みが✀意されおいたすが、これはJDBCなどの既存APIを前提ずしおいたす。

    ここでは Spring Framework を䟋に、Tsurugiに察する接続プヌルTsurugiの堎合はセッションプヌルを構築する䟋を✰したす。

    セッションプヌルは Spring Bean ずしお䜜り、他の Bean にむンゞェクトできるようにしたす。
    Jakarta EE の堎合は、Singleton EJB もしくは ApplicationScoped な CDI Bean ずしお䜜るのがいいでしょう。

    import com.tsurugidb.iceaxe.TsurugiConnector;
    import com.tsurugidb.iceaxe.session.TsurugiSession;
    import jakarta.annotation.PreDestroy;
    import org.springframework.stereotype.Component;
    
    import java.io.IOException;
    import java.util.Deque;
    import java.util.concurrent.ConcurrentLinkedDeque;
    
    @Component
    public class TsurugiSessionPool {
    
      // 1, 2
      private final Deque<SessionCache> sessionCacheDeque = new
    ConcurrentLinkedDeque<>();
    
      private final TsurugiConnector connector;
    
      public TsurugiSessionPool(TsurugiConnector connector) {
           this.connector = connector;
      }
    
      // 3
      public SessionCache getSessionCache() throws IOException {
           while (true) {
              var sessionCache = sessionCacheDeque.pollFirst();
              // 4
              if (sessionCache == null) {
                  var session = connector.createSession();
                  return new SessionCache(this, session);
              }
              var session = sessionCache.getSession();
              // 5
              if (session.isClosed() || !session.isAlive()) {
                  // 保持しおいるセッションが無効な堎合は取り盎す
                  continue;
              }
              return sessionCache;
           }
      }
    
      // 6
      void releaseSessionCache(SessionCache sessionCache) {
          var session = sessionCache.getSession();
          if (session.isClosed() || !session.isAlive()) {
              // 接続が無効な堎合は戻さずに砎棄
              return;
          }
          sessionCacheDeque.addFirst(sessionCache);
      }
    
      // 7
      @PreDestroy
      public void destroy() {
          for (SessionCache sessionCache : sessionCacheDeque) {
              try {
                  sessionCache.getSession().close();
              } catch (IOException | InterruptedException ignore) {
                   // 終了時凊理なので䟋倖が発✣しおも無芖する
              }
          }
          sessionCacheDeque.clear();
      }
    }

    実装䞊のポむントは次の通りです。

    1. セッションプヌルは Deque で管理したす。耇数スレッドからアクセスされるのでConcurrentLinkedDeque を䜿いたす。
      ここでは容量無制限の ConcurrentLindedDeque を䜿っおいたすが、プヌルサむズを制限したい堎合は LinkedBlockingDeque を䜿っおもいいかもしれたせん。

    2. セッションオブゞェクトは埌述する SessionCache ずいうラッパヌクラスにラップしお管理したす。このクラスは、セッションがクロヌズされた際に実際に閉じるのではなく、セッションプヌルに返华する圹割を持ちたす。

    3. 利✀偎は getSessionCache() メ゜ッドを経由しお SessionCache オブゞェクトを取埗したす。

    4. プヌルからセッションが取埗できない、もしくは取埗しおも接続が無効であった堎合は新しくTsurugiConnector から TsurugiSession を取埗しお SessionChache にくるんで返したす。

    5. キャッシュから取埗したセッションが無効である堎合は再取埗を⟏いたす (ここで取り出された無効なセッションは捚おられるこずになる) 。

    6. この releaseSessionCache() メ゜ッドは埌述の SessionCache から呌び出され、䜿い終わったセッションをプヌルに戻したす。

    7. @PreDestory アノテヌションを付䞎したメ゜ッドを実装し、このBeanが砎棄される (アプリケヌションが終了する) タむミングでセッションを党お閉じたす。

    SessionCache の実装は次のようになりたす。

    import com.tsurugidb.iceaxe.session.TsurugiSession;
    
    import java.io.Closeable;
    import java.io.IOException;
    
    public class SessionCache implements Closeable { // 1
    
        private final TsurugiSessionPool ownerPool;
    
        private final TsurugiSession session;
    
        public SessionCache(TsurugiSessionPool ownerPool, TsurugiSession session) {
            this.ownerPool = ownerPool;
            this.session = session;
        }
    
        // 2
        public TsurugiSession getSession() {
            return session;
        }
    
        @Override
        public void close() throws IOException {
            ownerPool.releaseSessionCache(this);
        }
    }

    実装䞊のポむントは次の通りです。

    1. java.io.Closeable むンタヌフェヌスを実装し、try-with-resources✂で扱えるようにしたす。

    2. getSession() メ゜ッドを通しお⟃⟝が保持する TsurugiSession オブゞェクトを返したす。

    3. close() 凊理にお、内郚で SessionCache#releaseSessionCache() を呌び出しお⟃⟝をプヌルに返华したす。

    このセッションプヌルを利✀する✅法を✰したす。

    『 Webアプリケヌションではトランザクション境界ずなるコンポヌネントでTransactionManagerを䜿う 』にお䟋✰したサヌビスコンポヌネントに぀いお、このセッションプヌルを利✀する✅法に曞き換えたものになりたす。

    @Service
    public class ExampleService {
    
        private final TsurugiSessionPool sessionPool;
    
        private final ExampleRepository repository;
    
        // 1
        public ExampleService(TsurugiSessionPool sessionPool, ExampleRepository
    repository) {
            this.sessionPool = sessionPool;
            this.repository = repository;
       }
    
        public ExampleResponse doSomeLogic(ExampleInput input) {
            // 2
            try (SessionCache sessionCache = sessionPool.getSessionCache()) {
               // 3
               var tm = sessionCache.getSession().createTransactionManager(
                       TgTmSetting.ofAlways(TgTxOption.ofOCC(), 2);
               return tm.execute(transaction -> {
                   // 1トランザクションにたずめるべき凊理を実装しお結果を返す
               });
            } catch (IOException | InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    実装䞊のポむントは次の通りです。

    1. セッションプヌルはSpring Beanずしお䜜っおいるので、他のSpring Beanにむンゞェクト可胜です。ここではコンストラクタむンゞェクションを䜿っおいたす。

    2. try-with-resources✂を䜿っお SessionCache オブゞェクトを取埗しおいたす。こうするこずでtry✂を抜けるず⟃動的に SesshonCache#close が呌び出され、プヌルにセッションが返华されたす。

    3. SessionCache#getSession() メ゜ッドで TsurugiSession を取埗、TsurugiTransactionManagerを䜜りたす。


    次䞖代高速RDB劔"Tsurugi"は、オヌプン゜ヌスで公開䞭です。
    ぜひダりンロヌドしおみお、觊っおみおください。

    ダりンロヌドはコチラから
    https://www.tsurugidb.com/

    あなたぞのおすすめ