Executando PostgreSQL apenas na memória

103

Eu quero executar um pequeno banco de dados PostgreSQL que é executado apenas na memória, para cada teste de unidade que escrevo. Por exemplo:

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

Idealmente, terei um único executável do postgres verificado no controle de versão, que o teste de unidade usará.

Algo assim HSQL, mas para postgres. Como eu posso fazer isso?

Onde posso obter essa versão do Postgres? Como posso instruí-lo a não usar o disco?

Chi-Lan
fonte

Respostas:

48

Isso não é possível com o Postgres. Ele não oferece um mecanismo em processo / em memória como HSQLDB ou MySQL.

Se você quiser criar um ambiente independente, pode colocar os binários do Postgres no SVN (mas é mais do que apenas um único executável).

Você precisará executar o initdb para configurar seu banco de dados de teste antes de fazer qualquer coisa com isso. Isso pode ser feito a partir de um arquivo em lote ou usando Runtime.exec (). Mas observe que o initdb não é algo rápido. Você definitivamente não vai querer executá-lo para cada teste. Você pode escapar executando isso antes de sua suíte de testes.

No entanto, embora isso possa ser feito, eu recomendo ter uma instalação Postgres dedicada, onde você simplesmente recriar seu banco de dados de teste antes de executar seus testes.

Você pode recriar o banco de dados de teste usando um banco de dados de modelo que torna sua criação bastante rápida ( muito mais rápido do que executar o initdb para cada teste)

um cavalo sem nome
fonte
8
Parece que a segunda resposta de Erwin abaixo deve ser marcada como a resposta certa
vfclists
3
@vfclists Na verdade, um espaço de tabela em um ramdisk é uma péssima ideia. Não faça isso. Consulte postgresql.org/docs/devel/static/manage-ag-tablespaces.html , stackoverflow.com/q/9407442/398670
Craig Ringer
1
@CraigRinger: Para esclarecer esta questão em particular: É uma má ideia misturar com dados valiosos (e obrigado pelo aviso). Para teste de unidade com um cluster de banco de dados dedicado, um ramdisk é adequado.
Erwin Brandstetter
1
Com o docker-use sendo comum, algumas pessoas tiveram sucesso com uma ferramenta como testcontainers, que essencialmente permite que seu teste inicie uma instância postgres descartável, dockerizada. Consulte github.com/testcontainers/testcontainers-java/blob/master/…
Hans Westerbeek
1
@ekcrisp. esta não é uma verdadeira versão embutida do Postgres. É apenas uma biblioteca wrapper para tornar mais fácil iniciar uma instância do Postgres (em um processo separado). O Postgres ainda será executado "fora" do aplicativo Java e não "incorporado" no mesmo processo que executa o JVM
a_horse_with_no_name
77

(Movendo minha resposta do Uso do PostgreSQL na memória e generalizando-o):

Você não pode executar o Pg em processo, na memória

Não consigo descobrir como executar o banco de dados Postgres na memória para testes. É possível?

Não, não é possível. PostgreSQL é implementado em C e compilado para o código da plataforma. Ao contrário do H2 ou Derby, você não pode simplesmente carregar o jare ativá-lo como um banco de dados in-memory descartável.

Ao contrário do SQLite, que também é escrito em C e compilado para o código da plataforma, o PostgreSQL também não pode ser carregado durante o processo. Requer vários processos (um por conexão) porque é uma arquitetura de multiprocessamento, não multithreading. O requisito de multiprocessamento significa que você deve iniciar o postmaster como um processo independente.

Em vez disso: pré-configure uma conexão

Eu sugiro simplesmente escrever seus testes para esperar que um determinado nome de host / nome de usuário / senha funcione, e ter o teste de controle de CREATE DATABASEum banco de dados descartável, então DROP DATABASEno final da execução. Obtenha os detalhes de conexão do banco de dados de um arquivo de propriedades, crie propriedades de destino, variável de ambiente, etc.

É seguro usar uma instância existente do PostgreSQL na qual você já tenha bancos de dados importantes, contanto que o usuário fornecido para os testes de unidade não seja um superusuário, apenas um usuário com CREATEDBdireitos. Na pior das hipóteses, você criará problemas de desempenho em outros bancos de dados. Eu prefiro executar uma instalação do PostgreSQL completamente isolada para teste por esse motivo.

Em vez disso: inicie uma instância descartável do PostgreSQL para teste

Alternativamente, se você estiver realmente interessado, pode fazer com que seu equipamento de teste localize os binários initdbe postgres, execute initdbpara criar um banco de dados, modifique pg_hba.confpara trust, execute postgrespara iniciá-lo em uma porta aleatória, crie um usuário, crie um banco de dados e execute os testes . Você pode até empacotar os binários do PostgreSQL para múltiplas arquiteturas em um jar e descompactar os da arquitetura atual em um diretório temporário antes de executar os testes.

Pessoalmente, acho que é uma grande dor que deve ser evitada; é muito mais fácil apenas ter um banco de dados de teste configurado. No entanto, ficou um pouco mais fácil com o advento do include_dirsuporte em postgresql.conf; agora você pode apenas anexar uma linha e escrever um arquivo de configuração gerado para todo o resto.

Teste mais rápido com PostgreSQL

Para obter mais informações sobre como melhorar com segurança o desempenho do PostgreSQL para fins de teste, consulte uma resposta detalhada que escrevi sobre este tópico: Otimize o PostgreSQL para testes rápidos

O dialeto PostgreSQL de H2 não é um verdadeiro substituto

Algumas pessoas, em vez disso, usam o banco de dados H2 no modo de dialeto PostgreSQL para executar testes. Eu acho que isso é quase tão ruim quanto o pessoal do Rails usando SQLite para testes e PostgreSQL para implantação de produção.

H2 suporta algumas extensões PostgreSQL e emula o dialeto PostgreSQL. No entanto, é apenas isso - uma emulação. Você vai encontrar áreas onde H2 aceita uma consulta, mas PostgreSQL não faz, onde difere de comportamento, etc . Você também encontrará muitos lugares onde o PostgreSQL suporta fazer algo que o H2 simplesmente não consegue - como funções de janela, no momento da escrita.

Se você entender as limitações dessa abordagem e seu acesso ao banco de dados for simples, H2 pode ser adequado. Mas, nesse caso, você provavelmente é um candidato melhor para um ORM que abstrai o banco de dados porque você não está usando seus recursos interessantes de qualquer maneira - e, nesse caso, você não precisa mais se preocupar tanto com a compatibilidade do banco de dados.

Os espaços de tabela não são a resposta!

Você não usar uma tabela para criar um banco de dados "in-memory". Além de desnecessário, não ajudará no desempenho de maneira significativa, mas também é uma ótima maneira de interromper o acesso a qualquer outro que seja do seu interesse na mesma instalação do PostgreSQL. A documentação 9.4 agora contém o seguinte aviso :

AVISO

Embora localizados fora do diretório de dados principal do PostgreSQL, os espaços de tabela são parte integrante do cluster de banco de dados e não podem ser tratados como uma coleção autônoma de arquivos de dados. Eles dependem dos metadados contidos no diretório de dados principal e, portanto, não podem ser anexados a um cluster de banco de dados diferente ou fazer backup individualmente. Da mesma forma, se você perder um espaço de tabela (exclusão de arquivo, falha de disco, etc.), o cluster de banco de dados pode se tornar ilegível ou incapaz de iniciar. Colocar um espaço de tabela em um sistema de arquivos temporário como um ramdisk arrisca a confiabilidade de todo o cluster.

porque percebi que muitas pessoas estavam fazendo isso e tendo problemas.

(Se você fez isso, pode mkdirremover o diretório do espaço de tabela ausente para fazer o PostgreSQL iniciar novamente, depois DROPos bancos de dados, tabelas etc. ausentes. É melhor simplesmente não fazer isso.)

Craig Ringer
fonte
1
Não estou certo sobre o aviso fornecido aqui. Se estou tentando executar testes de unidade rapidamente, por que há um cluster envolvido? Isso não deveria ser apenas na minha instância local e descartável do PG? Se o cluster (de um) está corrompido, por que isso importa, eu estava planejando excluí-lo de qualquer maneira.
Gates VP de
1
@GatesVP PostgreSQL usa o termo "cluster" de uma maneira um tanto estranha, para se referir à instância do PostgreSQL (diretório de dados, coleção de bancos de dados, postmaster, etc). Portanto, não é um "cluster" no sentido de "cluster de computação". Sim, isso é irritante e gostaria de ver essa mudança de terminologia. E se for descartável, é claro que não importa, mas as pessoas regularmente tentam ter um espaço de tabela descartável na memória em uma instalação do PostgreSQL que contém dados com os quais de outra forma se importam. Isso é um problema.
Craig Ringer de
OK, isso é "o que eu pensei" e "muito assustador" , a solução RAMDrive claramente pertence apenas a um banco de dados local que não contém dados úteis. Mas por que alguém iria querer executar testes de unidade em uma máquina que não é sua? Com base em sua resposta, Tablespaces + RamDisk parece perfeitamente legítimo para uma instância real de Teste de Unidade de PGSQL rodando somente em sua máquina local.
Gates VP de
1
@GatesVP Algumas pessoas mantêm coisas que lhes interessam em sua máquina local - o que é bom, mas é um pouco bobo executar testes de unidade na mesma instalação de banco de dados. As pessoas são bobas, no entanto. Alguns deles também não mantêm backups adequados. Lamentos se seguem.
Craig Ringer de
Em qualquer caso, se você estiver indo para a opção ramdisk, você realmente quer WAL no ramdisk também, então você pode muito bem initdbinstalar uma nova Pg lá. Mas, realmente, há pouca diferença entre um Pg que é ajustado para testes rápidos em armazenamento normal (fsync = off e outros recursos de durabilidade / segurança de dados desativados) do que rodar em um ramdisk, pelo menos no Linux.
Craig Ringer de
66

Ou você pode criar um TABLESPACE em um ramfs / tempfs e criar todos os seus objetos lá.
Recentemente, fui apontado para um artigo sobre fazer exatamente isso no Linux .

Aviso

Isso pode colocar em risco a integridade de todo o cluster de banco de dados .
Leia o aviso adicionado no manual.
Portanto, esta é apenas uma opção para dados dispensáveis.

Para o teste de unidade , deve funcionar bem. Se você estiver executando outros bancos de dados na mesma máquina, certifique-se de usar um cluster de banco de dados separado (que tem sua própria porta) para segurança.

Erwin Brandstetter
fonte
4
Eu realmente acho que este é um conselho ruim. Não faça isso. Em vez disso, initdbuma nova instância do postgres em um tempfs ou ramdisk. Você não usar uma tabela em um tempfs etc, é frágil e inútil. É melhor usar um espaço de tabela normal e criar UNLOGGEDtabelas - o desempenho será semelhante. E não tratará do desempenho do WAL e dos fatores fsync, a menos que você execute ações que arriscarão a integridade de todo o banco de dados (consulte stackoverflow.com/q/9407442/398670 ). Não faça isso.
Craig Ringer
29

Agora é possível executar uma instância na memória do PostgreSQL em seus testes JUnit por meio do componente PostgreSQL incorporado do OpenTable: https://github.com/opentable/otj-pg-embedded .

Adicionando a dependência à biblioteca otj-pg-embedded ( https://mvnrepository.com/artifact/com.opentable.components/otj-pg-embedded ) você pode iniciar e parar sua própria instância do PostgreSQL em seu @Before e @Afer hooks:

EmbeddedPostgres pg = EmbeddedPostgres.start();

Eles até oferecem uma regra JUnit para que o JUnit inicie e pare automaticamente o servidor de banco de dados PostgreSQL para você:

@Rule
public SingleInstancePostgresRule pg = EmbeddedPostgresRules.singleInstance();
Rubms
fonte
1
Como é sua experiência com este pacote seis meses depois? Funciona bem ou está cheio de bugs?
oligofren
@Rubms Você migrou para o JUnit5? Como você usa a substituição de @Rulepor @ExtendWith? Basta usar o .start()em @BeforeAll?
Frankie Drake
Não migrei para o JUnit5, então ainda não posso responder sua pergunta. Desculpe.
Rubms
Isso funcionou bem. Obrigado. Use o seguinte para criar uma fonte de dados em sua configuração de primavera, se desejar:DataSource embeddedPostgresDS = EmbeddedPostgres.builder().start().getPostgresDatabase();
Sacky San
12

Você pode usar TestContainers para ativar um contêiner docker PosgreSQL para testes: http://testcontainers.viewdocs.io/testcontainers-java/usage/database_containers/

TestContainers fornecem um JUnit @ Rule / @ ClassRule : este modo inicia um banco de dados dentro de um contêiner antes de seus testes e o desmonta depois.

Exemplo:

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);
    }
}
Andrejs
fonte
7

Agora existe uma versão na memória do PostgreSQL da empresa Russian Search chamada Yandex: https://github.com/yandex-qatools/postgresql-embedded

É baseado no processo de incorporação do Flapdoodle OSS.

Exemplo de uso (da página do 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));");

Estou usando há algum tempo. Isso funciona bem.

ATUALIZADO : este projeto não está mais sendo mantido ativamente

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.
Akvyalkov
fonte
1
Isso deve explodir em todos os tipos de maneiras novas e emocionantes se você usar vários encadeamentos, incorporar um JVM ou tempo de execução Mono, fork () seus próprios processos filhos ou qualquer coisa assim. Edit : Não é realmente incorporado, é apenas um invólucro.
Craig Ringer
3

Você também pode usar as definições de configuração do PostgreSQL (como as detalhadas na pergunta e resposta aceita aqui ) para obter desempenho sem necessariamente recorrer a um banco de dados na memória.

Dan
fonte
O principal problema do OP é girar uma instância do Postgres na memória, não para desempenho, mas para simplicidade na inicialização de testes de unidade em um ambiente de desenvolvimento e CI.
triple.vee
0

Se estiver usando NodeJS, você pode usar o pg-mem (isenção de responsabilidade: eu sou o autor) para emular os recursos mais comuns de um banco de dados postgres.

Você terá um banco de dados completo em memória, isolado e independente de plataforma, que replica o comportamento do PG (ele até roda em navegadores ).

Eu escrevi um artigo para mostrar como usá-lo para seus testes de unidade aqui .

Olivier
fonte