Esecuzione di PostgreSQL solo in memoria


104

Voglio eseguire un piccolo database PostgreSQL che gira solo in memoria, per ogni unit test che scrivo. Per esempio:

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

Idealmente avrò un singolo eseguibile postgres controllato nel controllo della versione, che verrà utilizzato dallo unit test.

Qualcosa di simile HSQL, ma per postgres. Come lo posso fare?

Dove posso ottenere una versione di Postgres del genere? Come posso istruirlo a non usare il disco?

Risposte:


49

Questo non è possibile con Postgres. Non offre un motore in-process / in-memory come HSQLDB o MySQL.

Se vuoi creare un ambiente autonomo puoi mettere i binari di Postgres in SVN (ma è più di un semplice eseguibile).

Dovrai eseguire initdb per configurare il tuo database di prova prima di poter fare qualsiasi cosa con questo. Questa operazione può essere eseguita da un file batch o utilizzando Runtime.exec (). Ma nota che initdb non è qualcosa che è veloce. Sicuramente non vorrai eseguirlo per ogni test. Potresti farla franca eseguendolo prima della tua suite di test.

Tuttavia, sebbene ciò possa essere fatto, ti consiglio di avere un'installazione Postgres dedicata in cui devi semplicemente ricreare il tuo database di test prima di eseguire i test.

È possibile ricreare il database di test utilizzando un database modello che rende la creazione abbastanza veloce ( molto più veloce dell'esecuzione di initdb per ogni esecuzione di test)


8
Sembra che la seconda risposta di Erwin di seguito dovrebbe essere contrassegnata come risposta corretta
vfclists

3
@vfclists In realtà, un tablespace su un ramdisk è una pessima idea. Non farlo. Vedi postgresql.org/docs/devel/static/manage-ag-tablespaces.html , stackoverflow.com/q/9407442/398670
Craig Ringer

1
@ CraigRinger: Per chiarire questa domanda in particolare: è una cattiva idea mescolare con dati preziosi (e grazie per l'avvertimento). Per i test di unità con un cluster DB dedicato, un ramdisk va bene.
Erwin Brandstetter

1
Poiché l'uso di docker è all'ordine del giorno, alcune persone hanno avuto successo con uno strumento come testcontainers, che essenzialmente consente l'avvio del test come un'istanza postgres usa e getta, dockerized. Vedi github.com/testcontainers/testcontainers-java/blob/master/…
Hans Westerbeek

1
@ekcrisp. non è una vera versione incorporata di Postgres. È solo una libreria wrapper per rendere più semplice l'avvio di un'istanza Postgres (in un processo separato). Postgres verrà comunque eseguito "fuori" dall'applicazione Java e non "incorporato" nello stesso processo che esegue la JVM
a_horse_with_no_name

77

(Spostando la mia risposta dall'utilizzo di PostgreSQL in memoria e generalizzandola):

Non puoi eseguire Pg in-process, in-memory

Non riesco a capire come eseguire il database Postgres in memoria per i test. È possibile?

No, non è possibile. PostgreSQL è implementato in C e compilato nel codice della piattaforma. A differenza di H2 o Derby non puoi semplicemente caricare il jare accenderlo come un DB in memoria usa e getta.

A differenza di SQLite, che è scritto anche in C e compilato nel codice della piattaforma, PostgreSQL non può essere caricato in-process. Richiede più processi (uno per connessione) perché è un'architettura multiprocessing, non multithreading. Il requisito di multiprocessing significa che è necessario avviare il postmaster come processo autonomo.

Invece: preconfigurare una connessione

Suggerisco semplicemente di scrivere i tuoi test per aspettarti che un particolare nome host / nome utente / password funzioni e che il test utilizzi CREATE DATABASEun database usa e getta, quindi DROP DATABASEalla fine della corsa. Ottieni i dettagli della connessione al database da un file delle proprietà, crea proprietà di destinazione, variabile di ambiente, ecc.

È sicuro utilizzare un'istanza PostgreSQL esistente in cui hai già database a cui tieni, a condizione che l'utente che fornisci ai tuoi unit test non sia un superutente, ma solo un utente con CREATEDBdiritti. Nel peggiore dei casi creerai problemi di prestazioni negli altri database. Per questo motivo preferisco eseguire un'installazione PostgreSQL completamente isolata per i test.

Invece: avvia un'istanza PostgreSQL usa e getta per il test

In alternativa, se si sta davvero molto entusiasta si potrebbe avere il vostro test harness individuare le initdbe postgresfile binari, eseguire initdbper creare un database, modificare pg_hba.confa trust, correre postgresper avviarlo su una porta a caso, creare un utente, creare un DB, ed eseguire le prove . Potresti anche raggruppare i binari di PostgreSQL per più architetture in un jar e decomprimere quelli per l'architettura corrente in una directory temporanea prima di eseguire i test.

Personalmente penso che sia un grande dolore che dovrebbe essere evitato; è molto più semplice configurare un DB di prova. Tuttavia, è diventato un po 'più facile con l'avvento del include_dirsupporto in postgresql.conf; ora puoi semplicemente aggiungere una riga, quindi scrivere un file di configurazione generato per tutto il resto.

Test più veloci con PostgreSQL

Per ulteriori informazioni su come migliorare in sicurezza le prestazioni di PostgreSQL a scopo di test, vedere una risposta dettagliata che ho scritto su questo argomento in precedenza: Ottimizza PostgreSQL per test veloci

Il dialetto PostgreSQL di H2 non è un vero sostituto

Alcune persone invece usano il database H2 in modalità dialetto PostgreSQL per eseguire i test. Penso che sia dannoso quasi quanto le persone Rails che usano SQLite per i test e PostgreSQL per la distribuzione in produzione.

H2 supporta alcune estensioni PostgreSQL ed emula il dialetto PostgreSQL. Tuttavia, è proprio questo: un'emulazione. Troverai aree in cui H2 accetta una query ma PostgreSQL no, dove il comportamento è diverso, ecc . Troverai anche molti posti in cui PostgreSQL supporta fare qualcosa che H2 semplicemente non può - come le funzioni della finestra, al momento della scrittura.

Se comprendi i limiti di questo approccio e l'accesso al database è semplice, H2 potrebbe andare bene. Ma in quel caso probabilmente sei un candidato migliore per un ORM che astrae il database perché non stai comunque utilizzando le sue caratteristiche interessanti - e in quel caso, non devi più preoccuparti della compatibilità del database.

I tablespace non sono la risposta!

Do Non utilizzare uno spazio tabella per creare una base di dati "in-memory". Non solo non è necessario in quanto non aiuterà comunque le prestazioni in modo significativo, ma è anche un ottimo modo per interrompere l'accesso a qualsiasi altro potrebbe interessarti nella stessa installazione di PostgreSQL. La documentazione 9.4 ora contiene il seguente avviso :

AVVERTIMENTO

Anche se si trovano al di fuori della directory principale dei dati di PostgreSQL, i tablespace sono parte integrante del cluster di database e non possono essere trattati come una raccolta autonoma di file di dati. Dipendono dai metadati contenuti nella directory dei dati principale e pertanto non possono essere collegati a un cluster di database diverso o sottoposti a backup singolarmente. Allo stesso modo, se si perde uno spazio tabella (eliminazione di file, guasto del disco, ecc.), Il cluster di database potrebbe diventare illeggibile o non essere in grado di avviarsi. Posizionare uno spazio tabella su un file system temporaneo come un ramdisk rischia l'affidabilità dell'intero cluster.

perché ho notato che troppe persone lo facevano e si trovavano nei guai.

(Se lo hai fatto, puoi utilizzare mkdirla directory del tablespace mancante per far ripartire PostgreSQL, quindi DROPi database, le tabelle, ecc. Mancanti, è meglio non farlo.)


1
Non sono chiaro sull'avviso fornito qui. Se sto cercando di eseguire test unitari velocemente, perché è coinvolto un cluster? Non dovrebbe essere tutto nella mia istanza locale e usa e getta di PG? Se il cluster (di uno) è danneggiato, perché è importante, stavo pianificando di eliminarlo comunque.
Gates VP

1
@GatesVP PostgreSQL usa il termine "cluster" in un modo un po 'strano, per fare riferimento all'istanza PostgreSQL (directory dei dati, raccolta di database, postmaster, ecc.). Quindi non è un "cluster" nel senso di "cluster di calcolo". Sì, è fastidioso e mi piacerebbe vedere cambiare la terminologia. E se è usa e getta, ovviamente non importa, ma le persone tentano regolarmente di avere un tablespace in memoria usa e getta su un'installazione PostgreSQL che contiene dati a cui altrimenti tengono. Questo é un problema.
Craig Ringer

OK, questo è sia "quello che pensavo" e "molto spaventoso" , la soluzione RAMDrive appartiene chiaramente solo a un DB locale che non contiene dati utili. Ma perché qualcuno dovrebbe voler eseguire test unitari su una macchina che non è la loro macchina? In base alla tua risposta, Tablespaces + RamDisk sembra perfettamente legittimo per un'istanza effettiva di Unit Test di PGSQL in esecuzione esclusivamente sulla tua macchina locale.
Gates VP

1
@GatesVP Alcune persone mantengono le cose a cui tengono sulla loro macchina locale, il che va bene, ma è un po 'sciocco eseguire test di unità sulla stessa installazione DB. Le persone sono stupide, però. Alcuni di loro inoltre non mantengono backup adeguati. Ne derivano lamenti.
Craig Ringer

In ogni caso, se hai intenzione di utilizzare l'opzione ramdisk, vuoi davvero WAL anche sul ramdisk, quindi potresti anche initdbinstallare un Pg completamente nuovo lì. Ma in realtà, c'è poca differenza tra un Pg ottimizzato per test rapidi su archiviazione normale (fsync = off e altre funzionalità di sicurezza / durabilità dei dati disattivate) rispetto a un ramdisk, almeno su Linux.
Craig Ringer

66

Oppure puoi creare un TABLESPACE in ramfs / tempfs e creare tutti i tuoi oggetti lì.
Recentemente mi è stato indicato un articolo su come fare esattamente questo su Linux .

avvertimento

Ciò può mettere in pericolo l'integrità dell'intero cluster di database .
Leggere l'avviso aggiunto nel manuale.
Quindi questa è solo un'opzione per i dati sacrificabili.

Per i test unitari dovrebbe funzionare bene. Se stai eseguendo altri database sulla stessa macchina, assicurati di utilizzare un cluster di database separato (che ha la sua porta) per sicurezza.


4
Penso davvero che questo sia un cattivo consiglio. Non farlo. Invece, initdbuna nuova istanza di postgres in un tempfs o ramdisk. Do Non utilizzare uno spazio tabella in un tempfs etc, è fragile e inutile. È meglio usare un normale tablespace e creare UNLOGGEDtabelle: funzionerà in modo simile. E non affronterà le prestazioni WAL e i fattori fsync a meno che non si intraprendano azioni che rischieranno l'integrità dell'intero DB (vedere stackoverflow.com/q/9407442/398670 ). Non farlo.
Craig Ringer

29

Ora è possibile eseguire un'istanza in memoria di PostgreSQL nei test JUnit tramite il componente PostgreSQL incorporato da OpenTable: https://github.com/opentable/otj-pg-embedded .

Aggiungendo la dipendenza alla libreria otj-pg-embedded ( https://mvnrepository.com/artifact/com.opentable.components/otj-pg-embedded ) puoi avviare e interrompere la tua istanza di PostgreSQL nel tuo @Before e @Afer ganci:

EmbeddedPostgres pg = EmbeddedPostgres.start();

Offrono persino una regola JUnit per fare in modo che JUnit avvii e arresti automaticamente il tuo server di database PostgreSQL per te:

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

1
Come è stata la tua esperienza con questo pacchetto sei mesi dopo? Funziona bene o è pieno di bug?
oligofren

@Rubms Sei migrato a JUnit5? Come si utilizza la sostituzione del @Rulecon @ExtendWith? Basta usare il .start()in @BeforeAll?
Frankie Drake

Non sono migrato a JUnit5, quindi non posso ancora rispondere alla tua domanda. Scusate.
Rubms

Ha funzionato bene. Grazie. Usa quanto segue per creare un'origine dati nella tua configurazione primaverile se ti piace:DataSource embeddedPostgresDS = EmbeddedPostgres.builder().start().getPostgresDatabase();
Sacky San

12

È possibile utilizzare TestContainers per avviare un contenitore docker PosgreSQL per i test: http://testcontainers.viewdocs.io/testcontainers-java/usage/database_containers/

I TestContainer forniscono una JUnit @ Rule / @ ClassRule : questa modalità avvia un database all'interno di un contenitore prima dei test e lo elimina in seguito.

Esempio:

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

Ora è disponibile una versione in memoria di PostgreSQL della società di ricerca russa denominata Yandex: https://github.com/yandex-qatools/postgresql-embedded

È basato sul processo di incorporamento di Flapdoodle OSS.

Esempio di utilizzo (dalla pagina 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));");

Lo sto usando da un po 'di tempo. Funziona bene.

AGGIORNATO : questo progetto non viene più mantenuto attivamente

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
Questo deve esplodere in tutti i tipi di modi nuovi ed eccitanti se usi più thread, incorpori un runtime JVM o Mono, fork () dei tuoi processi figli o qualcosa del genere. Modifica : non è realmente incorporato, è solo un wrapper.
Craig Ringer

3

È inoltre possibile utilizzare le impostazioni di configurazione di PostgreSQL (come quelle dettagliate nella domanda e la risposta accettata qui ) per ottenere prestazioni senza necessariamente ricorrere a un database in memoria.


Il problema principale dell'OP è avviare un'istanza Postgres in memoria, non per le prestazioni, ma per semplicità nei test unitari di bootstrap in un ambiente di sviluppo e CI.
triple.vee l'

0

Se stai usando NodeJS, puoi usare pg-mem (disclaimer: I'm the author) per emulare le caratteristiche più comuni di un db postgres.

Avrai un database completo in memoria, isolato e indipendente dalla piattaforma che replica il comportamento del PG (funziona anche nei browser ).

Ho scritto un articolo per mostrare come usarlo per i tuoi unit test qui .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.