仅在内存中运行 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 提供):
警告
这可能会危及您的整个数据库集群的完整性。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.conf
中include_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的主要内容,如果未能解决你的问题,请参考以下文章
ResultSet 是将所有数据加载到内存中还是仅在请求时加载?
应用程序仅在 iPhone 8 iOS 14.4.1 上因内存不足而崩溃