仅在内存中运行 PostgreSQL

Posted

技术标签:

【中文标题】仅在内存中运行 PostgreSQL【英文标题】:Running PostgreSQL in memory only 【发布时间】:2011-12-13 22:25:42 【问题描述】:

我想为我编写的每个单元测试运行一个仅在内存中运行的小型 PostgreSQL 数据库。例如:

@Before
void setUp() 
    String port = runPostgresOnRandomPort();
    connectTo("postgres://localhost:"+port+"/in_memory_db");
    // ...

理想情况下,我会将一个 postgres 可执行文件签入版本控制,单元测试将使用它。

类似于HSQL,但用于postgres。我该怎么做?

我能得到这样的 Postgres 版本吗?如何指示它不使用磁盘?

【问题讨论】:

【参考方案1】:

如果您使用的是 java,有一个我见过有效使用的库,它提供了一个内存中的“嵌入式”postgres 环境,主要用于单元测试。

https://github.com/opentable/otj-pg-embedded

如果您来此搜索结果寻找答案,这也许可以解决您的用例。

【讨论】:

【参考方案2】:

或者您可以在 ramfs / tempfs 中创建 TABLESPACE 并在那里创建所有对象。 最近有人向我指出一篇关于在 Linux 上执行此操作的文章。原始链接已失效。但它已存档(由 Arsinclair 提供):

https://web.archive.org/web/20160319031016/http://magazine.redhat.com/2007/12/12/tip-from-an-rhce-memory-storage-on-postgresql/

警告

这可能会危及您的整个数据库集群的完整性。Read the added warning in the manual. 所以这只是消耗性数据的一种选择。

对于单元测试,它应该可以正常工作。如果您在同一台机器上运行其他数据库,请务必使用单独的数据库集群(有自己的端口)以确保安全。

【讨论】:

我真的认为这是个坏建议。不要这样做。相反,initdb 在 tempfs 或 ramdisk 中创建一个新的 postgres 实例。不要在 tempfs 等中使用表空间,它很脆弱且毫无意义。你最好使用一个普通的表空间并创建UNLOGGED 表——它会执行类似的操作。而且它不会解决 WAL 性能和 fsync 因素,除非您采取可能危及整个数据库完整性的操作(请参阅***.com/q/9407442/398670)。不要这样做。 答案中的链接已失效。但已存档:web.archive.org/web/20160319031016/http://magazine.redhat.com/…【参考方案3】:

如果你可以使用docker,你可以在内存中挂载postgresql数据目录进行测试

docker run --tmpfs=/data -e PGDATA=/data postgres

【讨论】:

【参考方案4】:

如果您使用的是 NodeJS,您可以使用 pg-mem(免责声明:我是作者)来模拟 postgres db 的最常见功能。

您将拥有一个完整的内存中、隔离的、与平台无关的数据库来复制 PG 行为(甚至runs in browsers)。

我写了一篇文章来展示如何将它用于您的单元测试here。

【讨论】:

看起来棒极了!我正在寻找一些有用的工具。我缺少 CURRENT_TIMESTAMP、SUM()、枚举支持,但其余的看起来不错 @RodrigoManguinho 你是什么意思?你能打开一个提供更多上下文的问题吗?就像您遇到错误的方式,安装了哪个版本的 pg-mem 和 Typeorm,...(它可以在我的机器上运行) 嗨奥利弗。我让它工作的唯一方法是手动运行脚本来创建我的表。如果我使用配置选项进行同步它不起作用。尝试对 ormconfig 选项和连接实例使用同步。这两种情况都给我错误。 @Olivier 只是为了给你更多细节。如果我运行 connection.synchronize() 我收到此错误: QueryFailedError: column "columns.table_name" 不存在 但是如果我运行 connection.query('create table ...') 它可以工作。该表非常简单,只有两个字段:id 和 name @RodrigoManguinho 好的,这是 typeorm@0.2.30 出现的问题(我只测试了 typeorm@0.2.29)...我为此创建了一个问题 github.com/oguimbal/pg-mem/issues/53【参考方案5】:

现在有来自俄罗斯搜索公司 Yandex 的 PostgreSQL 内存版本:https://github.com/yandex-qatools/postgresql-embedded

它基于Flapdoodle OSS的嵌入过程。

使用示例(来自github页面):

// starting Postgres
final EmbeddedPostgres postgres = new EmbeddedPostgres(V9_6);
// predefined data directory
// final EmbeddedPostgres postgres = new EmbeddedPostgres(V9_6, "/path/to/predefined/data/directory");
final String url = postgres.start("localhost", 5432, "dbName", "userName", "password");

// connecting to a running Postgres and feeding up the database
final Connection conn = DriverManager.getConnection(url);
conn.createStatement().execute("CREATE TABLE films (code char(5));");

我用了一段时间。效果很好。

已更新:此项目不再积极维护

Please be adviced that the main maintainer of this project has successfuly 
migrated to the use of Test Containers project. This is the best possible 
alternative nowadays.

【讨论】:

如果您使用多线程、嵌入 JVM 或 Mono 运行时、fork() 您自己的子进程或类似的任何东西,那一定会以各种新的和令人兴奋的方式爆炸。 编辑:它并不是真正嵌入的,它只是一个包装器。【参考方案6】:

现在可以通过 OpenTable 中的嵌入式 PostgreSQL 组件在您的 JUnit 测试中运行 PostgreSQL 的内存实例:https://github.com/opentable/otj-pg-embedded。

通过将依赖项添加到 otj-pg-embedded 库 (https://mvnrepository.com/artifact/com.opentable.components/otj-pg-embedded),您可以在 @Before 和 @Afer 挂钩中启动和停止您自己的 PostgreSQL 实例:

EmbeddedPostgres pg = EmbeddedPostgres.start();

他们甚至提供了一个 JUnit 规则来自动让 JUnit 为您启动和停止您的 PostgreSQL 数据库服务器:

@Rule
public SingleInstancePostgresRule pg = EmbeddedPostgresRules.singleInstance();

【讨论】:

六个月后您对这个包的体验如何?运行良好,还是充满了错误? @Rubms 你迁移到 JUnit5 了吗?您如何使用@ExtendWith 替换@Rule?只需在@BeforeAll 中使用.start() 我还没有迁移到 JUnit5,所以我还不能回答你的问题。对不起。 这很好用。谢谢。如果您愿意,可以使用以下方法在您的 spring 配置中创建数据源:DataSource embeddedPostgresDS = EmbeddedPostgres.builder().start().getPostgresDatabase();【参考方案7】:

您可以使用 TestContainers 启动 PosgreSQL docker 容器进行测试: http://testcontainers.viewdocs.io/testcontainers-java/usage/database_containers/

TestContainers 提供 JUnit @Rule/@ClassRule:此模式在您的测试之前在容器内启动一个数据库,然后将其拆除。

例子:

public class SimplePostgreSQLTest 

    @Rule
    public PostgreSQLContainer postgres = new PostgreSQLContainer();

    @Test
    public void testSimple() throws SQLException 
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setJdbcUrl(postgres.getJdbcUrl());
        hikariConfig.setUsername(postgres.getUsername());
        hikariConfig.setPassword(postgres.getPassword());

        HikariDataSource ds = new HikariDataSource(hikariConfig);
        Statement statement = ds.getConnection().createStatement();
        statement.execute("SELECT 1");
        ResultSet resultSet = statement.getResultSet();

        resultSet.next();
        int resultSetInt = resultSet.getInt(1);
        assertEquals("A basic SELECT query succeeds", 1, resultSetInt);
    

【讨论】:

【参考方案8】:

(将我的答案从Using in-memory PostgreSQL 移出并概括):

你不能运行 Pg in-process, in-memory

​​>

我不知道如何运行内存中的 Postgres 数据库进行测试。有可能吗?

不,这是不可能的。 PostgreSQL 用 C 实现并编译为平台代码。与 H2 或 Derby 不同,您不能只加载 jar 并将其作为一次性内存数据库启动。

与同样用 C 语言编写并编译为平台代码的 SQLite 不同,PostgreSQL 也不能在进程中加载​​。它需要多个进程(每个连接一个),因为它是多处理架构,而不是多线程架构。多处理要求意味着您必须将 postmaster 作为独立进程启动。

改为:预配置连接

我建议简单地编写测试以期望特定的主机名/用户名/密码能够正常工作,并让测试工具CREATE DATABASE 成为一次性数据库,然后在运行结束时使用DROP DATABASE。从属性文件中获取数据库连接详细信息、构建目标属性、环境变量等。

只要您提供给单元测试的用户不是超级用户,只有CREATEDB 的用户,使用现有的 PostgreSQL 实例是安全的。权利。在最坏的情况下,您会在其他数据库中产生性能问题。出于这个原因,我更喜欢运行完全隔离的 PostgreSQL 安装进行测试。

改为:启动一次性 PostgreSQL 实例进行测试

或者,如果您真的很感兴趣,您可以have your test harness locate the initdb and postgres binaries, run initdb to create a database, modify pg_hba.conf to trust, run postgres to start it on a random port, create a user, create a DB, and run the tests。您甚至可以将多个架构的 PostgreSQL 二进制文件捆绑在一个 jar 中,然后在运行测试之前将当前架构的二进制文件解压到一个临时目录。

我个人认为这是一个应该避免的主要痛苦;配置测试数据库要容易得多。但是,随着postgresql.confinclude_dir 支持的出现,它变得更容易了;现在你可以只追加一行,然后为其余的编写一个生成的配置文件。

使用 PostgreSQL 进行更快的测试

有关如何安全地提高 PostgreSQL 的性能以用于测试目的的更多信息,请参阅我之前在此主题上写的详细答案:Optimise PostgreSQL for fast testing

H2 的 PostgreSQL 方言不是真正的替代品

有些人转而使用 PostgreSQL 方言模式下的 H2 数据库来运行测试。我认为这几乎与 Rails 人员使用 SQLite 进行测试和使用 PostgreSQL 进行生产部署一样糟糕。

H2 支持一些 PostgreSQL 扩展并模拟 PostgreSQL 方言。然而,它只是 - 一个仿真。 You'll find areas where H2 accepts a query but PostgreSQL doesn't, where behaviour differs, etc。在撰写本文时,您还会发现 PostgreSQL 支持做一些 H2 不能做的事情的很多地方——比如窗口函数。

如果您了解这种方法的局限性并且您的数据库访问很简单,那么 H2 可能没问题。但在这种情况下,您可能更适合使用抽象数据库的 ORM,因为您无论如何都不会使用它的有趣功能 - 在这种情况下,您不必再关心数据库的兼容性。

表空间不是答案!

不要使用表空间来创建“内存”数据库。它不仅没有必要,因为它无论如何都不会显着提高性能,而且它也是一种很好的方式来中断对同一 PostgreSQL 安装中您可能关心的任何其他内容的访问。 The 9.4 documentation now contains the following warning:

警告

即使位于主 PostgreSQL 数据目录之外, 表空间是数据库集群的一个组成部分,不能 被视为数据文件的自主集合。他们依赖 在主数据目录中包含的元数据上,因此不能 附加到不同的数据库集群或单独备份。 同样,如果您丢失了一个表空间(文件删除、磁盘故障、 等),数据库集群可能变得不可读或无法启动。 将表空间放置在像 ramdisk 这样的临时文件系统上存在风险 整个集群的可靠性。

因为我注意到有太多人这样做并遇到了麻烦。

(如果你已经这样做了,你可以 mkdir 丢失的表空间目录让 PostgreSQL 重新启动,然后 DROP 丢失的数据库、表等。最好不要这样做。)

【讨论】:

我不清楚这里提供的警告。如果我想快速运行单元测试,为什么会涉及到集群?这不应该只在我本地的一次性 PG 实例上吗?如果集群(一个)已损坏,为什么这很重要,我本来打算将其删除。 @GatesVP PostgreSQL 以一种有点奇怪的方式使用术语“集群”来指代 PostgreSQL 实例(数据目录、数据库集合、postmaster 等)。所以它不是“计算集群”意义上的“集群”。是的,这很烦人,我希望看到该术语发生变化。如果它是一次性的,那么当然没关系,但是人们经常尝试在 PostgreSQL 安装上拥有一个一次性的内存 tablespace ,其中包含他们原本关心的数据。这是个问题。 好的,这既是 “我的想法”“非常可怕”,RAMDrive 解决方案显然只属于本地数据库,不包含有用的数据。但是为什么有人要对不是他们自己机器的机器运行单元测试呢?根据您的回答,对于仅在本地计算机上运行的 PGSQL 的实际单元测试实例,Tablespaces + RamDisk 听起来完全合法。 @GatesVP 有些人将他们关心的东西保存在本地机器上——这很好,但是针对同一个数据库安装运行单元测试就有点傻了。人虽然很傻。他们中的一些人也没有保留适当的备份。哀号接踵而至。 无论如何,如果你要选择 ramdisk 选项,你真的希望 ramdisk 上也有 WAL,所以你不妨initdb 在那里安装一个全新的 Pg。但实际上,经过调整以在普通存储上进行快速测试(fsync=off 和其他数据持久性/安全功能关闭)的 Pg 与在 ramdisk 上运行(至少在 Linux 上)几乎没有区别。【参考方案9】:

您还可以使用 PostgreSQL 配置设置(例如问题和接受的答案 here 中详细说明的设置)来实现性能,而不必求助于内存数据库。

【讨论】:

OP 的主要问题是在内存中启动 Postgres 实例,不是为了性能,而是为了在开发和 CI 环境中简单地引导单元测试。【参考方案10】:

这在 Postgres 中是不可能的。它不提供像 HSQLDB 或 mysql 这样的进程内/内存引擎。

如果您想创建一个独立的环境,您可以将 Postgres 二进制文件放入 SVN(但它不仅仅是一个可执行文件)。

您需要先运行initdb 来设置您的测试数据库,然后您才能对此进行任何操作。这可以通过批处理文件或使用 Runtime.exec() 来完成。但请注意,initdb 不是很快的东西。您绝对不想为每个测试都运行它。不过,您可能会在您的测试套件之前运行它。

尽管可以这样做,但我建议您安装专用的 Postgres,您只需在运行测试之前重新创建测试数据库即可。

您可以使用模板数据库重新创建测试数据库,这使得创建它的速度非常快(比每次测试运行运行 initdb 快很多

【讨论】:

看起来下面 Erwin 的第二个答案应该被标记为正确答案 @vfclists 实际上,ramdisk 上的表空间是一个非常糟糕的主意。不要那样做。见postgresql.org/docs/devel/static/manage-ag-tablespaces.html、***.com/q/9407442/398670 @CraigRinger:澄清这个特殊问题:混用有价值的数据是个坏主意(感谢您的警告)。对于使用专用数据库集群进行单元测试,ramdisk 就可以了。 随着 docker-use 的普及,一些人已经成功地使用了像 testcontainers 这样的工具,它本质上让你的测试启动一个一次性的、dockerized、postgres-instance。见github.com/testcontainers/testcontainers-java/blob/master/… @ekcrisp。这不是 Postgres 的真正嵌入式版本。它只是一个包装库,可以更轻松地启动 Postgres 实例(在单独的进程中)。 Postgres 仍将在 Java 应用程序“外部”运行,而不是“嵌入”在运行 JVM 的同一进程中

以上是关于仅在内存中运行 PostgreSQL的主要内容,如果未能解决你的问题,请参考以下文章

是否可以仅在内存中以编程方式编译 java 源代码?

ResultSet 是将所有数据加载到内存中还是仅在请求时加载?

应用程序仅在 iPhone 8 iOS 14.4.1 上因内存不足而崩溃

为啥有时会立即释放内存,而有时仅在自动释放池耗尽时才释放内存?

c++基础——动态内存

仅在一定限度内释放 2D 指针数组的内存