代表的な面接トピック

WASI 0.3 面接対策: Async、Streams、Futures はコンポーネント間をどのように横断するか?

コーディング難しい
Offer.cc 編集チーム公開日 更新日

質問

プラグインホストが WASI 0.2 から 0.3 への移行を準備しています。ネイティブ async、streams、futures がコンポーネント境界を越えて readiness(準備完了状態)をどのように伝播させるかを説明し、キャンセル、バックプレッシャー、エラー処理、段階的移行の挙動を設計してください。

プロンプトとコンテキスト

あるプラグインホストが、複数の言語で書かれたコンポーネントを実行しています。既存のコンポーネントは WASI 0.2 の wasi:io リソースに依存していますが、新規コンポーネントは WASI 0.3 の async funcstreamfuture を必要としています。ログストリーミング、リモート呼び出し、キャンセルのためのインターフェースを設計してください。ホストがどのように作業をスケジューリングし、リソースを制限し、既存コンポーネントの動作を維持するかを説明してください。

この設問は、すべての関数が async 指定されているかではなく、ABI 境界における非同期セマンティクスをテストするものです。WASI.dev では 0.3 について、ネイティブ async の追加とインターフェースのリファクタリングを行うと説明されています。Component Model FAQ では、wasi:io が削除され、readiness は Component Model のプリミティブによって境界を越えて伝達されると記載されています。優れた回答では、インターフェース契約、ランタイムスケジューリング、ビジネス上の副作用が明確に分離されています。

面接官の評価ポイント

まず、操作を「単一結果」、「継続的なストリーム」、「キャンセル可能な長時間実行の呼び出し」のいずれかに分類します。次に、future、stream、または通常の戻り値を選択します。0.2 のポーリング可能リソースがなぜコンポーネント境界で「サンドイッチ問題」を引き起こしたのか、そして 0.3 でどのようにランタイムが待機とウェイクアップの伝播を管理できるようになるのかを説明します。

回答にはバックプレッシャー、リソース上限、エラー分類、移行計画も含める必要があります。「非同期のほうが速い」といった説明だけでは不十分です。面接官は、コンシューマーの処理速度、future の完了、キャンセル後の副作用、旧コンポーネント用のアダプター経路について確認したいと考えています。

回答前に明確にすべき質問

単一の値か継続的なストリームか

リモート呼び出しが1つのレスポンスを返すのか、それともログ、ファイルチャンク、イベントを発行するのかを確認します。1つの結果には future を、明示的な終了シグナルとキャパシティポリシーを持つインクリメンタルなシーケンスには stream を使用します。コンシューマー側で一時停止が可能な場合は、バッファ上限とプロデューサー側の挙動を定義します。

キャンセルによって副作用を防ぐ必要があるか

ライフサイクルの中でキャンセルの発生箇所を特定します(キュー滞留中、実行中、外部書き込み後)。コンポーネント境界はキャンセルリクエストを伝達できますが、確定済みの外部副作用をロールバックすることはできません。そのためには、冪等性キー、ステータス確認、または補償処理が必要です。

移行中に両方のバージョンを実行できるか

ホストが 0.2 と 0.3 を同時にロードできるか、旧コンポーネントを無変更で維持する必要があるか、ランタイムがアダプターをサポートしているかを確認します。これらの回答によって、ランタイムの並行運用、0.2 から 0.3 へのアダプター、あるいは一斉切り替えのいずれを採用するかが決まります。

30秒の回答フレームワーク

「セマンティクスに基づいてプリミティブを選択します。単発のリモート結果には future を、継続的なログには上限付きの stream を使用し、同期的な純粋計算は同期のまま維持します。WASI 0.3 では、各コンポーネントが pollable を保持する代わりに、Component Model のネイティブ ABI に readiness とウェイクアップが組み込まれます。ホストは依然として並行性、バッファ、デッドライン、キャンセルの制限を設定します。キャンセルはそれ以降の作業を停止しますが、外部への副作用には冪等なステータスを用います。移行期間中は 0.2 の world を維持し、アダプターと互換性マトリクスを介して移行します。」

ステップごとの詳細解説

ステップ 1: WIT world に非同期セマンティクスを組み込む

インターフェースは最小限に定義し、すべての関数を非同期にしてはいけません。以下の WIT 形式の疑似コードは、ストリームと単一結果を分離しています。

text
package acme:plugin@0.3.0;

interface logs {
  record chunk { bytes: list<u8>, end: bool }
  export next: func() -> future<chunk>
}

world worker {
  import logs;
  export run: async func(input: string) -> result<string, failure>;
}

future は将来得られる1つの値を示し、無制限のバックグラウンドタスクではありません。stream は時間をかけて配信されるシーケンス用です。インターフェースではエラー、ストリームの終了、キャンセル後の状態も定義する必要があります。そうしないと、異なる言語で生成されたバインディング間で不整合が生じる可能性があります。

ステップ 2: 0.2 から 0.3 への境界を説明する

WASI 0.2 では、I/O の readiness を pollableinput-streamoutput-stream で表現していました。コンポーネントリソースとして、それらの readiness は呼び出し側と呼び出され側の独立した非同期レイヤーを横断する必要があり、サンドイッチ問題を引き起こしていました。WASI 0.3 では async funcfuturestream プリミティブを使用するため、ランタイムがコンポーネント間で readiness を伝播させることができます。

これは古いバイナリの強制的な書き換えを意味するものではありません。公式 FAQ では、0.3 ランタイムがホスト境界で 0.2 のインポートを 0.3 のプリミティブにマッピングできると説明されています。旧 world を維持し、アダプターに変換を行わせ、コンポーネントを1つずつアップグレードします。

ステップ 3: ストリームにバックプレッシャーとメモリ境界を設定する

プロデューサーが無制限に書き込みを続けることはできません。ストリームごとに要素数、バイト数、待機時間の上限を設定します。コンシューマーの処理が遅れた場合は、読み取りを一時停止するか、明示的な過負荷結果を返します。1つの遅いコンシューマーがランタイムメモリを枯渇させないよう、中間バッファをテナントおよびコンポーネントごとに分離します。ファイルやログの場合は、境界を越えてオブジェクト全体をコピーするのではなく、チャンクや制御されたハンドルを渡します。

ステップ 4: future のキャンセルとエラーを設計する

future が完了する前に、操作 ID、デッドライン、キャンセルシグナルを保存します。キャンセルでは、未開始の作業を停止し、実行中のコードに対して協調的な停止シグナルを送信する必要があります。外部サービスがすでに書き込みを完了している可能性がある場合は、状態を UNKNOWN としてマークします。無闇に再試行するのではなく、冪等性キーで問い合わせるか補償処理を行います。タイムアウト、キャンセル、ビジネス上の拒否、依存関係の障害、プロトコル不整合を区別し、それぞれに適切な再試行ポリシーを適用します。

ステップ 5: スケジューリングをランタイム側に保持する

各コンポーネントが同じ I/O クラスに対して隠れたイベントループを個別に起動すべきではありません。ホストランタイムがウェイクアップ、並行性、公平性を管理し、コンポーネントはインターフェースを宣言して結果を返します。不要なタスク生成を避けるため、短い同期ホットパスは同期のまま維持します。Bytecode Alliance のロードマップでも、同期呼び出しに対する非同期インフラの適用はオーバーヘッドを増加させる可能性があると指摘されているため、同期アダプター、非同期境界、ビジネスロジックのベンチマークを個別に測定します。

ステップ 6: 段階的移行と検証マトリクスを構築する

WIT パッケージ、WASI バージョン、ランタイム、アダプター、コンポーネントのダイジェストをリリースマニフェストに記録します。0.3 ホスト上の 0.2 コンポーネント、旧ホスト上の 0.3 コンポーネント、ストリーム途中のキャンセル、タイムアウト後の不明な副作用、遅いコンシューマー、再接続などをテストします。新パスをシャドウ実行し、バージョンベースのロールアウト前に、完了率、エンドツーエンドのレイテンシ、ピーク時バッファリング量、キャンセルレイテンシ、再試行回数を比較します。

高評価の模範解答

すべてのインターフェースを async にするのではなく、各操作を分類します。単発のリモート結果には future、継続的なログには stream を割り当て、純粋なローカル計算は同期のまま維持します。WASI 0.3 の主要な変更点は、async func、future、stream を Component Model のネイティブ ABI に組み込み、0.2 の wasi:io ポーリング可能リソースを置き換えることで、readiness とウェイクアップがコンポーネント境界を越えられるようにした点です。

ランタイムでは、ストリームの要素数、バイト数、待機時間を制限します。コンシューマーの処理が遅延した場合は、生成を一時停止するか過負荷を返します。共有メモリを無制限に消費させることはできません。future は操作 ID とデッドラインを保持します。キャンセルは未開始の作業を停止し、外部への書き込みは UNKNOWN となり、無闇な再試行ではなく冪等性キーによる検索や補償処理によって解決します。

移行に関しては、0.2 の world を維持し、ホスト境界でアダプターを用いてマッピングした上で、シャドウ運用を行いコンポーネントのバージョンごとにロールアウトします。検証マトリクスには新旧コンポーネント、キャンセル、バックプレッシャー、エラー、再接続を含めます。ランタイムやツールチェーンが 0.3 をサポートしていない場合は、ABI に互換性があるかのように装うのではなく、world の切り替えを延期します。

よくある間違い

  • 間違い → future を汎用的なバックグラウンドタスクとして扱う → 失敗の理由 → future は1つの完了可能な結果であり、ライフサイクルとキャンセルには契約が必要であるため → 対策 → 操作 ID、デッドライン、キャンセル状態を追跡する。
  • 間違い → ストリームのスパイクに対して無制限のキューを使用する → 失敗の理由 → 遅いコンシューマーがメモリ圧迫を引き起こし、ランタイムの停止につながるため → 対策 → バイト数と要素数を制限し、バックプレッシャーを伝播させるか明確に拒絶する。
  • 間違い → 0.3 が外部への書き込みを自動的にロールバックすると主張する → 失敗の理由 → ABI レベルのキャンセルは分散トランザクションではないため → 対策 → 冪等性、ステータス確認、補償処理を使用する。
  • 間違い → 0.2 の world を即座に削除する → 失敗の理由 → 旧コンポーネントやツールチェーンが依然としてポーリング可能リソースに依存している可能性があるため → 対策 → アダプターと互換性マトリクスを介して移行する。
  • 間違い → すべてのコンポーネントに個別のイベントループを持たせる → 失敗の理由 → スケジューリング、キャンセル、公平性に不整合が生じるため → 対策 → ウェイクアップと並行性バジェットの管理をランタイム側に一元化する。

フォローアップの質問と回答

フォローアップ 1: プロデューサーがコンシューマーを追い越してしまった場合、データを破棄しますか、それともブロックしますか?

まずデータを分類します。ログであれば、破棄数をカウントしつつ重要度の低いレコードを破棄できます。金融イベントなどは通知なく消失させてはならないため、上限付きの外部キューに永続化し、バックプレッシャーを返します。いずれの場合も、集計スループットのみを見るのではなく、メトリクスにテナント ID、コンポーネント ID、ストリーム ID を記録します。

フォローアップ 2: タイムアウトした future が完了したかどうかをどのように把握しますか?

タイムアウトは呼び出し元が待機を停止したことを意味し、操作がロールバックされたことを意味するわけではありません。操作 ID を保持し、依存先に問い合わせを行います。ステータス API がない場合は結果を UNKNOWN としてマークし、補償処理またはオペレーターに委ねます。契約で冪等性が保証されており、ステータス確認で完了していないことが確認された場合にのみ再試行します。

フォローアップ 3: 0.2 コンポーネントが pollable を使用しています。0.3 ホストは同等の動作をどのように公開しますか?

アダプターがクローズ、エラー、キャンセルのセマンティクスを維持しながら、0.2 のリソースイベントを 0.3 の future または stream にマッピングします。まず単一コンポーネントのテストで readiness の順序と EOF を比較し、次にコンポジションのテストを行います。アダプターが挙動の一部しかエミュレートできない場合、デプロイゲートで不足しているインターフェースを必要とする world を拒絶する必要があります。

フォローアップ 4: 非同期への書き換えが有益であったことをどのように証明しますか?

測定値をアダプター時間、ランタイムウェイクアップ、ピーク時バッファリング、CPU、メモリ、エンドツーエンドのレイテンシ、ビジネス完了率に分割し、同期のベースラインと比較します。並行待機がほとんどない極めて短い呼び出しの場合、タスク管理や ABI 適合処理がメリットを打ち消してしまう可能性があるため、同期インターフェースを維持するか、呼び出しをバッチ化します。

公開情報ソース

関連する質問

関連面接ツール

コーディング問題にはスクリーンショットを使用

問題をキャプチャし、制約条件、解法アプローチ、コード、エッジケース、計算量の順に進めます。

ツールを見る