0

Migrating Metabase from H2 to PostgreSQL database: A step-by-step approach to overcoming challenges

Context

image.png

Recently, I had a task to migrate a development Metabase instance used internally by the dev team. The current Metabase was running on Kubernetes with a single node and using the default H2 database engine.

If you haven’t heard about H2 before, here’s a quick introduction:

H2 is a lightweight, fast, open-source relational database management system (RDBMS) written in Java. It supports in-memory and disk-based storage modes and is often used for development, testing, and small-scale applications. H2 is fully compatible with JDBC and SQL standards and can be embedded in Java applications or run as a standalone database. It also provides a web-based console for easy management.

Despite its advantages, H2 has significant drawbacks, mainly related to scalability and reliability. To make Metabase production-ready, we needed to migrate it to PostgreSQL, a more robust and reliable database engine. H2 stores its data as a file in a fixed Persistent Volume (PV).

Metabase provides an official migration guide here, which is quite detailed. However, I wanted to document the process for myself to reflect on the challenges I faced and, hopefully, help others encountering the same issues.

Migration Challenges and Solutions

Following the official documentation, the migration involves these steps:

  1. Confirm that you can connect to your target application database
  2. Back up your H2 application database
  3. Stop the existing Metabase container
  4. Download the JAR
  5. Run the migration command
  6. Start a new Docker container that uses the new app db
  7. Remove the old container that was using the H2 database

To simplify, my steps were:

  1. Extract the database file from the Kubernetes Persistent Volume.
  2. Download the necessary tools and the Metabase JAR file.
  3. Run the migration command.
  4. Start a new Metabase instance with PostgreSQL as the database engine.

First Challenge: Copying the Database File from the Pod

If someone asks you to retrieve a file from a pod, your first thought is probably to use the kubectl cp command. That was my initial approach too.

However, the database file was around 40MB, and every time I ran the command, it would only complete about 90% before getting interrupted.

kubectl cp <namespace>/<pod-name>:<path-in-pod> <local-path>

If a corrupted database file is migrated to the new database, the process will fail.

The download failure could be due to:

  • An unstable VPN connection.
  • The file being too large for kubectl cp to handle reliably.

After multiple failed attempts, I found a better solution: hosting a temporary Python web server inside the pod to serve the file.

# Step 1: Access to source metabase pod using kubectl exec 
kubectl exec -it <metabase-pod-name> -- /bin/sh

# Step 2: Install Python (if not already installed)
apt update && apt install -y python3

# Step 3: Host a simple python web server
cd /path/to/metabase-folder 
python3 -m http.server 8080 --bind 0.0.0.0

If you have direct access to the pod, the web server will be available at pod-ip:8080, allowing you to download the database file via a browser.

If you don’t have direct access, you can create a simple NodePort or LoadBalancer service to expose it externally.

✅ With this approach, the download was significantly faster and completed without interruptions.

Second Challenge: Matching Versions is CRUCIAL!

Now that I had the H2 database file, I needed to install dependencies and download metabase.jar to run the migration.

One key lesson: Version mismatches can break everything. Here are three important versions to check:

  1. Java version:
  • Java must be version 21 or higher, as older versions are no longer supported.
  • I ran into migration failures before realizing this! 😭
  • Check your Java version with:
java -version
  1. PostgreSQL version:
  • The target PostgreSQL version must be 12 or higher.
  • I recommend using the latest LTS version.
  • More details: Supported databases.
  1. Metabase version:
  • The Metabase versions on the source (H2) and destination (PostgreSQL) must be identical.
  • If versions don’t match, the migration might fail or cause issues later.
  • Find the current Metabase version in the Docker image tag.
  • Download the correct JAR version https://downloads.metabase.com/<VERSION>/metabase.jar
  • Example: https://downloads.metabase.com/v0.53.5/metabase.jar

Now that I had:

  • ✅ The source database file
  • ✅ A PostgreSQL destination database
  • ✅ The correct Java environment
  • ✅ The matching Metabase JAR version

I could finally run the migration:

export MB_DB_TYPE=postgres
export MB_DB_CONNECTION_URI="jdbc:postgresql://<host>:5432/metabase?user=<username>&password=<password>"
java --add-opens java.base/java.nio=ALL-UNNAMED -jar metabase.jar load-from-h2 /path/to/metabase.db # do not include .mv.db

The process should complete within a few minutes—unless you use mismatched Metabase versions, like I did 🤣.

Third challenge: Some random errors

I was using Metabase 0.47.2, and during migration, I ran into foreign key constraint errors:

org.postgresql.util.PSQLException: ERROR: insert or update on table "report_dashboardcard" violates foreign key constraint "fk_report_dashboardcard_ref_dashboard_tab_id"
  Detail: Key (dashboard_tab_id)=(1) is not present in table "dashboard_tab".
        at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2676)
        at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2366)
        at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:356)
        at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:316)
        at org.postgresql.jdbc.PgConnection.executeTransactionCommand(PgConnection.java:928)
...
Command failed with exception: ERROR: insert or update on table "report_dashboardcard" violates foreign key constraint "fk_report_dashboardcard_ref_dashboard_tab_id"
  Detail: Key (dashboard_tab_id)=(1) is not present in table "dashboard_tab".

I was confused, how could the schema be different if the Metabase versions matched?

After digging around, I found this post, which explained that Metabase 0.47.2 had a migration bug.

A user suggested upgrading to Metabase 0.47.4, so I downloaded that version and the migration completed successfully! 🎉

End

Thanks for reading! This post was mostly for my own reference, but I hope it helps others facing similar challenges.

If you run into technical issues and need help, feel free to reach out: https://hoangviet.io.vn

Hope you have a great day! 🚀


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí