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