[Laravel 8.45.1] マルチテナントアーキテクチャ

Laravelでのマルチテナントは、tenancy/tenancyを使用する。
tenancy/tenancyと、ベースの機能のみで必要な分は後から追加するtenancy/frameworkがある。
tenancy/framework推奨

$ composer require tenancy/framework
$ php artisan make:model Organization -m

create_organizations_table.php

public function up()
    {
        Schema::create('organizations', function (Blueprint $table) {
            $table->id();
            $table->string('name')->unique();
            $table->timestamps();
        });
    }

$ php artisan migrate

Organization.php

use Illuminate\Http\Request;
use Tenancy\Identification\Contracts\Tenant;

class Organization extends Model implements Tenant
{
    use HasFactory;

    protected $fillable = [
    	'name',
    	'subdomain',
    ];

    protected $dispatchesEvents = [
    	'created' => \Tenancy\Tenant\Events\Created::class,
    	'updated' => \Tenancy\Tenant\Events\Updated::class,
    	'deleted' => \Tenancy\Tenant\Events\Deleted::class,
    ];

    public function getTenantKeyName(): string {
    	return 'name';
    }

    public function getTenantKey(){
    	return $this->name;
    }

    public function getTenantIdentifier(): string {
    	return get_class($this);
    }
}

Tenancy\Identification\Concerns\AllowsTenantIdentification を使うだけでもできる
-テナントが追加されたら、テナント用のデータベースを作成してマイグレーション
-テナントが更新されたらマイグレーション実行
-テナントが削除されたらデータベース削除
-テナントを識別するため、Resolverに登録

app/Providers/AppServiceProvider

use Tenancy\Identification\Contracts\ResolvesTenants;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        //
        $this->app->resolving(ResolvesTenants::class, function(ResolvesTenants $resolver){
            $resolver->addModel(Organization::class);
            return resolver;
        });
    }
}

MySQLのデータベースドライバをインストール
$ composer require tenancy/db-driver-mysql

affects connections : テナントのDBにテナントのDBにアクセスできるようにする
$ composer require tenancy/affects-connections

tenancyの拡張パッケージは Event/Listner で構成
$ php artisan make:listener ResolveTenantConnection
/listeners/ResolveTenantConnection.php

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Tenancy\Identification\Contracts\Tenant;
use Tenancy\Affects\Connections\Contracts\ProvidesConfiguration;

class ResolveTenantConnection
{
    public function handle(Resolving $event)
    {
        //
        return $this;
    }

    public function configure(Tenant $tenant): array {
        $config = [];

        event(new Configuring($tenant, $config, $this));

        return $config;
    }
}

/app/Providers/EventServiceProvider.php

use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [

        \Tenancy\Affects\Connections\Events\Resolving::class => [
            App\Listeners\ResolveTenantConnection\ResolveTenantConnection::class,
        ],
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
    ];
}

$ composer require tenancy/hooks-migration
$ php artisan make:listener ConfigureTenantMigrations

use Tenancy\Hooks\Migration\Events\ConfigureMigrations;
use Tenancy\Tenant\Events\Deleted;

class ConfigureTenantMigrations
{
    public function handle(ConfigureMigrations $event)
    {
        //
        if($event->event->tenant){
            if($event->event instanceof Deleted){
                $event->disable();
            } else {
                $event->path(database_path('tenant/migrations'));
            }
        }
    }
}

EventServiceProvider.php

use Illuminate\Support\Facades\Event;
use App\Listeners\ConfigureTenantMigrations;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
        $this->app->resolving(ResolvesTenants::class, function(ResolvesTenants $resolver){
            $resolver->addModel(Organization::class);
            return resolver;
        });
    }

    protected $listen = [
        \Tenancy\Hooks\Migration\Events\ConfigureMigrations::class => [
            ConfigureTenantMigrations::class,
        ],
    ];
}

$ php artisan make:listener ConfigureTenantSeeds

use Database\Tenant\Seeders\DatabaseSeeder;
use Tenancy\Hooks\Migration\Events\ConfigureSeeds;
use Tenancy\Tenant\Events\Deleted;
use Tenancy\Tenant\Events\Updated;

class ConfigureTenantSeeds
{
    public function handle(ConfigureSeeds $event)
    {
        //
        if($event->event->tenant){
            if($event->event instanceof Deleted || $event->event instanceof Updated){
                $event->disable();
            } else {
                $event->seed(DatabaseSeeder::class);
            }
        }
    }
}

EventServiceProvider.php

    protected $listen = [

        \Tenancy\Affects\Connections\Events\Resolving::class => [
            App\Listeners\ResolveTenantConnection\ResolveTenantConnection::class,
        ],
        \Tenancy\Hooks\Migration\Events\ConfigureMigrations::class => [
            ConfigureTenantMigrations::class,
        ],
        \Tenancy\Hooks\Migration\Events\ConfigureSeeds::class => [
            ConfigureTenantSeed::class
        ],
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
    ];

.env.testing

TENANT_SLUG=test

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=test_central
DB_USERNAME=root
DB_PASSWORD=password

あれ? これでどうやって実装するのかようわからんな。。

エンジニアがベンチャーキャピタルと面談(資金調達)する時のコツ

エンジニアであれば、プロダクトを開発してベンチャーキャピタルに資金調達を依頼する機会が何度かあるであろう。
今日私はVCと面談した。その経験を元に、大事なことと感じた内容を整理したい。

■1.事業の拡大
これが全てだと断言して良い。ベンチャーキャピタルが興味を持っているのは「どうやって事業を拡大するか」の一点に尽きる。もちろんIPOを見据えての事だが、Buyoutでも数十億以上で売れないと意味がないらしい。
つまり、スケールしなければ、全く興味がなく、「equityで調達する意味ない」と言われる。
よって、どうやって事業を拡大するかを説得できるように準備して面談に望む事。

■2.課題・悩み事
どういうことに悩んでいるか、面談で言ってみると良い。それに対して、ベンチャーキャピタリストの見解を答えてくれる。

■3.技術的なアドバンテッジについて
VCはコンサルに近い。相手によるかと思うが、どういう技術を使っているかなどは、聞かれたら答える程度で良い。
企画や営業と話す時のように、噛み砕いで話す姿勢を見せた方が良いように思った。
それよりもビジネスとして競合よりもどう優れているかを説明しなければいけない。

■4.質問の準備
10個くらい質問を準備して面談に臨んだが、全く意味がなかった。
就活の面談ではないので、「相手に興味を示す」というのは全然求められていないようだった。
(投資で重視されていること、サポート体制、何故VCをやっているのかなど色々聞こうと思っていたが、それはナンセンスな質問だと悟った)

■5.実績のアピールが重要
サービスに関する実績をアピールすることが大事。
「どういう実績があるからこのサービスを作っている」ということを上手く説明するのが大事だ。
VCは、何故そのサービスをやっているかについては興味を持っている。

ということで、私は面談は撃沈したのだが、参考にして欲しい。
ベンチャーキャピタルの面談では、開発プロジェクトの面談などと違って、ゼロから1まで全て開発したとか、どの機能の開発に工夫しているなんてことは一切聞かれない。

——-
追加情報1
■6. ストーリーが大事
営業戦略、売上をどう立てていくか

■7. 売上10億立てないと駄目
上場基準として10億円ないと上場できない

■8. バリエーション
リード投資もしくは事業会社にバリエーションを作ってもらう

——-
追加情報2
■9. 組織体制
強いエンジニア、営業スタッフを揃えているか重視している

■10. ユーザヒヤリング
ニーズをきちんと聞くこと
ヒヤリングの仕組み作りを検討する

■11. 組織的な強みを作ること
競合にはない強みを持つこと

■12. 事業計画の立て方
事業計画の作成能力は大事
初年度などは赤字の方が見栄えが良い
売上が取れないことが最も多く致命的な問題

■13. リプレイス
システムのリプレイスの売り方を考える

[Python3 音声認識] juliasを使ってみる

### juliusとは
– 京大や名古屋工業大が開発しているオープンソース音声認識ライブラリ
– C言語で書かれている
– 独自の辞書モデルを定義することが可能
– 単体では動作せず、言語認識をするモデルを読み込んで動かす
– Juliux GitHub: https://github.com/julius-speech/julius

### julius install
$ sudo yum install build-essential zlib1g-dev libsdl2-dev libasound2-dev
$ git clone https://github.com/julius-speech/julius.git
$ cd julius
$ ./configure –enable-words-int
$ make -j4
$ ls -l julius/julius
-rwxrwxr-x 1 vagrant vagrant 700208 Jul 31 16:39 julius/julius

### julius model
https://sourceforge.net/projects/juliusmodels/
「ENVR-v5.4.Dnn.Bin.zip」をDownloadします。
$ unzip ENVR-v5.4.Dnn.Bin.zip
$ cd ENVR-v5.4.Dnn.Bin
$ ls
ENVR-v5.3.am ENVR-v5.3.layer5_weight.npy ENVR-v5.3.prior
ENVR-v5.3.dct ENVR-v5.3.layer6_bias.npy README.md
ENVR-v5.3.layer2_bias.npy ENVR-v5.3.layer6_weight.npy dnn.jconf
ENVR-v5.3.layer2_weight.npy ENVR-v5.3.layerout_bias.npy julius-dnn-output.txt
ENVR-v5.3.layer3_bias.npy ENVR-v5.3.layerout_weight.npy julius-dnn.exe
ENVR-v5.3.layer3_weight.npy ENVR-v5.3.lm julius.jconf
ENVR-v5.3.layer4_bias.npy ENVR-v5.3.mfc mozilla.wav
ENVR-v5.3.layer4_weight.npy ENVR-v5.3.norm test.dbl
ENVR-v5.3.layer5_bias.npy ENVR-v5.3.phn wav_config

$ sudo vi dnn.jconf

feature_options -htkconf wav_config -cvn -cmnload ENVR-v5.3.norm -cvnstatic
// 省略
state_prior_log10nize false // 追加

### Recognize Audio File
$ ../julius/julius/julius -C julius.jconf -dnnconf dnn.jconf
————-pass1_best: the shower
pass1_best_wordseq: the shower
pass1_best_phonemeseq: sil | dh iy | sh aw ax r
pass1_best_score: 130.578949
### Recognition: 2nd pass (RL heuristic best-first)
STAT: 00 _default: 21497 generated, 4114 pushed, 332 nodes popped in 109
ALIGN: === word alignment begin ===
sentence1: the shower
wseq1: the shower
phseq1: sil | dh iy | sh aw ax r | sil
cmscore1: 0.552 0.698 0.453 1.000
score1: 88.964218
=== begin forced alignment ===
— word alignment —
id: from to n_score unit
—————————————-
[ 0 11] 0.558529 []
[ 12 30] 2.023606 the [the]
[ 31 106] 2.249496 shower [shower]
[ 107 108] -2.753532
[
]
re-computed AM score: 207.983871
=== end forced alignment ===

——
### read waveform input
1 files processed

音声認識をやってるっぽいのはわかるけど、Google-cloud-speechとは大分違うな

$ sudo vi mic.jconf

-input mic
-htkconf wav_config
-h ENVR-v5.3.am
-hlist ENVR-v5.3.phn
-d ENVR-v5.3.lm
-v ENVR-v5.3.dct
-b 4000
-lmp 12 -6
-lmp2 12 -6
-fallback1pass
-multipath
-iwsp
-iwcd1 max
-spmodel sp
-no_ccd
-sepnum 150
-b2 360
-n 40
-s 2000
-m 8000
-lookuprange 5
-sb 80
-forcedict

$ ../julius/julius/julius -C mic.jconf -dnnconf dnn.jconf
Notice for feature extraction (01),
*************************************************************
* Cepstral mean and variance norm. for real-time decoding: *
* initial mean loaded from file, updating per utterance. *
* static variance loaded from file, apply it constantly. *
* NOTICE: The first input may not be recognized, since *
* cepstral mean is unstable on startup. *
*************************************************************
Notice for energy computation (01),
*************************************************************
* NOTICE: Energy normalization is activated on live input: *
* maximum energy of LAST INPUT will be used for it. *
* So, the first input will not be recognized. *
*************************************************************

——
### read waveform input
Stat: adin_oss: device name = /dev/dsp (application default)
Error: adin_oss: failed to open /dev/dsp
failed to begin input stream

使い方がイマイチよくわからん
全部コマンドラインでアウトプットが出てくるんかいな

[エンジニアの電話営業] コツ1

1. 嫌がられるのに負けない心を持つこと
 L 遠回しに言わずに簡潔に
 L 相手の気持ちを考える
 L セールスっぽくならないように
 -> モチベーションを保つ工夫をすること : 目標を達成したら美味いもの食べる、興味ある業界に営業する
 -> 何歳くらいの、どういう人が出たか記録を付けて分析する

2. 同業種の営業マンに聞いたり、話を振ってみる
      丁寧に言葉遣いを気をつける
「〜ご検討いただけないかと思いましてですね」と言っているので、「〜ご検討いただけないでしょうかと思いご連絡させていただきました。」にトークスクリプトを変更する

3. その他
 - リアクション芸人
– 声を大きく
 - メリットを伝える

モチベーション、精神面が一番大事だな。一番最初に一番大事な内容が来ました。

[GCP] Python3 x google-cloud-speechで音声認識を実装する

GCPでCloud Speech-to-Text APIを有効化します。

GCPユーザの認証データの*.jsonを取得します。

audio.rawはこちらからお借りします。
https://github.com/googleapis/java-speech/tree/master/samples/install-without-bom/resources

$ pip3 install google-cloud-speech

main.py

import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "hpscript.json"
from google.cloud import speech
import io

def transcribe_file(speech_file):
	
    client = speech.SpeechClient()

    with io.open(speech_file, "rb") as audio_file:
        content = audio_file.read()

    audio = speech.RecognitionAudio(content=content)
    config = speech.RecognitionConfig(
        encoding=speech.RecognitionConfig.AudioEncoding.LINEAR16,
        sample_rate_hertz=16000,
        language_code="en-US",
    )
    response = client.recognize(config=config, audio=audio)

    for result in response.results:
        print(u"Transcript: {}".format(result.alternatives[0].transcript))

if __name__ == '__main__':
	transcribe_file('audio.raw')

$ python3 main.py
Transcript: how old is the Brooklyn Bridge

うーむ、なんだこれは

[AWS ALB] EC2で最初の接続だけ異常に重い時

EC2で最初の接続だけ異常に重く、一度接続すると、サクサク動く。
何故だ?curlで計測したところ、以下の様に最初の接続に130秒もかかっている。

$ curl ‘https://*.com/login’ -H ‘Accept-Encoding: gzip, deflate, sdch’ ept-Language: en-US,en;q=0.8,ja;q=0.6′ -H ‘Upgrade-Insecure-Requests: 1’ -H ‘User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.86 Safari/537.36’ -H ‘Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8’ -H ‘Connection: keep-alive’ –compressed -o /dev/null -w “%{time_starttransfer}\n” -s
130.982826

最初、メモリが問題かと思って、増設し、さらにswapもつけたが、それでも変わらない。

$ df
ファイルシス 1K-ブロック 使用 使用可 使用% マウント位置
devtmpfs 492676 0 492676 0% /dev
tmpfs 503444 0 503444 0% /dev/shm
tmpfs 503444 524 502920 1% /run
tmpfs 503444 0 503444 0% /sys/fs/cgroup
/dev/xvda1 18862060 13096136 5765924 70% /
tmpfs 100692 0 100692 0% /run/user/1000

$ free
total used free shared buff/cache available
Mem: 1006892 713376 72396 352 221120 154016
Swap: 1048572 768 1047804

明らかにおかしいと思ったら、2つのsubnetのうち1つがinternet gatewayにアタッチされていなかったのが原因。

AWS Elastic Load Balancing: Seeing extremely long initial connection time
https://stackoverflow.com/questions/35523421/aws-elastic-load-balancing-seeing-extremely-long-initial-connection-time

ALBだとAZで2つのsubnetの設定が必要だが、今回はインスタンスは1つしか使わないので、internet gatewayには1つしかアタッチしてなかった。もう一つのpublic subnetをigwにアタッチしたところ、0.63msに改善

$ curl ‘https://*.com/login’ -H ‘Accept-Encoding: gzip, deflate, sdch’ -H ‘Accept-Language: en-US,en;q=0.8,ja;q=0.6’ -H ‘Upgrade-Insecure-Requests: 1’ -H ‘User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.86 Safari/537.36’ -H ‘Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8’ -H ‘Connection: keep-alive’ –compressed -o /dev/null -w “%{time_starttransfer}\n” -s
0.638270

メモリの問題じゃなかったああああああああああああああああああ 
なんてこったい

[Laravel x Python] exec($command, $output)のトラブルシューティング

execコマンドがvagrantのamazon linux2では問題なく動作するのに、ec2にデプロイして実行すると反応しない。
execコマンドだとダメそうなので、SymphonyのProcessでやってみたが、それでもダメ。
NLPは最も肝となる処理なので諦めるわけにはいかん、とトラブルシューティングを丸一日。Webの記事を探しまくるも一向に解決せず。

該当のcontroller

            $input = $request->input;
            $replace_input = str_replace(" ", "\ ", $input);
            $path = app_path() . "/python/main.py";
            // $command = "python3 " . $path . " ".$replace_input;
            // exec($command, $output);
            $process = new Process(["python3", $path, $replace_input]);
            $process->run();
            $output = $process->getOutput();
            dd($output);

結論から言うと、ProcessFailedExceptionでエラー原因を表示させたらわかった。sudu pip3でpandas, sklearn, MeCabなどをインストールすればよかった。

use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
// 省略

            $process = new Process(["python3", $path, $replace_input]);
            $process->run();
            if (!$process->isSuccessful()) {
                throw new ProcessFailedException($process);
            }

エラーが起きたら、まず、Exceptionで原因を調べるのが早いですね。
laravelでpythonの処理は変則的なので、かなり焦りました。
もっと上達してええええええええええええ

[AWS EC2] mecab-ipadic-neologd で std::bad_alloc

EC2にMecabを入れます。

ssh ec2-user@18.179.118.214 -i ~/.ssh/aws-dev.pem

$ sudo yum update -y
$ sudo yum groupinstall -y “Development Tools”

$ wget ‘https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7cENtOXlicTFaRUE’ -O mecab-0.996.tar.gz
$ tar xzf mecab-0.996.tar.gz
$ cd mecab-0.996
$ ./configure
$ make
$ make check
$ sudo make install
$ cd –
$ rm -rf mecab-0.996*

$ git clone –depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
$ ./mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -n -a -y
terminate called after throwing an instance of ‘std::bad_alloc’
what(): std::bad_alloc
/home/ec2-user/mecab-ipadic-neologd/bin/../libexec/make-mecab-ipadic-neologd.sh: 525 行: 24919 Aborted ${MECAB_LIBEXEC_DIR}/mecab-dict-index -f UTF8 -t UTF8

8.0G中、7.6G使用しており、メモリが足りなくなっています。
$ df -hT
ファイルシス タイプ サイズ 使用 残り 使用% マウント位置
devtmpfs devtmpfs 482M 0 482M 0% /dev
tmpfs tmpfs 492M 0 492M 0% /dev/shm
tmpfs tmpfs 492M 460K 492M 1% /run
tmpfs tmpfs 492M 0 492M 0% /sys/fs/cgroup
/dev/xvda1 xfs 8.0G 7.6G 455M 95% /
tmpfs tmpfs 99M 0 99M 0% /run/user/1000

memoryが足りないのでebsのボリュームを8Gから15Gにあげます。

$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda 202:0 0 15G 0 disk
└─xvda1 202:1 0 8G 0 part /

$ sudo xfs_growfs /dev/xvda1
data blocks changed from 2096635 to 3931643

$ df -h
ファイルシス サイズ 使用 残り 使用% マウント位置
devtmpfs 482M 0 482M 0% /dev
tmpfs 492M 0 492M 0% /dev/shm
tmpfs 492M 460K 492M 1% /run
tmpfs 492M 0 492M 0% /sys/fs/cgroup
/dev/xvda1 15G 7.6G 7.5G 51% /
tmpfs 99M 0 99M 0% /run/user/1000

$ ./mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -n -a -y
[install-mecab-ipadic-NEologd] : Finish..

$ echo すもももももももものうち | mecab
すもももももももものうち 名詞,固有名詞,一般,*,*,*,すもももももももものうち,スモモモモモモモモノウチ,スモモモモモモモモノウチ
EOS

>>> import MeCab
>>> tagger = MeCab.Tagger(‘-Ochasen -d/usr/local/lib/mecab/dic/mecab-ipadic-neologd’)
>>> print(tagger.parse(‘すもももももももものうち’))
すもももももももものうち スモモモモモモモモノウチ すもももももももものうち 名詞-固有名詞-一般
EOS

MeCabが使えないと色々と前提が崩れるところやったわ

[AWS] Lambdaの基本機能

tutorialの”Introduction to AWS Lambda”で学んでいきます。

S3でbucketを作ります。
images-1357-4563
images-1357-4563-resized

HappyFace.jpeg

bucketのimages-1357-4563にuploadします。
Lambdaのcreate function

function name: Create-Thumbnail
runtime: Python 3.7 *tutorialはpython3.8で実行するとエラーになります。
Permissions: use an existing role
L lambda execution role

Add Trigger
S3
bucket: images-1357-4563
event type: All object create events

upload file
https://s3-us-west-2.amazonaws.com/us-west-2-aws-training/awsu-spl/spl-88/2.3.15.prod/scripts/CreateThumbnail.zip

Runtime setting: CreateThumbnail.handler

Test event: New event
template: s3-put

{
  "Records": [
    {
      "eventVersion": "2.0",
      "eventSource": "aws:s3",
      "awsRegion": "us-west-2",
      "eventTime": "1970-01-01T00:00:00.000Z",
      "eventName": "ObjectCreated:Put",
      "userIdentity": {
        "principalId": "EXAMPLE"
      },
      "requestParameters": {
        "sourceIPAddress": "127.0.0.1"
      },
      "responseElements": {
        "x-amz-request-id": "EXAMPLE123456789",
        "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH"
      },
      "s3": {
        "s3SchemaVersion": "1.0",
        "configurationId": "testConfigRule",
        "bucket": {
          "name": "images-1357-4563",
          "ownerIdentity": {
            "principalId": "EXAMPLE"
          },
          "arn": "arn:aws:s3:::images-1357-4563"
        },
        "object": {
          "key": "HappyFace.jpeg",
          "size": 1024,
          "eTag": "0123456789abcdef0123456789abcdef",
          "sequencer": "0A1B2C3D4E5F678901"
        }
      }
    }
  ]
}

Execution result: succeeded
S3で確認するとリサイズされていることがわかります。

monitor, cloudwatchで確認できます。

add triggerは基本的にAWSでのサービスなんですね。

一周回って動きの流れは理解したが、アプリケーション側のイベントとは概念が異なるようです。
インフラで何か起きた時に、サーバレスでtriggerから実行するってことか。
数年前にも同じことやったが過去と比べて理解度が違ってる。