You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 06:17:01