Kafka Trigger で Deployment Slot を使ったらどういう挙動をするか？

`Kafka Trigger` で Deployment Slot を使うとどうなるか？というのを聞かれたので実際に自分で試してみた。

# Kafka Trigger サンプル

次のようなサンプルを作ってみる。バージョンは最新の`<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Kafka" Version="3.1.0" />` を使っている。アプリはとても簡単なのもので、次のようになっている。

| Property | Production | Staging |
| -------- + ---------- + -------- |
| TOPIC | message | message_staging |
| SLOT_KIND | Production | Staging |

上記のように、Production/Staging で設定を変えてアプリのバージョンアップをしながら次の操作を行う。

1. Message を message トピックに送信
2. スワップ
3. Message 2 を message トピックに送信

下記のアプリケーションは、途中で３分停止するようになっていて、その後処理を再開する。その３分の間にスワップをするとどのような挙動になるのかを調べてみたい。

```csharp
   public class SimpleKafkaTrigger
    {
        [FunctionName(nameof(SampleConsumerAsync))]
        public async Task SampleConsumerAsync(
        [KafkaTrigger(
            "%ConfluentBrokerList%",
            "%TOPIC%",
            ConsumerGroup = "$Default",
            Username = "%ConfluentCloudUsername%",
            Password = "%ConfluentCloudPassword%",
            Protocol = BrokerProtocol.SaslSsl,
            SslCaLocation = "confluent_cloud_cacert.pem",
            AuthenticationMode = BrokerAuthenticationMode.Plain)] KafkaEventData<string> kafkaEvent,
            ILogger logger)
        {
            logger.LogInformation($"Ver 1.0.1 Slot [{Environment.GetEnvironmentVariable("SLOT_KIND")}] Consumed: {kafkaEvent.Value.ToString()}");
            logger.LogInformation("Going To Sleep");
            await Task.Delay(TimeSpan.FromMinutes(3));
            logger.LogInformation($"Ver 1.0.1 Slot [{Environment.GetEnvironmentVariable("SLOT_KIND")}] Finished: {kafkaEvent.Value.ToString()}");
        }
    }
```

今回は Consumption Plan - Windows 試している。ホストのバージョンは`3.0.14492.0` だった。デプロイメントスロットのスワップを設定する。AppSettings をまず Production 側で設定した後に、Staging 側を作成すると設定がコピーされて楽だった。そして、`Deployment slot setting` を、`SLOT_KIND` と `TOPIC` のみチェックを入れる。これによって、それらのセッティングがスロットスワップでも入れ替わらなくなる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470/5dda742d-98b4-90bb-6857-269dcc52d11d.png)

準備は完了。試しに Confluence Cloud にメッセージを送信して、正しく読み込まれるかを確認する。Production には Ver 1.0.0 を Staging には Ver 1.0.1 をデプロイしている。

# Message (before)
Confluent Cloud から Message を送信する

# Swap

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470/d9b9b8c7-ed69-2488-4d44-b96358574635.png)

Message で起動されたFunctionが３分間停止している間に、Deployment Slots の `Swap` ボタンをクリックしてスワップを行う。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470/83d52ec3-3365-268a-36a3-7c6e8b7f61db.png)
上記に `SLOT_KIND` と `TOPIC` が含まれていないことを確認する。

# Message (After)
Messageを送信する

# Log
Application Insgihtsのログを時系列にソートしてどのようなことが起こっているかを分析する。

最初のメッセージが `ver 1.0.0` として受け取られて、そのままスリープ状態になっている。その後、`Host` が停止して、Functions自体のリスナーも停止している。ただし、このメッセージは終了していない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470/39660494-8556-622b-e255-f37db16548db.png)

基本的な挙動では、Swapの前に、Staging側のスロットのプリウォームが行われるので、ちゃんとFunctionsを起動させておくため、ここで、Production側 (V1.0.1, tsushiswap3) 側ホストのスタートが行われている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470/dd5d956f-ad8c-27aa-3047-2eaa0a3c0e85.png)

その後に、該当のFunction が実行されているのがわかる。３つ動いているが、１つは私のオペミスなので、忘れてほしい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470/ede36581-a67a-96e9-9d24-e54e79b604ce.png)

面白いことに、Swapのあとのものだけではなく、Swap前のものが、新しいバージョンで起動している。Functions側でRe-Run が起こったのではなく、Kafka Trigger 側で、Functionsの実行が終了する前に途中でホストが止まったので、メッセージが再度 TOPIC に戻って再度 Consumpe されたと考えるのが自然だと思う。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470/d9a600b4-9002-b1cb-da5e-ac53d5dc04a6.png)
３分経つと、ちゃんと、それぞれのメッセージが終了している
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470/c3cd3f9e-85f0-29f3-ba67-87cc3e67e1ab.png)

# Kafka Trigger 時の Swap Slot の挙動のまとめ

メッセージの実行中のFunctionsがあるときに、スワップが実行されるとホストが停止して、Production側に新しいバージョンで、再実行される。それは、Swap前のメッセージのFunction実行が終了してないからで、そのケースでは、TOPICにメッセージが戻されるため、Production側で、新しいアプリケーションとして、再度実行される。

つまり、この挙動から考えられることは、SwapしたいKafkaTriggerがある場合は、KafkaTriggerをIdempotent、つまり、何回実行しても結果は同じというコードにしておく必要がある。しかし、それを実行しておけば、Slotを使うと、バージョンの切り替えはメッセージのロスなく、簡単に行うことができるだろう。

P.S. 自分の実験では上記の通りだが、本当にこの解釈であってるかはコードを読んで本人に聞いてみたい。あと、Premium でも同じ挙動だと思うけど、どうなるかは気になるところ。Premium は testing in production の機能があるので、少しづつ切り替えることが可能なので。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470/743f0c0f-e847-2c24-b558-b284695161f6.png)
**NOTE:** 本人に聞いてみたが、私の観察したとおりの振る舞いの様子（Premiumも） 10/5/2020

# Resource

* [Azure Functions deployment slots](https://docs.microsoft.com/en-us/azure/azure-functions/functions-deployment-slots)