如何从compojure API流式传输一个大型CSV响应,从而使整个响应不会同时保存在内存中?

如何从compojure API流式传输一个大型CSV响应,从而使整个响应不会同时保存在内存中?,csv,clojure,out-of-memory,large-files,compojure,Csv,Clojure,Out Of Memory,Large Files,Compojure,我对使用compojure还不熟悉,但到目前为止我一直很喜欢使用它。我是 当前在我的一个API端点中遇到问题,该端点正在生成 来自数据库的大型CSV文件,然后将其作为响应主体传递 我似乎遇到的问题是,整个CSV文件都被保留了 内存中,这将导致API内存不足错误。问题是什么 处理和生成此文件的最佳方法,理想情况下是作为gzip文件?可能吗 流式传输响应以便一次返回几千行?什么时候 我为相同的数据返回一个JSON响应体,返回没有问题 这个 以下是我用来返回此信息的当前代码: (defn comple

我对使用compojure还不熟悉,但到目前为止我一直很喜欢使用它。我是 当前在我的一个API端点中遇到问题,该端点正在生成 来自数据库的大型CSV文件,然后将其作为响应主体传递

我似乎遇到的问题是,整个CSV文件都被保留了 内存中,这将导致API内存不足错误。问题是什么 处理和生成此文件的最佳方法,理想情况下是作为gzip文件?可能吗 流式传输响应以便一次返回几千行?什么时候 我为相同的数据返回一个JSON响应体,返回没有问题 这个

以下是我用来返回此信息的当前代码:

(defn complete
  "Returns metrics for each completed benchmark instance"
  [db-client response-format]
  (let [benchmarks  (completed-benchmark-metrics {} db-client)]
    (case response-format
      :json  (json-grouped-output field-mappings benchmarks)
      :csv   (csv-output benchmarks))))

(defn csv-output [data-seq]
  (let [header (map name (keys (first data-seq)))
        out    (java.io.StringWriter.)
        write  #(csv/write-csv out (list %))]
    (write header)
    (dorun (map (comp write vals) data-seq))
    (.toString out)))
data seq
是从数据库返回的结果,我认为这是一个 惰性序列。我正在使用yesql执行数据库调用

以下是此API端点的compojure资源:

(defresource results-complete [db]
  :available-media-types  ["application/json" "text/csv"]
  :allowed-methods        [:get]
  :handle-ok              (fn [request]
                            (let [response-format (keyword (get-in request [:request :params :format] :json))
                                  disposition     (str "attachment; filename=\"nucleotides_benchmark_metrics." (name response-format) "\"")
                                  response        {:headers {"Content-Type" (content-types response-format)
                                                             "Content-Disposition" disposition}
                                                   :body    (results/complete db response-format)}]
                              (ring-response response))))

csv输出函数完全实现数据集并将其转换为字符串。要延迟地流式传输数据,您需要返回除具体数据类型(如字符串)之外的其他内容。建议ring支持返回一条流,这可以通过Jetty懒洋洋地实现。可能会很有用。

我也在努力处理大的csv文件流。我的解决方案是使用流式传输数据序列的每一行到客户端,然后关闭通道。我的解决方案如下所示:

[org.httpkit.server :refer :all]

(fn handler [req]
    (with-channel req channel (let [header "your$header"
                                    data-seq ["your$seq-data"]]
                                    (doseq [line (cons header data-seq)]
                                       (send! channel
                                              {:status  200
                                              :headers {"Content-Type" "text/csv"}
                                              :body    (str line "\n")}
                                              false))
                                    (close channel))))

多亏了此线程中提供的所有建议,我能够使用
管道输入流创建解决方案

(defn csv-output [data-seq]
  (let [headers     (map name (keys (first data-seq)))
        rows        (map vals data-seq)
        stream-csv  (fn [out] (csv/write-csv out (cons headers rows))
                              (.flush out))]
    (piped-input-stream #(stream-csv (io/make-writer % {})))))
这与我的解决方案不同,因为它不使用
dorun
实现序列,也不创建大型
String
对象。而是异步写入
PipedInputStream
连接:

从将输出流作为其输出流的函数创建输入流 论点该函数将在单独的线程中执行。小溪 将在功能完成后自动关闭


你为什么在那里有
dorun
?它的全部目的是不懒惰。我很确定
.toString
是非惰性的,我也不确定
&
VAL
@AlanThompson,据我所知,
dorun
不保留序列头,因此可以用于诸如写入字符串缓冲区之类的副作用。是的,但它会导致立即对序列进行评估,不懒惰。本页讨论使用csv文件时的懒惰。以防万一:谢谢所有的建议,我能够根据链接的文章和文档创建一个解决方案。谢谢你的建议。我不熟悉httpkit服务器,最后我用管道输入流回答了这个问题,因为这符合我现有的clojure API堆栈,其中包括liberator和compojure。我很可能会在未来的渠道更大的数据。