Exécution de PostgreSQL en mémoire uniquement

104

Je veux exécuter une petite base de données PostgreSQL qui ne fonctionne qu'en mémoire, pour chaque test unitaire que j'écris. Par exemple:

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

Idéalement, je vais avoir un seul exécutable postgres vérifié dans le contrôle de version, que le test unitaire utilisera.

Quelque chose comme HSQL, mais pour postgres. Comment puis je faire ça?

Où puis-je obtenir une telle version de Postgres? Comment puis-je lui dire de ne pas utiliser le disque?

Chi-Lan
la source

Réponses:

49

Cela n'est pas possible avec Postgres. Il n'offre pas de moteur en processus / en mémoire comme HSQLDB ou MySQL.

Si vous souhaitez créer un environnement autonome, vous pouvez placer les binaires Postgres dans SVN (mais c'est plus qu'un simple exécutable).

Vous devrez exécuter initdb pour configurer votre base de données de test avant de pouvoir faire quoi que ce soit avec cela. Cela peut être fait à partir d'un fichier de commandes ou en utilisant Runtime.exec (). Mais notez que initdb n'est pas quelque chose de rapide. Vous ne voudrez certainement pas exécuter cela pour chaque test. Vous pourriez vous en sortir avant votre suite de tests.

Cependant, même si cela peut être fait, je vous recommande d'avoir une installation Postgres dédiée où vous recréez simplement votre base de données de test avant d'exécuter vos tests.

Vous pouvez recréer la base de données de test en utilisant une base de données modèle qui rend sa création assez rapide ( beaucoup plus rapide que d'exécuter initdb pour chaque test)

un cheval sans nom
la source
8
Il semble que la deuxième réponse d'Erwin ci-dessous devrait être marquée comme la bonne réponse
vfclists
3
@vfclists En fait, un tablespace sur un disque virtuel est une très mauvaise idée. Ne fais pas ça. Voir postgresql.org/docs/devel/static/manage-ag-tablespaces.html , stackoverflow.com/q/9407442/398670
Craig Ringer
1
@CraigRinger: Pour clarifier cette question particulière: c'est une mauvaise idée de mélanger avec des données précieuses (et merci pour l'avertissement). Pour les tests unitaires avec un cluster DB dédié, un disque virtuel convient.
Erwin Brandstetter
1
L'utilisation de docker étant courante, certaines personnes ont réussi avec un outil comme testcontainers, qui permet essentiellement à votre test de démarrer une instance jetable, dockerisée, postgres. Voir github.com/testcontainers/testcontainers-java/blob/master
Hans Westerbeek
1
@ekcrisp. ce n'est pas une véritable version intégrée de Postgres. C'est juste une bibliothèque de wrapper pour faciliter le démarrage d'une instance Postgres (dans un processus séparé). Postgres fonctionnera toujours "en dehors" de l'application Java et non "intégré" dans le même processus qui exécute la JVM
a_horse_with_no_name
77

(Déplacer ma réponse de Utiliser PostgreSQL en mémoire et la généraliser):

Vous ne pouvez pas exécuter Pg en cours de traitement, en mémoire

Je ne peux pas comprendre comment exécuter la base de données Postgres en mémoire pour les tests. C'est possible?

Non ce n'est pas possible. PostgreSQL est implémenté en C et compilé en code de plateforme. Contrairement à H2 ou Derby, vous ne pouvez pas simplement charger le jaret le lancer comme une base de données en mémoire jetable.

Contrairement à SQLite, qui est également écrit en C et compilé en code de plate-forme, PostgreSQL ne peut pas non plus être chargé en cours de traitement. Il nécessite plusieurs processus (un par connexion) car il s'agit d'une architecture multitraitement et non multithreading. L'exigence de multitraitement signifie que vous devez lancer le postmaster en tant que processus autonome.

Au lieu de cela: préconfigurer une connexion

Je suggère simplement d'écrire vos tests pour vous attendre à ce qu'un nom d'hôte / nom d'utilisateur / mot de passe particulier fonctionne, et que le test exploite CREATE DATABASEune base de données jetable, puisDROP DATABASE à la fin de la course. Obtenez les détails de connexion à la base de données à partir d'un fichier de propriétés, des propriétés de la cible de construction, d'une variable d'environnement, etc.

Vous pouvez utiliser en toute sécurité une instance PostgreSQL existante dans laquelle vous avez déjà des bases de données qui vous intéressent, tant que l'utilisateur que vous fournissez à vos tests unitaires n'est pas un super-utilisateur, seulement un utilisateur avec des CREATEDBdroits. Au pire, vous créerez des problèmes de performances dans les autres bases de données. Je préfère exécuter une installation PostgreSQL complètement isolée pour tester pour cette raison.

Au lieu de cela: lancez une instance PostgreSQL jetable à des fins de test

Alternativement, si vous êtes vraiment envie que vous pourriez avoir votre harnais de test localiser les initdbet postgresbinaires, exécutez initdbpour créer une base de données, modifier pg_hba.confà trust, courir postgrespour le démarrer sur un port aléatoire, créez un utilisateur, créez un DB, et exécuter les tests . Vous pouvez même regrouper les binaires PostgreSQL pour plusieurs architectures dans un fichier jar et décompresser ceux de l'architecture actuelle dans un répertoire temporaire avant d'exécuter les tests.

Personnellement, je pense que c'est une douleur majeure qui devrait être évitée; il est beaucoup plus facile de configurer simplement une base de données de test. Cependant, c'est devenu un peu plus facile avec l'avènement du include_dirsupport dans postgresql.conf; maintenant, vous pouvez simplement ajouter une ligne, puis écrire un fichier de configuration généré pour tout le reste.

Des tests plus rapides avec PostgreSQL

Pour plus d'informations sur la façon d' améliorer en toute sécurité les performances de PostgreSQL à des fins de test, consultez une réponse détaillée que j'ai écrite sur ce sujet plus tôt: Optimiser PostgreSQL pour des tests rapides

Le dialecte PostgreSQL de H2 n'est pas un véritable substitut

Certaines personnes utilisent à la place la base de données H2 en mode dialecte PostgreSQL pour exécuter des tests. Je pense que c'est presque aussi mauvais que les gens de Rails qui utilisent SQLite pour les tests et PostgreSQL pour le déploiement en production.

H2 prend en charge certaines extensions PostgreSQL et émule le dialecte PostgreSQL. Cependant, c'est juste cela - une émulation. Vous trouverez les zones où H2 accepte une requête mais PostgreSQL qui ne fonctionne pas, où diffère du comportement, etc . Vous trouverez également de nombreux endroits où PostgreSQL prend en charge ce que H2 ne peut tout simplement pas faire - comme les fonctions de fenêtre, au moment de l'écriture.

Si vous comprenez les limites de cette approche et que votre accès à la base de données est simple, H2 peut être OK. Mais dans ce cas, vous êtes probablement un meilleur candidat pour un ORM qui résume la base de données car vous n'utilisez de toute façon pas ses fonctionnalités intéressantes - et dans ce cas, vous n'avez plus à vous soucier de la compatibilité de la base de données.

Les tablespaces ne sont pas la solution!

N'utilisez pas d' espace de table pour créer une base de données «en mémoire». Non seulement cela n'est pas nécessaire car cela n'aidera pas de manière significative les performances de toute façon, mais c'est aussi un excellent moyen de perturber l'accès à tout autre élément qui pourrait vous intéresser dans la même installation PostgreSQL. La documentation 9.4 contient désormais l'avertissement suivant :

AVERTISSEMENT

Même s'ils sont situés en dehors du répertoire de données principal de PostgreSQL, les tablespaces font partie intégrante du cluster de bases de données et ne peuvent pas être traités comme une collection autonome de fichiers de données. Ils dépendent des métadonnées contenues dans le répertoire de données principal et ne peuvent donc pas être attachés à un autre cluster de bases de données ou sauvegardés individuellement. De même, si vous perdez un tablespace (suppression de fichier, panne de disque, etc.), le cluster de base de données peut devenir illisible ou ne pas pouvoir démarrer. Le fait de placer un tablespace sur un système de fichiers temporaire tel qu'un disque virtuel risque de compromettre la fiabilité de l'ensemble du cluster.

parce que j'ai remarqué que trop de gens faisaient cela et avaient des problèmes.

(Si vous avez fait cela, vous pouvez mkdirle répertoire de tablespace manquant pour redémarrer PostgreSQL, puis DROPles bases de données manquantes, les tables, etc. Il vaut mieux ne pas le faire.)

Craig Ringer
la source
1
Je ne suis pas certain de l'avertissement fourni ici. Si j'essaie d'exécuter des tests unitaires rapidement, pourquoi un cluster est-il impliqué? Cela ne devrait-il pas être tout simplement sur mon instance locale jetable de PG? Si le cluster (d'un) est corrompu, pourquoi est-ce important, j'avais l'intention de le supprimer de toute façon.
Gates VP
1
@GatesVP PostgreSQL utilise le terme "cluster" d'une manière un peu étrange, pour désigner l'instance PostgreSQL (répertoire de données, collection de bases de données, postmaster, etc.). Ce n'est donc pas un «cluster» au sens de «cluster de calcul». Oui, c'est ennuyeux, et j'aimerais voir cette terminologie changer. Et si c'est jetable, bien sûr, cela n'a pas d'importance, mais les gens essaient régulièrement d'avoir un tablespace en mémoire jetable sur une installation PostgreSQL qui contient des données qui les intéressent autrement. C'est un problème.
Craig Ringer
OK, c'est à la fois "ce que je pensais" et "très effrayant" , la solution RAMDrive n'appartient clairement qu'à une base de données locale qui ne contient aucune donnée utile. Mais pourquoi quelqu'un voudrait-il exécuter des tests unitaires sur une machine qui n'est pas sa propre machine? Sur la base de votre réponse, Tablespaces + RamDisk semble parfaitement légitime pour une instance de test unitaire réelle de PGSQL fonctionnant uniquement sur votre machine locale.
Gates VP
1
@GatesVP Certaines personnes gardent les choses qui leur tiennent à cœur sur leur machine locale - ce qui est bien, mais c'est alors un peu ridicule d'exécuter des tests unitaires sur la même installation de base de données. Les gens sont stupides, cependant. Certains d'entre eux ne conservent pas non plus de sauvegardes appropriées. Des pleurs s'ensuivent.
Craig Ringer
Dans tous les cas, si vous optez pour l'option ramdisk, vous voulez vraiment WAL sur le ramdisk aussi, donc vous pourriez aussi bien initdbinstaller une toute nouvelle Pg là-bas. Mais en réalité, il y a peu de différence entre un Pg qui a été modifié pour des tests rapides sur un stockage normal (fsync = off et d'autres fonctionnalités de durabilité / sécurité des données désactivées) que de s'exécuter sur un disque virtuel, du moins sous Linux.
Craig Ringer
66

Ou vous pouvez créer un TABLESPACE dans un ramfs / tempfs et y créer tous vos objets.
On m'a récemment signalé un article sur le fait de faire exactement cela sous Linux .

avertissement

Cela peut mettre en danger l'intégrité de l' ensemble de votre cluster de base de données .
Lisez l'avertissement ajouté dans le manuel.
Ce n'est donc qu'une option pour les données non durables.

Pour les tests unitaires, cela devrait fonctionner correctement. Si vous exécutez d'autres bases de données sur la même machine, veillez à utiliser un cluster de base de données distinct (qui a son propre port) pour être sûr.

Erwin Brandstetter
la source
4
Je pense vraiment que c'est un mauvais conseil. Ne faites pas cela. Au lieu de cela, initdbune nouvelle instance de postgres dans un tempfs ou un ramdisk. N'utilisez pas de tablespace dans un tempfs etc, c'est fragile et inutile. Il vaut mieux utiliser un tablespace normal et créer des UNLOGGEDtables - il fonctionnera de la même manière. Et il ne traitera pas les performances WAL et les facteurs fsync à moins que vous ne preniez des mesures qui risquent de compromettre l'intégrité de l'ensemble de la base de données (voir stackoverflow.com/q/9407442/398670 ). Ne fais pas ça.
Craig Ringer
29

Il est maintenant possible d'exécuter une instance en mémoire de PostgreSQL dans vos tests JUnit via le composant PostgreSQL intégré d'OpenTable: https://github.com/opentable/otj-pg-embedded .

En ajoutant la dépendance à la bibliothèque intégrée otj-pg ( https://mvnrepository.com/artifact/com.opentable.components/otj-pg-embedded ), vous pouvez démarrer et arrêter votre propre instance de PostgreSQL dans votre @Before et @Afer crochets:

EmbeddedPostgres pg = EmbeddedPostgres.start();

Ils proposent même une règle JUnit pour que JUnit démarre et arrête automatiquement votre serveur de base de données PostgreSQL pour vous:

@Rule
public SingleInstancePostgresRule pg = EmbeddedPostgresRules.singleInstance();
Rubms
la source
1
Quelle est votre expérience avec ce package six mois plus tard? Fonctionne bien ou est criblé de bugs?
oligofren
@Rubms Avez-vous migré vers JUnit5? Comment utilisez-vous le remplacement du @Ruleavec @ExtendWith? Utilisez simplement le .start()in @BeforeAll?
Frankie Drake
Je n'ai pas migré vers JUnit5, je ne peux donc pas encore répondre à votre question. Désolé.
Rubms
Cela a bien fonctionné. Merci. Utilisez ce qui suit pour créer une source de données dans votre configuration de printemps si vous le souhaitez:DataSource embeddedPostgresDS = EmbeddedPostgres.builder().start().getPostgresDatabase();
Sacky San
12

Vous pouvez utiliser TestContainers pour lancer un conteneur Docker PosgreSQL pour les tests: http://testcontainers.viewdocs.io/testcontainers-java/usage/database_containers/

TestContainers fournit un JUnit @ Rule / @ ClassRule : ce mode démarre une base de données dans un conteneur avant vos tests et la déchire ensuite.

Exemple:

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
la source
7

Il existe maintenant une version en mémoire de PostgreSQL de la société de recherche russe nommée Yandex: https://github.com/yandex-qatools/postgresql-embedded

Il est basé sur le processus d'intégration de Flapdoodle OSS.

Exemple d'utilisation (à partir de la page 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));");

Je l'utilise depuis un certain temps. Ça marche bien.

MISE À JOUR : ce projet n'est plus activement maintenu

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
la source
1
Cela doit exploser de toutes sortes de façons nouvelles et excitantes si vous utilisez plusieurs threads, intégrez un runtime JVM ou Mono, forkez () vos propres processus enfants, ou quelque chose comme ça. Edit : Ce n'est pas vraiment intégré, c'est juste un wrapper.
Craig Ringer
3

Vous pouvez également utiliser les paramètres de configuration PostgreSQL (tels que ceux détaillés dans la question et la réponse acceptée ici ) pour obtenir des performances sans nécessairement recourir à une base de données en mémoire.

Dan
la source
Le principal problème de l'OP est de faire tourner une instance Postgres en mémoire, non pas pour les performances, mais pour simplifier les tests unitaires de démarrage dans un environnement de développement et CI.
triple.vee le
0

Si vous utilisez NodeJS, vous pouvez utiliser pg-mem (avertissement: je suis l'auteur) pour émuler les fonctionnalités les plus courantes d'une base de données postgres.

Vous disposerez d'une base de données en mémoire complète, isolée et indépendante de la plate-forme, répliquant le comportement PG (elle fonctionne même dans les navigateurs ).

J'ai écrit un article pour montrer comment l'utiliser pour vos tests unitaires ici .

Olivier
la source