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

为何仅存于内存变量的Heroku Express API数据会持久化?

我使用Express搭建了一个简易API并部署到Heroku平台,完整实现代码如下:

const express = require("express");
const app = express();
const cors = require("cors");
app.use(express.json());
app.use(cors());
app.use(express.static("build"));
let notes = [
    {
        id: 1,
        content: "HTML is easy",
        date: "2022-05-30T17:30:31.098Z",
        important: true,
    },
    {
        id: 2,
        content: "Browser can execute only Javascript",
        date: "2022-05-30T18:39:34.091Z",
        important: false,
    },
    {
        id: 3,
        content: "GET and POST are the most important methods of HTTP protocol",
        date: "2022-05-30T19:20:14.298Z",
        important: true,
    },
];

const generateId = (arr) => {
    const maxId = arr.length < 0 ? 0 : Math.max(...arr.map((item) => item.id));
    return maxId + 1;
};

app.get("/", (req, res) => {
    res.send(`<h1>Hello World!</h1>`);
});

app.get("/api/notes", (req, res) => {
    res.json(notes);
});

app.get("/api/notes/:id", (req, res) => {
    const id = Number(req.params.id);
    const note = notes.find((note) => note.id === id);
    if (note) {
        res.json(note);
    } else {
        res.status(404).end();
    }
});

app.delete("/api/notes/:id", (req, res) => {
    const { id } = Number(req.params);
    notes = notes.filter((note) => note.id !== id);
    res.status(204).end();
});

app.post("/api/notes", (req, res) => {
    const body = req.body;
    if (!body.content) {
        return res.status(400).json({
            error: "Content Missing",
        });
    }
    const note = {
        content: body.content,
        important: body.important || false,
        date: new Date(),
        id: generateId(notes),
    };
    notes = notes.concat(note);
    res.json(note);
});

app.put("/api/notes/:id", (req, res) => {
    const newNote = req.body;
    notes = notes.map((note) => (note.id !== newNote.id ? note : newNote));
    res.json(newNote);
});

const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
});

如代码所示,为前端React应用提供服务的数据来自/api/notes接口,该接口直接返回notes数组作为响应内容。
将项目部署到Heroku后,新增笔记、修改笔记重要性标记的功能均可正常运行,但出现了超出预期的现象:刷新页面、甚至使用其他设备访问该应用时,之前操作新增/修改的数据依然存在,表现出持久化特性。但这份数据仅存储在内存中的notes变量里,既没有接入数据库,也没有做其他持久化配置。


现象原理解释

这不是Heroku平台自动修改变量或做了持久化配置,本质是Heroku容器运行机制造成的持久化假象,核心逻辑如下:

  • 代码中的notes变量存储在Node.js进程的运行内存中,只要承载服务的Node进程不退出、不重启,内存中的数据就会持续保留,所有路由到该进程的请求都可以读写这份数据。
  • Heroku以dyno(轻量容器)为单位运行应用,应用首次部署完成后,默认分配的单个dyno会在无异常的情况下持续运行Node服务进程。这段时间内不管是同设备刷新还是跨设备访问,请求都会被路由到这个正在运行的dyno上,自然能读到之前修改过的内存数据。
  • 这种内存级的数据保留完全不可靠,触发以下任意场景时,所有修改过的数据都会被清空,恢复为代码中写死的初始3条笔记:
    • 免费档位dyno在半小时无访问后会自动休眠,下次唤醒时会重新拉起Node进程,内存数据重置
    • 重新推送代码部署应用时,旧dyno会被直接销毁,新启动的dyno会运行全新的Node进程
    • Heroku平台例行维护、dyno崩溃自动重启时,内存数据会被全部清除
    • 如果将应用扩容到多个dyno,负载均衡会把请求分发到不同dyno实例,每个实例持有独立的notes内存变量,还会出现不同请求返回数据不一致的问题

Heroku dyno的本地文件系统和内存都是临时的,所有运行时产生的修改都会在dyno生命周期结束时永久丢失,平台不会自动持久化任何用户数据。要实现真正可靠的数据持久化,必须外接独立的数据库服务。

内容的提问来源于stack exchange,提问作者Rammah_N

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 09:33:18