Ejecutar PostgreSQL solo en memoria

104

Quiero ejecutar una pequeña base de datos PostgreSQL que se ejecute solo en la memoria, para cada prueba unitaria que escribo. Por ejemplo:

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

Idealmente, tendré un solo ejecutable de postgres registrado en el control de versiones, que usará la prueba unitaria.

Algo así HSQL, pero para postgres. ¿Cómo puedo hacer eso?

¿Puedo obtener una versión de Postgres? ¿Cómo puedo indicarle que no utilice el disco?

Chi-Lan
fuente

Respuestas:

49

Esto no es posible con Postgres. No ofrece un motor en proceso / en memoria como HSQLDB o MySQL.

Si desea crear un entorno autónomo, puede colocar los binarios de Postgres en SVN (pero es más que un solo ejecutable).

Deberá ejecutar initdb para configurar su base de datos de prueba antes de poder hacer algo con esto. Esto se puede hacer desde un archivo por lotes o utilizando Runtime.exec (). Pero tenga en cuenta que initdb no es algo rápido. Definitivamente no querrá ejecutar eso para cada prueba. Sin embargo, es posible que pueda ejecutar esto antes de su suite de pruebas.

Sin embargo, aunque esto se puede hacer, recomendaría tener una instalación de Postgres dedicada en la que simplemente vuelva a crear su base de datos de prueba antes de ejecutar sus pruebas.

Puede volver a crear la base de datos de prueba utilizando una base de datos de plantilla que hace que crearla sea bastante rápido ( mucho más rápido que ejecutar initdb para cada ejecución de prueba)

un caballo sin nombre
fuente
8
Parece que la segunda respuesta de Erwin a continuación debe marcarse como la respuesta correcta
vfclists
3
@vfclists En realidad, un espacio de tabla en un disco RAM es una muy mala idea. No hagas eso. Ver postgresql.org/docs/devel/static/manage-ag-tablespaces.html , stackoverflow.com/q/9407442/398670
Craig Ringer
1
@CraigRinger: Para aclarar esta pregunta en particular: es una mala idea mezclar con datos valiosos (y gracias por la advertencia). Para pruebas unitarias con un clúster de base de datos dedicado, un disco RAM está bien.
Erwin Brandstetter
1
Dado que el uso de la ventana acoplable es algo común, algunas personas han tenido éxito con una herramienta como testcontainers, que esencialmente permite que su inicio de prueba sea una instancia de postgres desechable y dockerizada. Ver github.com/testcontainers/testcontainers-java/blob/master/…
Hans Westerbeek
1
@ekcrisp. esa no es una verdadera versión integrada de Postgres. Es solo una biblioteca contenedora para facilitar el inicio de una instancia de Postgres (en un proceso separado). Postgres aún se ejecutará "fuera" de la aplicación Java y no "incrustado" en el mismo proceso que ejecuta la JVM
a_horse_with_no_name
77

(Moviendo mi respuesta de Usar PostgreSQL en memoria y generalizándola):

No puede ejecutar Pg en proceso, en memoria

No puedo averiguar cómo ejecutar la base de datos de Postgres en memoria para realizar pruebas. ¿Es posible?

No, no es posible. PostgreSQL se implementa en C y se compila en el código de la plataforma. A diferencia de H2 o Derby, no puede simplemente cargar el jary activarlo como una base de datos en memoria desechable.

A diferencia de SQLite, que también está escrito en C y compilado en el código de la plataforma, PostgreSQL tampoco se puede cargar en el proceso. Requiere múltiples procesos (uno por conexión) porque es una arquitectura de multiprocesamiento, no de múltiples subprocesos. El requisito de multiprocesamiento significa que debe iniciar el administrador de correos como un proceso independiente.

En su lugar: preconfigura una conexión

Sugiero simplemente escribir sus pruebas para esperar que funcione un nombre de host / nombre de usuario / contraseña en particular, y hacer que la prueba aproveche CREATE DATABASEuna base de datos desechable, luego DROP DATABASEal final de la ejecución. Obtenga los detalles de conexión de la base de datos de un archivo de propiedades, cree propiedades de destino, variable de entorno, etc.

Es seguro usar una instancia de PostgreSQL existente en la que ya tiene bases de datos que le interesan, siempre que el usuario que proporcione a sus pruebas unitarias no sea un superusuario, solo un usuario con CREATEDBderechos. En el peor de los casos, creará problemas de rendimiento en las otras bases de datos. Prefiero ejecutar una instalación de PostgreSQL completamente aislada para realizar pruebas por ese motivo.

En su lugar: inicie una instancia de PostgreSQL desechable para realizar pruebas

Alternativamente, si usted está realmente Keen usted podría tener su instrumento de prueba localizar las initdby postgreslos binarios, dirigido initdba crear una base de datos, modificar pg_hba.confa trust, correr postgrespara iniciarlo en un puerto aleatorio, crear un usuario, crear una base de datos, y ejecutar las pruebas . Incluso podría agrupar los binarios de PostgreSQL para múltiples arquitecturas en un contenedor y descomprimir los de la arquitectura actual en un directorio temporal antes de ejecutar las pruebas.

Personalmente, creo que es un gran dolor que debe evitarse; es mucho más fácil configurar una base de datos de prueba. Sin embargo, se ha vuelto un poco más fácil con la llegada del include_dirsoporte postgresql.conf; ahora puede agregar una línea y luego escribir un archivo de configuración generado para el resto.

Pruebas más rápidas con PostgreSQL

Para obtener más información sobre cómo mejorar de forma segura el rendimiento de PostgreSQL con fines de prueba, consulte una respuesta detallada que escribí sobre este tema anteriormente: Optimizar PostgreSQL para pruebas rápidas

El dialecto PostgreSQL de H2 no es un verdadero sustituto

En cambio, algunas personas usan la base de datos H2 en el modo de dialecto de PostgreSQL para ejecutar pruebas. Creo que eso es casi tan malo como la gente de Rails que usa SQLite para pruebas y PostgreSQL para implementación de producción.

H2 admite algunas extensiones de PostgreSQL y emula el dialecto de PostgreSQL. Sin embargo, es solo eso, una emulación. Encontrará áreas donde H2 acepta una consulta, pero PostgreSQL no, donde se diferencia de comportamiento, etc . También encontrará muchos lugares donde PostgreSQL admite hacer algo que H2 simplemente no puede, como funciones de ventana, al momento de escribir.

Si comprende las limitaciones de este enfoque y su acceso a la base de datos es simple, H2 podría estar bien. Pero en ese caso, probablemente sea un mejor candidato para un ORM que abstraiga la base de datos porque de todos modos no está usando sus características interesantes, y en ese caso, ya no tiene que preocuparse tanto por la compatibilidad de la base de datos.

¡Los espacios de tabla no son la respuesta!

No , no utilizar un espacio de tablas para crear una base de datos "en memoria". No solo es innecesario, ya que de todos modos no ayudará al rendimiento de manera significativa, sino que también es una excelente manera de interrumpir el acceso a cualquier otro que pueda interesarle en la misma instalación de PostgreSQL. La documentación 9.4 ahora contiene la siguiente advertencia :

ADVERTENCIA

Aunque se encuentran fuera del directorio de datos principal de PostgreSQL, los espacios de tabla son una parte integral del clúster de la base de datos y no pueden tratarse como una colección autónoma de archivos de datos. Dependen de los metadatos contenidos en el directorio de datos principal y, por lo tanto, no se pueden adjuntar a un clúster de base de datos diferente ni respaldar individualmente. De manera similar, si pierde un espacio de tabla (eliminación de archivos, falla del disco, etc.), el clúster de la base de datos podría volverse ilegible o no poder iniciarse. Colocar un espacio de tabla en un sistema de archivos temporal como un disco RAM pone en riesgo la confiabilidad de todo el clúster.

porque me di cuenta de que demasiadas personas estaban haciendo esto y tenían problemas.

(Si ha hecho esto, puede mkdirusar el directorio de espacio de tabla que falta para que PostgreSQL comience de nuevo, luego DROPlas bases de datos, tablas, etc. faltantes. Es mejor no hacerlo).

Craig Ringer
fuente
1
No tengo claro la advertencia proporcionada aquí. Si intento ejecutar pruebas unitarias rápidamente, ¿por qué hay un clúster involucrado? ¿No debería estar todo esto en mi instancia local y desechable de PG? Si el clúster (de uno) está dañado, ¿por qué importa? Estaba planeando eliminarlo de todos modos.
Vicepresidente de Gates
1
@GatesVP PostgreSQL usa el término "cluster" de una manera algo extraña, para referirse a la instancia de PostgreSQL (directorio de datos, colección de bases de datos, postmaster, etc.). Por lo tanto, no es un "clúster" en el sentido de "clúster de cálculo". Sí, eso es molesto y me gustaría que cambiara esa terminología. Y si es desechable, por supuesto que no importa, pero la gente intenta regularmente tener un espacio de tabla en memoria desechable en una instalación de PostgreSQL que contiene datos que de otra manera les interesan. Eso es un problema.
Craig Ringer
Bien, eso es "lo que pensé" y "muy aterrador" , la solución RAMDrive claramente solo pertenece a una base de datos local que no contiene datos útiles. Pero, ¿por qué querría alguien ejecutar pruebas unitarias en una máquina que no es su propia máquina? Según su respuesta, Tablespaces + RamDisk suena perfectamente legítimo para una instancia de prueba unitaria real de PGSQL que se ejecuta únicamente en su máquina local.
Vicepresidente de Gates
1
@GatesVP Algunas personas guardan las cosas que les interesan en su máquina local, lo cual está bien, pero luego es un poco tonto ejecutar pruebas unitarias en la misma instalación de base de datos. Sin embargo, la gente es tonta. Algunos de ellos tampoco mantienen copias de seguridad adecuadas. Siguen los lamentos.
Craig Ringer
En cualquier caso, si va a optar por la opción ramdisk, realmente también quiere WAL en el ramdisk, por lo que también puede initdbinstalar una Pg completamente nueva allí. Pero en realidad, hay poca diferencia entre una página que se ha ajustado para realizar pruebas rápidas en el almacenamiento normal (fsync = off y otras características de durabilidad / seguridad de datos desactivadas) que ejecutarse en un ramdisk, al menos en Linux.
Craig Ringer
66

O puede crear un TABLESPACE en ramfs / tempfs y crear todos sus objetos allí.
Recientemente me señalaron un artículo sobre cómo hacer exactamente eso en Linux .

Advertencia

Esto puede poner en peligro la integridad de todo su clúster de base de datos .
Lea la advertencia agregada en el manual.
Así que esta es solo una opción para datos fungibles.

Para las pruebas unitarias , debería funcionar bien. Si está ejecutando otras bases de datos en la misma máquina, asegúrese de usar un clúster de base de datos separado (que tiene su propio puerto) para estar seguro.

Erwin Brandstetter
fuente
4
Realmente creo que este es un mal consejo. No hagas esto. En su lugar, initdbuna nueva instancia de postgres en tempfs o ramdisk. No , no utilizar un espacio de tabla en un tempfs etc, es frágil y sin sentido. Es mejor usar un espacio de tabla normal y crear UNLOGGEDtablas; funcionará de manera similar. Y no abordará el rendimiento de WAL y los factores fsync a menos que tome medidas que pongan en riesgo la integridad de toda la base de datos (consulte stackoverflow.com/q/9407442/398670 ). No lo hagas.
Craig Ringer
29

Ahora es posible ejecutar una instancia en memoria de PostgreSQL en sus pruebas JUnit a través del componente PostgreSQL incrustado de OpenTable: https://github.com/opentable/otj-pg-embedded .

Al agregar la dependencia a la biblioteca otj-pg-embedded ( https://mvnrepository.com/artifact/com.opentable.components/otj-pg-embedded ) puede iniciar y detener su propia instancia de PostgreSQL en su @Before y @Afer ganchos:

EmbeddedPostgres pg = EmbeddedPostgres.start();

Incluso ofrecen una regla JUnit para que JUnit inicie y detenga automáticamente su servidor de base de datos PostgreSQL:

@Rule
public SingleInstancePostgresRule pg = EmbeddedPostgresRules.singleInstance();
Rubms
fuente
1
¿Cómo es su experiencia con este paquete seis meses después? ¿Funciona bien o está plagado de errores?
oligofren
@Rubms ¿Migraste a JUnit5? ¿Cómo se usa el reemplazo del @Rulecon @ExtendWith? Solo usa el .start()in @BeforeAll?
Frankie Drake
No he migrado a JUnit5, por lo que todavía no puedo responder a su pregunta. Lo siento.
Rubms
Esto funcionó bien. Gracias. Use lo siguiente para crear una fuente de datos en su configuración de primavera si lo desea:DataSource embeddedPostgresDS = EmbeddedPostgres.builder().start().getPostgresDatabase();
Sacky San
12

Puede usar TestContainers para activar un contenedor Docker de PosgreSQL para pruebas: http://testcontainers.viewdocs.io/testcontainers-java/usage/database_containers/

TestContainers proporciona un JUnit @ Rule / @ ClassRule : este modo inicia una base de datos dentro de un contenedor antes de sus pruebas y luego la destruye.

Ejemplo:

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
fuente
7

Ahora hay una versión en memoria de PostgreSQL de la compañía de búsqueda rusa llamada Yandex: https://github.com/yandex-qatools/postgresql-embedded

Se basa en el proceso de incrustación de Flapdoodle OSS.

Ejemplo de uso (de la página de 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 estoy usando algún tiempo. Funciona bien.

ACTUALIZADO : este proyecto ya no se mantiene activamente

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
fuente
1
Eso debe explotar en todo tipo de formas nuevas y emocionantes si usa múltiples subprocesos, incrusta un tiempo de ejecución JVM o Mono, bifurca () sus propios procesos secundarios o algo por el estilo. Editar : no está realmente incrustado, es solo un contenedor.
Craig Ringer
3

También puede utilizar las opciones de configuración de PostgreSQL (como las que se detallan en la pregunta y la respuesta aceptada aquí ) para lograr el rendimiento sin tener que recurrir necesariamente a una base de datos en memoria.

Dan
fuente
El problema principal del OP es activar una instancia de Postgres en la memoria, no por rendimiento, sino por simplicidad en las pruebas unitarias de arranque en un entorno de desarrollo y CI.
triple.vee
0

Si está usando NodeJS, puede usar pg-mem (descargo de responsabilidad: soy el autor) para emular las características más comunes de una base de datos de postgres.

Tendrá una base de datos completa en memoria, aislada e independiente de la plataforma que replica el comportamiento de PG (incluso se ejecuta en navegadores ).

Escribí un artículo para mostrar cómo usarlo para tus pruebas unitarias aquí .

Olivier
fuente