4

Scotty/WAI アプリケーションがあり、エンドポイントの 1 つが、Text要素のリストから構築された大量の出力を送信します。関連するコードは次のとおりです。

  import Data.Text.Lazy as L
  import Data.Text.Lazy.Encoding as E

  class (Show csv) => ToCSV csv where
    toCSV :: csv -> L.Text
    toCSV = pack . show

  instance (ToCSV c) => ToCSV [c] where
    toCSV []     = empty
    toCSV (c:cs) = toCSV c <> "\n" <> toCSV cs


  get "/api/transactions" $ accept "text/csv" $ do
    purp <- selectPurpose
    txs <- allEntries <$> inWeb (listTransactions purp)
    setHeader "Content-Type" "text/csv"
    raw $ E.encodeUtf8 $ toCSV txs

Scotty のドキュメントを理解しているので、メモリ内にテキスト/バイト文字列全体を構築する必要なく、出力を遅延構築してネットワーク経由で送信する必要があります。ただし、これは私が観察した動作ではありません。このエンドポイントを呼び出すと、サーバーはメモリを消費し始め、一度に送信する前に文字列全体を構築していると推測します。

何か不足していますか?

編集1

doStream結果のBSのチャンクを1つずつ送信することになっている関数を作成しました。

doStream :: Text -> W.StreamingBody   
doStream t build flush = do
  let bs = E.encodeUtf8 t
  mapM_ (\ chunk -> build (B.fromByteString chunk)) (BS.toChunks bs)
  flush

しかし実際には、出力全体をメモリに構築します...

編集2

実際、この方法でのストリーミングは問題なく動作します。ただし、サーバー プロセスは依然として大量のメモリを消費します。これは実際には、各チャンクを送信する際にガベージ コレクション可能になる可能性があります。メモリ使用量をより深く分析して、この消費がどこから来ているかを確認します。

編集3

ヒープを 2GB に制限しようとしましたが、これによりプロセスがクラッシュします。変換プロセス全体で一部のメモリが保持されます...

4

1 に答える 1

2

Web.Scotty.Transの「ストリーム」関数を見てください。ソケットにフラッシュされる前に生成されるデータのサイズをより細かく制御する目的で作成されます。

StreamingBody 引数を使用して呼び出します。これは、実際には (Builder -> IO ()) -> IO () -> IO () 型の関数です。

したがって、関数を記述します。

doMyStreaming send flush =
...

データを分割して送信およびフラッシュし、「生」への呼び出しの代わりに doMyStreaming を引数としてストリーム関数を呼び出します。

于 2015-07-03T08:42:53.363 に答える