Slackは先頃、Eメールアドレスが社内向けか社外向けかを判別可能なEメールアドレス分類エンジン(classification engine)の開発に関する詳細を公開した。Slackのエンジニアたちは、同社システム内におけるデータの、結果整合的かつほぼリアルタイムな表現を利用したドリフト検出メカニズムを実装し、エラーを修正することによって、エンジン処理の正常性の維持を実現した。
同社のソフトウェアエンジニアであるSarah Henkens氏は、チームのアーキテクチャ決定の背景にあった動機を次のように説明している。
エンドユーザに対してスマートな提案を提供するために、Eメールアドレスが内部か、あるいは外部であるかを判断可能な分類エンジンを開発しました。これをサポートするために、ドメインとロール毎にグループ化したユーザ総数のほぼリアルタイムな値が必要でした。
数百万人のユーザを集計するバックフィルを毎日実行するのはコストが大き過ぎるので、結果整合データモデルを設計することによって、バックフィルやリアルタイムデータ更新を必要とせず、この機能をロールアウトすることができたのです。
Slackユーザが仲間を自身のSlackワークスペースに招待する場合、Slackは、内部か外部か、どちらのコラボレータを招待しようとしているのかを判断する必要がある。そのためSlackでは、社内の同僚を招待する場合には標準的なWorkspace Inviteを、外部の場合はSlack ConnectInviteを、それぞれ使用するように推奨している。以下の図は、そのプロセスを説明したものだ。
出典: https://slack.engineering/email-classification/
最初に、Slackのエンジニアが、招待された人それぞれのEメールアドレスを、テナントのドメイン設定および招待者のドメインと比較する。この比較は、実行コンテキスト内のデータを使用し、処理に時間を要しないことから、O(1)で実行される。
これとは対照的に、Team Contextの検索には、ドメイン毎にデータベースのラウンドトリップが必要だ。この問題についてHenkens氏は、次のように説明している。
Slackのチームは100万ユーザ以上の規模に達する場合もあるので、分類プロセス中にこの集計チェックをリアルタイムに実行するクエリは、極めて高価なものになる可能性があります。そのために、集計データセットをリアルタイムで反映するデータモデルを設計する必要が生じたのです。
集計データはVitessを使って、team_idで分割したシンプルなテーブルに格納しました。分類は常に単一チームのコンテキストで実行されるので、こうすることによって、常にひとつのシャード(shard)にヒットすることが保証されます。
それぞれのシャードは、ロール毎に、特定のテナント内におけるEメールドメイン毎の出現数を集計した値を、マテリアライズドビューとして維持する。分類エンジンがこのビューをクエリすることで、このデータに基づいたヒューリスティックな判断が可能になるのだ。以下に示すのは、ビューのスキーマである。
CREATE TABLE `domains` (
`team_id` bigint unsigned NOT NULL,
`domain` varchar NOT NULL,
`count` int NOT NULL DEFAULT '0',
`date_update` int unsigned NOT NULL,
`role` varchar NOT NULL,
PRIMARY KEY (`team_id`,`domain`,`role`)
)
例えばユーザがワークスペースに参加したり、あるいは自身のEメールアドレスを変更したりすれば、システム内でイベントが生成される。このイベントを"Mutate Job"が検知して、マテリアライズドビューに適切な変更を加えるが、再計算は必ずしも行わない。
出典: https://slack.engineering/email-classification/
しかしながら、非同期ジョブキューでは、通常、Mutate Jobが各変更イベントを正確に1回のみ実行する、という保証はない。この事実はやがて総数のずれを引き起こすことになるため、エンジニアはこれに対処する必要がある。定期的にビューをゼロから再構築する方法もあるが、
Slackのエンジニアたちは、Healerコンポーネントを開発することにした。このHealerコンポーネントは、ドメインの集計数が無効データを示すマイナス値になると起動され、集計データとリアルタイムデータを効率的に比較した上で、修正のためにアップデートを実行する。修正操作はデータベース内の既存の集計値に対して+Nないし-Nを行う単純なUPSERTなので、処理を停止しなくても、Healerの動作中に発生した変更操作が失われる心配はない。
Slackの次のステップは、ディープラーニングを使ってEメール分類エンジンを強化し、より正確な結果を得ることだ。