IMAP:移行の問題

IMAPに埋められたレーキ





しばらくの間、IMAPはMail.Ru Mailで全面的に機能しており、開始時にどのような問題が発生したかについて話す準備ができています。 それらの一部はプロトコル自体の機能とその歴史に関連しており、その他はIMAPとリポジトリとの相互作用の詳細に起因していました。 別のカテゴリの問題は、さまざまな電子メールクライアントが原因です。



詳細については、猫へようこそ。



IMAPの現在の起動は、発射体に対する2番目のアプローチです。 前回、Dovecotサーバーを使用して、自分用にシャープ化を試みました。 結果は私たちには合わなかった。それは私たちのワークロードとインフラストラクチャーとうまく組み合わされていなかった。 今回は別のパスを選択することにし、独自のソリューションを作成しました。





5月にIMAPを開始しましたが、6月にのみ発表しました。 実際、5月のオーディエンスは、従業員と、クライアントが自動的にメールでIMAPを検出し、新しい追加アカウントをそれに接続したユーザーです。




IMAP固有の問題



1.プロトコル自体のかさ高さ


IMAPの最初のバージョンは1986年に登場しました。 2003年に更新されたIMAP標準バージョン4rev1は現在関連しています。 このような長い期間、標準は大幅に成長しました。現在のバージョンには約200ページあります。



現在、この規格に記載されているポイントの多くは時代遅れであり、今日の状況ではもはや必要ではありません。 たとえば、プロトコルは、行数と文字の一部のMD5合計を返します。これは、現代のクライアントでは実際には使用されない機能です。



さらに、多くのオプションのプロトコル拡張機能があります。 実際、それらのいくつかは、メールボックスでの便利な作業に必要です。



歴史的遺産を克服するには、いくつかの拡張機能を実装する必要がありました。 それらの1つはUID +です。メッセージをコピーまたは追加すると、コピーまたは追加の結果としてサーバーに表示された新しいメッセージのIDを返します。 これにより、どの文字が追加されたかを認識するためにクライアントが実行しなければならなかったリソース集約的な検索操作を節約できます。



2.標準的なサーバー操作パターンの欠如


IMAPは同じ問題を解決するための多くの方法を提供し、その結果、ほとんどすべてのクライアントが異なる動作パターンを持っています。 また、作業のパターンがWebメールやPOP3の動作と大きく異なることも重要です。





Appleデバイスの顧客は半数以上です。理由は、IMAPの自動検出がうまく機能しているためです。 対照的に、OutloookはPOP3でデフォルトで機能するため、手動でIMAPを設定する必要があります。


ここでは、メールボックスまたはフォルダ内のすべての手紙に関する情報をすぐに要求するデスクトップクライアントと、最初に最新の手紙に関する情報のみを最初に要求するモバイルクライアントの2つの主要なカテゴリを区別できます。 次に、手紙に関する情報を正確に引き出す方法の状態を更新するために、彼らが行う要求を調査する必要がありました。



なぜこれが私たちにとって必要なステップでしたか? 適度なワークロードでは(たとえば、サーバーが小規模企業の企業メールを処理する場合)、最適性の問題はそれほど深刻ではありません。 ただし、ボリュームが大きくなると、最適性が重要になります。そのため、リポジトリにアクセスする電子メールクライアントの動物園全体のパターンを調査する必要がありました。



3.同時セッションの数


標準に従って、最小サーバータイムアウトは30分です。 さらに、1つのクライアントがサーバーへの複数の接続を一度に保持できます(プロトコルは許可された接続の最大数を示していません)。 実際、私たちの規模では、これは1つのサーバーが何万もの同時接続で最適に動作することを意味します。 同期モードで作業している場合、このような数の接続はすべてのリソースを単純に吸収します。



この問題を解決するために、エッジトリガーepollに基づいて構築された非同期操作用のライブラリを作成しました。 最初は、ライブラリを作成するタスクを自分で設定しました。これにより、今後2、3日で非同期サーバーを記述してIMAP以外の問題を解決できるようになります。 その結果、ほとんどすべてのコードを使用して他のサービスを作成できます。



4.クライアントを一意に識別できない


サーバーはID拡張をサポートしているため、クライアントの約半分を特定できます。 残念ながら、残りの半分は、この拡張機能(たとえば、Outlookなどの人気のある拡張機能)については知りません。

どのクライアントと連携しているかを理解することで、その特徴的なバグを回避したり、このセッションのフレームワークでの作業パターンを予測したり、それに応じて作業を最適化したりできます。 これは私たちにとって非常に重要です。したがって、クライアントがIDに名前を付けない場合、他の方法(Outlookの場合はタグで)で識別しようとします。



5.メッセージを移動するコマンドがない


クライアントでは、移動はコピー+削除によって実装されます。 もちろん、手紙のコピーを正しいフォルダーに入れて、オリジナルを削除してバスケットを捨てないようにしたいと思います。 一方、ユーザー自身がレターを新しいフォルダーにコピーしてから元のフォルダーを削除することもあります。この場合、削除されたレターはごみ箱に入れる必要があります。

これら2つのケースを区別するために、同じセッションでコピーした後、特別な内部フラグで文字をマークします。 ユーザー自身がレターをコピーし、オリジナルを削除すると、クライアントは原則としてレターのリストを更新します。 更新すると、フラグは自動的にリセットされ、削除されたメッセージはゴミ箱にあります。 (移動の一部として)レターがクライアントによって削除された場合、更新は行われず、フラグの付いたレターは完全に削除されます。



文字と索引の現在のリポジトリを適応させることの難しさ



1.メッセージの識別


IMAPで作業するには、2種類のメッセージ識別子をサポートする必要がありました。セッションごとに異なるシリアル番号と、メッセージの全期間にわたって保存される一意の番号です。 両方の識別子は、WebメールとPOP3サーバーで使用されるパターンに一致しないかなり厳しい基準を満たす必要があります。



シリアル番号のセット全体、内部識別子への対応、およびIMAP IDをメモリに保持することが最も合理的です。 フォルダーを開くと、リポジトリからすべての文字、内部識別子、IMAP ID、チェックボックス、およびメッセージサイズのリストが取り出されます。これらはすべて、不要なリクエストなしで提供したいものです。



1つのセッション内でのシリアル番号のすべての変更は、クライアントに通知する必要があります。 標準に従って、IMAP IDシーケンスはシーケンス番号のシーケンスと一致する必要があります。 文字のリストを一意の番号でソートすることにより、シリアル番号を受け取ります。 クライアントが新しいコマンドを発行すると、リポジトリへの接続を再度開き、ボックスで行われた最後の変更の時刻を要求します。 最後のそのようなリクエスト以降に変更がなかった場合、コマンドへのレスポンスを返すだけです。 それ以外の場合は、フォルダー内のメッセージのリストを再要求し、クライアントの同様のリストと比較します。 次に、変更に関する情報を返すか、プロトコルでこれをすぐに行うことが許可されていない場合、レターが削除されたことをメモし、クライアントにこれを通知するタイミングを待ちます。



この特殊性のために、手紙はメールボックスにはもうありませんが、クライアントはまだそれを見つけていません-クライアントはすでに削除された手紙に関する情報を要求するかもしれません。 さらに、彼らの何人かは彼らの要求への応答が受け取られなければ深刻な問題を経験しました。 クライアントの破損を防ぐために、このようなリクエストに応じてスタブを返します。 クライアントは、通常の操作に必要な情報を受け取り、すでに文字のリストの次の更新時に(通常はすぐに発生します)、クライアントからメッセージを削除します。



2.レターのMIME構造に関する情報を最適に返す必要性


ほとんどすべてのクライアントは、レターの構造に関する情報を要求します。 多くの場合、最初のセッション中に、フォルダー内のすべての文字に関する情報をすぐに要求します。 そのようなリクエストごとにレターを解析することは、非常に最適ではありません。

代わりに、MIME構造のキャッシュを作成しました。 キャッシュの存在は、IMAP機能に関連するいくつかの困難を一度に克服するのに役立ちました-特に、サーバーと連携するための標準パターンの欠如:情報の一部がキャッシュに格納されるため、これはクライアント作業のさまざまなパターンに関連する負荷の平準化に役立ちます。



ここで、最大50個のメッセージをキャッシュします。 なぜ2-3ではないのですか? 実際、一部のクライアントは最初にレターの構造を要求し、次に本文を要求し、複数のメッセージを一度に要求します。 このような「バンドル」の文字の最大数は通常50個です。



3.手紙の一部の最適な返却


多くの場合、クライアントは手紙のテキスト部分のみを要求しますが、これはメッセージ自体の最後にある場合があります。 スニペットを表示するために、クライアントは一度に50〜200文字のテキストパーツを要求できます。 メッセージファイル全体を読みたくありません(10 KBのテキストを処理するために10 MBの文字を処理します)。 また、インデックスを使用して、各リクエストでファイル内のパーツの位置を決定することも有益ではありません。 この状況では、レター構造キャッシュも保存されます。

このアプローチの利点は、クライアントが数十文字のスニペットをロードする場合に特に顕著です。キャッシュ構造を使用しない場合、多くのメガバイトを表示し、速度を犠牲にしなければなりません。



ストレージのスペースを節約するために、base64パーツはメッセージ内にデコードされた形式で保存されます。ウェブメールを使用する場合、これにより不必要なトランスコーディングなしで添付ファイルを送信できます。 この再コーディングを考慮して、部品の返品のスキームを作成する必要がありました。 IMAPサーバーでストリーミングトランスコーディングを作成しました。 キャッシュもここで役立ちました-それのおかげで、構造を再読み込みすることなく、このフラグメントまたはそのフラグメントが保存されている形式(バイナリかどうか)を理解できます。



4.一部の顧客の仕事の特徴


一部のクライアントは、RFC標準に完全に準拠していません。たとえば、標準のAndroidクライアントバージョン2.2-2.3は、オプションフィールドを返さずに文字を正しく表示できません。 主な困難は、これらのクライアントのそれぞれが必須とみなしたフィールドを決定することでした。彼らは総当たりでこれを解決しなければなりませんでした。



クライアントは、文字を削除するためのさまざまなアプローチを使用できることを既に説明しました。一部のユーザーはごみ箱に移動し、他のユーザーはすぐに取消不能に削除します。 ただし、一部のお客様は、サポートされている標準のXLIST拡張を理解していないため、どのフォルダーがごみ箱であるかを判別できます。 代わりに、彼らはフォルダをバスケットとして使用します(これは、たとえばSparrowの動作です)。



Outlookはおもしろい動作をします。deleteコマンドによって、文字はごみ箱に移動されず、取り消し線でマークされ、後で削除されます。 ユーザーにとってはバグのように見えることが判明しました。多くの人は次に何をすべきか、クライアントが通常通りに振る舞い、手紙がバスケットに入っていることを確認する方法を理解していません。



このような多様性の状況では、各クライアントの動作を経験的に理解し、これらのオプションに適応する必要があります-ユーザーが確信できるように:どのクライアントを使用していても、メールは自分の行動に習慣的に反応しますそして予想通り(この場合、削除された手紙はバスケット宛です)。 テストプロキシサーバーを介してさまざまな回答を試し、各オプションに対するクライアントの反応を監視することで、これを解決しました。







私たち自身が学んだこと:IMAPはかなり「ゆるい」ものであり、26年以上にわたって多くの歴史的特徴が獲得されており、それにさまざまな電子メールクライアントが掛けられています。 私たちの負荷の下では、これは既成のソリューションを採用してそれを自分で研ぎ澄まそうとすることは非合理的であるという事実に変換されます:最良の場合、作業量はソリューションの独立開発と同じになります。 これが私たちが行った方法です:)



ビクター・スタロドゥブ、

Mail.ruメールチーム



All Articles