如何防止scotty因大量文本输出而占用内存?

时间:2015-07-03 06:24:48

标签: haskell scotty

我有一个Scotty / WAI应用程序,其中一个端点发送一个从元素列表构建的大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's documentation,输出应该是延迟构建并通过线路发送而无需在内存中构建整个text / bytestring。然而,这不是我观察到的行为:当我调用此端点时,服务器开始占用内存,我推断它正在构建整个字符串,然后一次性发送它。

我错过了什么吗?

编辑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,但这会导致进程崩溃。在整个转换过程中会保留一些内存...

1 个答案:

答案 0 :(得分:2)

查看Web.Scotty.Trans中的"stream"函数。它的目的是对将数据刷新到套接字之前生成的数据大小进行更细粒度的控制。

您使用StreamingBody参数调用它,该参数实际上是类型的函数(Builder - &gt; IO()) - &gt; IO() - &gt; IO()。

所以你写了一个函数:

doMyStreaming send flush =
...

您将数据分段发送和刷新,然后使用doMyStreaming作为参数调用stream函数,而不是调用&#34; raw&#34;。