web-dev-qa-db-ja.com

mapとmapAsyncの違い

誰も私にマップとmapAsync w.r.t AKKAストリームの違いを教えてもらえますか? ドキュメント内 と言われています

外部の非ストリームベースのサービスを含むストリーム変換および副作用は、mapAsyncまたはmapAsyncUnorderedで実行できます

なぜここで単純にマッピングできないのですか? Flow、Source、Sinkはすべて本質的にモナディックであり、したがってこれらの性質の遅延でマップは正常に機能するはずだと思います。

19
Anand

署名

違いは signatures で最もよく強調されています。Flow.mapT型を返す関数を受け取り、Flow.mapAsyncFuture[T]型を返す関数を受け取ります。

実際の例

例として、ユーザーIDに基づいてユーザーのフルネームをデータベースに照会する関数があるとします。

type UserID   = String
type FullName = String

val databaseLookup : UserID => FullName = ???  //implementation unimportant

Source値のakkaストリームUserIDを指定すると、ストリーム内でFlow.mapを使用してデータベースをクエリし、フルネームをコンソールに出力できます。

val userIDSource : Source[UserID, _] = ???

val stream = 
  userIDSource.via(Flow[UserID].map(databaseLookup))
              .to(Sink.foreach[FullName](println))
              .run()

このアプローチの1つの制限は、このストリームが一度に1 dbクエリしか作成しないことです。このシリアルクエリは「ボトルネック」となり、ストリームの最大スループットを妨げる可能性があります。

Futureを使用して同時クエリを実行することで、パフォーマンスを向上させることができます。

def concurrentDBLookup(userID : UserID) : Future[FullName] = 
  Future { databaseLookup(userID) }

val concurrentStream = 
  userIDSource.via(Flow[UserID].map(concurrentDBLookup))
              .to(Sink.foreach[Future[FullName]](_ foreach println))
              .run()

この単純化された補遺の問題は、バックプレッシャーを効果的に排除したことです。

Sinkは、Futureを導入し、foreach printlnを追加しているだけです。これは、データベースクエリに比べて比較的高速です。ストリームは継続的にデマンドをソースに伝播し、Flow.mapの内部により多くのFutureを生成します。したがって、同時に実行されるdatabaseLookupの数に制限はありません。制限のない並列クエリにより、最終的にデータベースが過負荷になる可能性があります。

Flow.mapAsyncが助けになりました。同時ルックアップの数を制限しながら、同時にdbアクセスが可能です。

val maxLookupCount = 10

val maxLookupConcurrentStream = 
  userIDSource.via(Flow[UserID].mapAsync(maxLookupCount)(concurrentDBLookup))
              .to(Sink.foreach[FullName](println))
              .run()

また、Sink.foreachが単純になり、Future[FullName]ではなく、FullNameだけが取り込まれるようになりました。

順序付けされていない非同期マップ

UserIDからFullNamesへの順番を維持する必要がない場合は、Flow.mapAsyncUnorderedを使用できます。例:すべての名前をコンソールに出力する必要があるだけで、それらが出力された順序は関係ありません。