Quarkusのアーキテクチャ -スレッド編-

一般的な話

アプリケーションの中ではたくさんの通信やファイルなどのI/Oが実行されます。これらのI/Oが行われている間、スレッドはI/Oが終わるのを待っているしかありません。そのため、これらの処理はブロッキング処理と呼ばれています。待っている間、アプリケーションは処理をできなくなってしまいます。そのため、これまでのミドルウェアは100~1,000スレッドを用意して、同時に処理する数を増やすことでアプリケーションを稼働していました。

ブロッキング処理の対義語となる物はノンブロッキング処理です。ノンブロッキングではブロッキングの処理になったら別の処理をすることでスレッドが待機することを避けるようになっています。

多くのミドルウェアはノンブロッキングをサポートしています。しかし、ブロッキングを前提にしたアーキテクチャが多く残っています。そのため、アプリケーションとミドルウェアの内部で完全にノンブロッキングのメリットを受けることはできないことが多いです。

Quarkusでは

Quarkusはノンブロッキングを前提に設計されています。ノンブロッキングの部分の実装としてはVert.xを使用しています。ノンブロッキング対応の拡張機能を使うことで、アプリケーションとミドルウェアの両方がノンブロッキングで動きます。 また、Quarkusはもちろんブロッキング処理にも対応しています。この場合、ミドルウェアの部分はノンブロッキングで動きますが、アプリケーションの部分はブロッキングで動きます。

スループットが向上するため、ノンブロッキングの拡張機能がある場合はアプリケーションの部分をノンブロッキングで実装することをお奨めします。 以下のグラフはアプリケーションの実装方法によるスループットの比較です。アプリケーションの中で別のサービスへのREST APIを実行しており、その拡張機能がブロッキングかノンブロッキングなのかでスループットが大きく変わっています。 f:id:chiroito:20220322110901p:plain

Quarkusは一般的によく使われているライブラリをノンブロッキングな拡張機能として提供しています。しかしながら、これまで使われてきた全てのケースでノンブロッキングなものが使えるとは限りません。そのため、ブロッキングなライブラリを使わないといけないケースも多くあります。

Quarkusでブロッキング処理を実装するのも非常に簡単にできます。以下のように、これまで通りアプリケーションを実装するだけです。

    @GET
    @Path("/api")
    public Message endpoint1() {
        return blockingRestClient.invoke();
    }

これだけでミドルウェア部分はノンブロッキングで、アプリケーションの部分だけブロッキングで処理されます。

やってはいけないことは、アプリケーションの中で「ノンブロッキングです」と宣言しておいて、ブロッキングの処理をする事です。それをするとノンブロッキング用のライブラリの中でブロッキング処理するため、性能が非常に低下します。

    @GET
    @Path("/blocking/api")
    public Uni<Message> endpoint4() {
        return Uni.createFrom().item(blockingRestClient.invoke());
    }

参考

以下のブログでスレッドの使い方について細かく検証しました。詳細に興味があるかたはごらん下さい。

b.chiroito.dev

b.chiroito.dev

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。