基于Plumber与Future Promises的R应用内存累积问题排查
问题
我正在开发一款通过Plumber API调用R的应用,每个API都嵌入在采用multisession计划的future promise中,API结构如下:
#* API1 #* @get species/<scientific_name>/API1 #* @param scientific_name:string Scientific Name #* @serializer unboxedJSON #* @tag test function(scientific_name) { Prom<-future({ result <- CODE_OF_THE_PROMISE gc() return(list(object_to_return=result)) }, gc=T, seed=T) gc() return(Prom) }
尽管在返回结果和promise前都调用了gc(),但我发现存在内存累积现象(每次API调用会占用几十到几百MB内存)。有时部分内存会在后续释放,但并非总是如此。我原本期望API返回结果后,所有对象都会被移除,内存完全释放。
可复现示例
主代码:
### Set the asynchronous coding library(promises) ; library(future) future::plan("multisession") ### Plumber app library(plumber) ### Other libraries library(terra) ### Start app pr <- pr("API.R") pr %>% pr_run()
API.R文件代码:
#* API1 #* @get species/<scientific_name>/API1 #* @param scientific_name:string Scientific Name #* @serializer unboxedJSON #* @tag test function(scientific_name) { Prom<-future({ raster_test<-rast(xmin=-180, xmax=180, ymin=-90, ymax=90, resolution=0.1, crs="+init=epsg:4326", vals=rnorm(259200, 1, 10)) result <- sum(as.vector(raster_test)) gc() return(list(object_to_return=result)) }, gc=T, seed=T) gc() return(Prom) }
运行API1后,我的R会话在调用前占用850MB内存,调用后变为930MB,且后续数分钟内存未释放。
会话信息
R version 4.2.3 (2023-03-15 ucrt) Platform: x86_64-w64-mingw32/x64 (64-bit) Running under: Windows 10 x64 (build 22621) Matrix products: default locale: [1] LC_COLLATE=French_France.utf8 LC_CTYPE=French_France.utf8 LC_MONETARY=French_France.utf8 LC_NUMERIC=C LC_TIME=French_France.utf8 attached base packages: [1] stats graphics grDevices utils datasets methods base other attached packages: [1] terra_1.7-3 plumber_1.2.1 future_1.32.0 promises_1.2.0.1 loaded via a namespace (and not attached): [1] Rcpp_1.0.10 codetools_0.2-19 listenv_0.9.0 digest_0.6.31 later_1.3.0 parallelly_1.34.0 R6_2.5.1 lifecycle_1.0.3 [9] jsonlite_1.8.4 magrittr_2.0.3 stringi_1.7.12 rlang_1.1.0 cli_3.6.0 swagger_3.33.1 rstudioapi_0.14 ellipsis_0.3.2 [17] webutils_1.1 tools_4.2.3 httpuv_1.6.8 parallel_4.2.3 compiler_4.2.3 globals_0.16.2
内存累积的原因
- multisession子进程复用机制:
future::plan("multisession")会创建独立的R子进程执行任务,这些子进程会被复用处理后续请求,而非任务完成后立即销毁。future内部的gc()仅作用于子进程,但子进程中未彻底回收的内存会持续累积。 - terra底层资源未完全释放:
terra的栅格对象依赖C++底层资源,部分场景下R的gc()无法彻底回收这些非R内存,导致内存残留。 - promise对象的引用延迟释放:主进程返回的
Prom对象会被Plumber暂时持有直到响应发送完成,这段时间内相关内存无法被回收;若后续请求复用子进程,残留内存不会被自动清理。
解决办法
1. 控制multisession子进程生命周期
- 在future调用中启用
cleanup = TRUE(需future版本支持),强制任务完成后清理子进程资源:Prom<-future({ raster_test<-rast(...) result <- sum(as.vector(raster_test)) rm(raster_test) gc(full = TRUE) return(list(object_to_return=result)) }, gc=T, seed=T, cleanup = TRUE) - 限制子进程数量,例如
future::plan(multisession, workers = 1),减少内存累积的规模。
2. 优化terra对象内存处理
- 显式删除大对象后调用
gc(full = TRUE),强制回收所有内存(包括底层资源):Prom<-future({ raster_test<-rast(...) result <- sum(as.vector(raster_test)) rm(raster_test) # 先删除栅格对象 gc(full = TRUE) # 强制完全垃圾回收 return(list(object_to_return=result)) }, gc=T, seed=T) - 在future内部添加
terra::tmpFiles(remove=TRUE),清理terra生成的临时文件,避免磁盘缓存占用内存。
3. 调整Plumber配置
- 添加post-run钩子,在每个请求处理完成后触发主进程的完全垃圾回收:
pr <- pr("API.R") %>% pr_hook("post-run", function(req, res) { gc(full = TRUE) }) pr %>% pr_run()
4. 升级相关包版本
升级terra、future、plumber到最新版本,很多内存泄漏问题已在新版本中修复,比如terra 1.7+优化了栅格内存管理,future 1.33+改进了子进程内存回收逻辑。
内容的提问来源于stack exchange,提问作者Victor Cz
相关产品推荐
相关产品推荐

