仅在内存中运行PostgreSQL


104

对于我编写的每个单元测试,我都希望运行一个仅在内存中运行的小型PostgreSQL数据库。例如:

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

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

类似于HSQL,但适用于postgres。我怎样才能做到这一点?

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

Answers:


49

对于Postgres,这是不可能的。它不提供像HSQLDB或MySQL这样的进程内/内存引擎。

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

您将需要运行initdb来设置测试数据库,然后才能执行任何操作。可以从批处理文件或使用Runtime.exec()完成此操作。但是请注意,initdb并不是很快。您绝对不希望为每个测试都运行该程序。不过,在测试套件之前,您可能会逃避运行此操作。

但是,尽管可以做到这一点,但我建议安装专用的Postgres安装程序,您可以在运行测试之前简单地重新创建测试数据库。

通过使用使创建它相当快(一个模板数据库您可以重新创建测试数据库很多快于每个测试运行运行initdb)


8
看来下面欧文(Erwin)的第二个答案应标记为正确答案
vfclists 2012年

3
@vfclists实际上,ramdisk上的表空间是一个非常糟糕的主意。不要那样做 见postgresql.org/docs/devel/static/manage-ag-tablespaces.htmlstackoverflow.com/q/9407442/398670
克雷格·林格

1
@CraigRinger:需要澄清这个特定问题:将有价值的数据混合在一起是一个坏主意(感谢警告)。对于使用专用数据库集群的单元测试,可以使用虚拟磁盘。
Erwin Brandstetter 2015年

1
随着docker-use的使用变得司空见惯,有些人已经成功使用了诸如之类的工具testcontainers,该工具从根本上让您的测试启动公司成为了一个废弃的,docker化的postgres-instance。见github.com/testcontainers/testcontainers-java/blob/master/...
汉斯韦斯特比克

1
@ekcrisp。那不是Postgres的真正嵌入式版本。它只是一个包装库,使启动Postgres实例(在单独的过程中)更加容易。Postgres仍将在Java应用程序“外部”运行,而不是在运行JVM的同一过程中“嵌入”
a_horse_with_no_name

77

(将我的答案从使用内存中的PostgreSQL进行推广):

您无法在内存中运行Pg

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

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

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

而是:预配置连接

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

使用已经拥有数据库的现有PostgreSQL实例是安全的,只要提供给单元测试的用户不是超级用户,而只有超级用户即可CREATEDB。最糟糕的是,您将在其他数据库中造成性能问题。因此,我更喜欢运行一个完全隔离的PostgreSQL安装进行测试。

而是:启动一个废弃的PostgreSQL实例进行测试

或者,如果你真的热衷,你可以有你的测试工具定位initdbpostgres二进制文件,运行initdb创建数据库,修改pg_hba.conftrust,运行postgres启动它一个随机端口上,创建一个用户,创建一个数据库,并运行测试。您甚至可以将多个体系结构的PostgreSQL二进制文件捆绑在一个jar中,然后将当前体系结构的PostgreSQL二进制文件解压缩到一个临时目录,然后再运行测试。

我个人认为这是应避免的主要痛苦;只需配置一个测试数据库就更容易了。然而,它已经成为与的来临更容易一些include_dir支持postgresql.conf; 现在您只需追加一行,然后为其余所有内容编写一个生成的配置文件。

使用PostgreSQL进行更快的测试

有关如何安全地提高PostgreSQL性能以进行测试的更多信息,请参阅我之前在该主题上写的详细答案:优化PostgreSQL以进行快速测试

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

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

H2支持某些PostgreSQL扩展并模拟PostgreSQL方言。但是,仅此而已-一个仿真。您会发现H2接受查询但PostgreSQL不接受查询的地方,行为不同的地方等等。在撰写本文时,您还会发现PostgreSQL支持H2不能做的很多事情-例如窗口函数。

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

表空间不是答案!

千万不能使用表空间来创建一个“内存”数据库。这不仅是不必要的,因为它无论如何都无法显着提高性能,它还是一种中断对同一PostgreSQL安装中您可能关心的其他任何对象的访问的好方法。9.4文档现在包含以下警告

警告

即使位于主PostgreSQL数据目录之外,表空间还是数据库集群的组成部分,不能视为数据文件的自治集合。它们取决于主数据目录中包含的元数据,因此不能附加到其他数据库集群或单独备份。同样,如果丢失表空间(文件删除,磁盘故障等),则数据库集群可能变得不可读或无法启动。将表空间放在诸如ramdisk的临时文件系统上会危及整个群集的可靠性。

因为我注意到太多的人正在这样做并遇到麻烦。

(如果完成了此操作,则可以mkdir缺少的表空间目录来使PostgreSQL重新启动,然后DROP是缺少的数据库,表等。最好不要这样做。)


1
我不清楚此处提供的警告。如果我想快速运行单元测试,为什么要涉及集群?这不应该全部包含在我的本地PG实例中吗?如果群集(其中的一个)损坏了,为什么这很重要,我打算无论如何都将其删除。
盖茨副总裁

1
@GatesVP PostgreSQL以某种奇怪的方式使用术语“集群”来指代PostgreSQL实例(数据目录,数据库集合,postmaster等)。因此,它不是“计算集群”意义上的“集群”。是的,这很烦人,我希望看到术语发生变化。而且,如果它是一次性的,那也没关系,但是人们经常尝试在PostgreSQL安装上使用一次性的内存中表空间,该表空间包含了他们原本关心的数据。那是个问题。
Craig Ringer 2014年

好的,这既是“我的想法”又是“非常可怕”,RAMDrive解决方案显然仅属于不包含有用数据的本地数据库。但是,为什么有人要对不是他们自己的机器的机器运行单元测试呢?根据您的回答,对于仅在本地计算机上运行的PGSQL的实际单元测试实例,Tablespaces + RamDisk听起来完全合理。
盖茨副总裁

1
@GatesVP有些人将他们关心的事情保存在本地计算机上-很好,但是对于同一数据库安装运行单元测试有点愚蠢。但是,人们很傻。其中一些也没有正确备份。随之而来的哀号。
Craig Ringer 2014年

无论如何,如果您要使用ramdisk选项,那么您确实也希望在ramdisk上使用WAL,因此您最好initdb在那里安装一个全新的Pg。但是,实际上,为在普通存储上进行快速测试而调整的Pg(fsync = off和其他数据持久性/安全性功能已关闭)与在ramdisk上运行(至少在Linux上)几乎没有区别。
Craig Ringer 2014年

66

或者,您可以在ramfs / tempfs中创建一个TABLESPACE,并在那里创建所有对象。
最近我被指给我一篇关于在Linux上完全做到这一点文章

警告

这可能危及整个数据库集群的完整性。
阅读手册中添加的警告。
因此,这只是消耗性数据的一种选择。

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


4
我真的认为这是个坏建议。不要这样做。取而代之的initdb是tempfs或ramdisk中的新postgres实例。难道不是在tempfs等使用的表空间,它是脆弱的和毫无意义的。您最好使用普通的表空间并创建UNLOGGED表-它将执行类似的操作。除非您采取可能冒着损害整个数据库完整性的措施,否则它不会解决WAL性能和fsync因素(请参阅stackoverflow.com/q/9407442/398670)。不要这样
Craig Ringer

29

现在可以通过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钩子:

EmbeddedPostgres pg = EmbeddedPostgres.start();

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

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

1
六个月后,您对这个套餐的体验如何?运作良好,还是到处都是bug?
oligofren

@Rubms您是否迁移到JUnit5?你如何使用替换的@Rule@ExtendWith?只需使用.start()@BeforeAll
Frankie Drake

我尚未迁移到JUnit5,所以我无法回答您的问题。抱歉。
拉姆斯

这很好。谢谢。如果愿意,可以使用以下命令在spring配置中创建数据源:DataSource embeddedPostgresDS = EmbeddedPostgres.builder().start().getPostgresDatabase();
Sacky San

12

您可以使用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);
    }
}

7

现在有一个来自俄罗斯搜索公司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.

1
如果您使用多个线程,嵌入JVM或Mono运行时,您自己的子进程fork()或类似的东西,那必须以各种新颖而令人兴奋的方式爆炸。编辑:它不是真正的嵌入式,只是一个包装。
Craig Ringer

3

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


OP的主要问题是在内存中分配Postgres实例,不是为了提高性能,而是为了简化在dev和CI环境中引导单元测试的过程。
Triple.vee

0

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

您将拥有完整的内存中,独立于平台且与平台无关的数据库复制PG行为(它甚至可以在浏览器中运行))。

我写了一篇文章,以展示如何为你的单元测试使用它在这里

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.