4

Text我有一个 Scotty/WAI 应用程序,其中一个端点发送从元素列表构建的大量输出。以下是相关代码:

  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 块:

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 作为参数调用流函数,而不是调用“raw”。

于 2015-07-03T08:42:53.363 回答