So you have a Grafana with the default storage backend (SQLite) and you want to move it to PostgreSQL ? You’ve come to the right place.
Preparing
First we need to dump the SQLite database, since it’s just a single file simply shut down Grafana to avoid any corruption and copy the grafana.db
somewhere safe. It is usually located at /var/lib/grafana
.
In your Postgres DB create a grafana
user and database then edit your Grafana configuration file to enable postgres storage.
In my case since I am using the official Docker Image it’s a simple matter of adding environment variables :
GF_DATABASE_TYPE=postgres
GF_DATABASE_NAME=grafana
GF_DATABASE_USER=grafana
GF_DATABASE_PASSWORD=XXXXXXX
GF_DATABASE_HOST=hostname:port
If your DB enforces SSL you may want to set GF_DATABASE_SSL_MODE
as well.
Fresh start
We will use pgloader to read our SQLite file and translate it into something Postgres can understand however it will not cut it for the initial schema creation. Instead we’ll let Grafana run a migration on our fresh DB so we have a blank canvas to work on.
Simply start your Grafana instance and wait for it to come up, as soon as the login form is reachable you should have an initialized database in your Postgres DB.
Shut down Grafana for now.
Importing data to Postgres
With our schemas created we’ll now use pgloader
to read and load the SQLite data into the Postgres DB.
Create a grafana.load
file next to your grafana.db
with the following content :
load database
from sqlite:///path/to/grafana.prod.db
into postgresql://user:password@host:port/grafana?sslmode=prefer
with data only, reset sequences
set work_mem to '16MB', maintenance_work_mem to '512 MB';
Make sure to replace the path and connection strings on line 2 and 3.
Now we simply run pgloader grafana.load
:
pgloader grafana.load
2023-05-29T15:24:09.004954-04:00 LOG pgloader version "3.6.9"
2023-05-29T15:24:09.006273-04:00 LOG Data errors in '/private/tmp/pgloader/'
2023-05-29T15:24:09.006308-04:00 LOG Parsing commands from file #P"/.../grafana.load"
2023-05-29T15:24:09.678189-04:00 LOG Migrating from #<SQLITE-CONNECTION sqlite:///.../grafana.db {700E9F0973}>
2023-05-29T15:24:09.678306-04:00 LOG Migrating into #<PGSQL-CONNECTION pgsql://XXX>
2023-05-29T15:24:10.654083-04:00 WARNING Source column "public"."migration_log"."id" is casted to type "bigserial" which is not the same as "integer", the type of current target database column "public"."migration_log"."id".
2023-05-29T15:24:10.654547-04:00 WARNING Source column "public"."migration_log"."migration_id" is casted to type "text" which is not the same as "character varying", the type of current target database column "public"."migration_log"."migration_id".
2023-05-29T15:24:10.654616-04:00 WARNING Source column "public"."migration_log"."success" is casted to type "bigint" which is not the same as "boolean", the type of current target database column "public"."migration_log"."success".
[...]
2023-05-29T13:32:12.794507-04:00 ERROR Database error 23505: duplicate key value violates unique constraint "org_pkey"
DETAIL: Key (id)=(1) already exists.
CONTEXT: COPY org, line 1
2023-05-29T13:32:13.285749-04:00 ERROR Database error 23505: duplicate key value violates unique constraint "user_pkey1"
DETAIL: Key (id)=(1) already exists.
CONTEXT: COPY user, line 1
2023-05-29T13:32:13.286581-04:00 ERROR Database error 23505: duplicate key value violates unique constraint "migration_log_pkey"
DETAIL: Key (id)=(1) already exists.
CONTEXT: COPY migration_log, line 1
2023-05-29T13:32:14.479374-04:00 ERROR Database error 23505: duplicate key value violates unique constraint "org_user_pkey"
DETAIL: Key (id)=(1) already exists.
CONTEXT: COPY org_user, line 1
2023-05-29T13:32:16.676660-04:00 ERROR Database error 23505: duplicate key value violates unique constraint "dashboard_acl_pkey"
DETAIL: Key (id)=(1) already exists.
CONTEXT: COPY dashboard_acl, line 1
2023-05-29T13:32:17.304534-04:00 ERROR Database error 23505: duplicate key value violates unique constraint "UQE_server_lock_operation_uid"
DETAIL: Key (operation_uid)=(cleanup expired auth tokens) already exists.
CONTEXT: COPY server_lock, line 1
2023-05-29T13:32:20.856544-04:00 ERROR Database error 23505: duplicate key value violates unique constraint "kv_store_pkey"
DETAIL: Key (id)=(1) already exists.
CONTEXT: COPY kv_store, line 1
2023-05-29T13:32:28.301681-04:00 LOG report summary reset
table name errors rows bytes total time
-------------------------- --------- --------- --------- --------------
fetch 0 0 0.000s
fetch meta data 0 61 0.044s
Drop Foreign Keys 0 0 0.000s
-------------------------- --------- --------- --------- --------------
[...]
-------------------------- --------- --------- --------- --------------
COPY Threads Completion 0 4 12.382s
Reset Sequences 0 53 2.523s
Create Foreign Keys 0 0 0.000s
Install Comments 0 0 0.000s
-------------------------- --------- --------- --------- --------------
Total import time 7 538 4.9 MB 14.905s
At first you’ll see warnings, those are expected as column types between SQLite and Postgres don’t match 1:1 but this is fine, types will be automatically casted to the nearest.
However you may also encounter errors like this one :
2023-05-29T13:32:12.794507-04:00 ERROR Database error 23505: duplicate key value violates unique constraint "org_pkey"
DETAIL: Key (id)=(1) already exists.
CONTEXT: COPY org, line 1
This occurs because Grafana, while building the database, also created some default entities like a default organization, a default admin user etc. You’ll want to make a list of all the tables that failed the migration.
Almost there
Since we now have a database in an unknown state (some tables migrated, some failed) we want to start again from a clean state.
Drop the grafana
database from the Postgres DB and recreate-it, restart Grafana to let it build the schemas once again then shut it down.
On your Postgres DB you’ll want to TRUNCATE
all the failing tables to get rid of all the auto-generated rows conflicting with your existing data.
TRUNCATE TABLE org;
TRUNCATE TABLE user;
TRUNCATE TABLE migration_log;
TRUNCATE TABLE org_user;
TRUNCATE TABLE dashboard_acl;
TRUNCATE TABLE server_lock;
TRUNCATE TABLE kv_store;
...
At this point you should be able to execute pgloader again without errors :
pgloader grafana.load
2023-05-29T15:24:09.004954-04:00 LOG pgloader version "3.6.9"
2023-05-29T15:24:09.006273-04:00 LOG Data errors in '/private/tmp/pgloader/'
2023-05-29T15:24:09.006308-04:00 LOG Parsing commands from file #P"/.../grafana.load"
2023-05-29T15:24:09.678189-04:00 LOG Migrating from #<SQLITE-CONNECTION sqlite:///.../grafana.db {700E9F0973}>
2023-05-29T15:24:09.678306-04:00 LOG Migrating into #<PGSQL-CONNECTION pgsql://XXX>
2023-05-29T15:24:10.654083-04:00 WARNING Source column "public"."migration_log"."id" is casted to type "bigserial" which is not the same as "integer", the type of current target database column "public"."migration_log"."id".
2023-05-29T15:24:10.654547-04:00 WARNING Source column "public"."migration_log"."migration_id" is casted to type "text" which is not the same as "character varying", the type of current target database column "public"."migration_log"."migration_id".
2023-05-29T15:24:10.654616-04:00 WARNING Source column "public"."migration_log"."success" is casted to type "bigint" which is not the same as "boolean", the type of current target database column "public"."migration_log"."success".
[...]
2023-05-29T15:24:21.045861-04:00 LOG report summary reset
table name errors rows bytes total time
--------------------------- --------- --------- --------- --------------
fetch 0 0 0.000s
fetch meta data 0 63 0.044s
Drop Foreign Keys 0 0 0.000s
--------------------------- --------- --------- --------- --------------
"user" 0 20 4.2 kB 0.530s
migration_log 0 487 81.1 kB 0.562s
org 0 1 0.1 kB 0.546s
dashboard_tag 0 1 0.0 kB 1.023s
dashboard_provisioning 0 0 1.093s
api_key 0 0 1.517s
quota 0 0 1.585s
star 0 5 0.0 kB 0.814s
session 0 0 2.090s
org_user 0 20 1.1 kB 1.070s
playlist_item 0 0 2.058s
dashboard 0 36 1.0 MB 1.420s
alert 0 12 21.0 kB 2.549s
annotation 0 4490 902.4 kB 2.831s
data_source 0 2 0.5 kB 1.555s
dashboard_snapshot 0 1 6.1 kB 1.968s
plugin_setting 0 0 2.042s
playlist 0 0 2.493s
preferences 0 3 0.3 kB 2.600s
alert_notification 0 6 1.6 kB 2.944s
test_data 0 0 3.071s
team 0 6 0.3 kB 3.533s
dashboard_acl 0 12 0.7 kB 3.642s
login_attempt 0 0 4.112s
alert_notification_state 0 22 0.8 kB 4.214s
user_auth_token 0 12 3.8 kB 4.607s
temp_user 0 9 1.2 kB 4.703s
annotation_tag 0 0 5.115s
alert_instance 0 16 4.6 kB 5.137s
alert_rule_version 0 302 548.1 kB 5.674s
library_element 0 0 5.595s
ngalert_configuration 0 0 6.066s
data_keys 0 110 21.5 kB 6.111s
role 0 28 3.1 kB 6.533s
user_role 0 19 0.6 kB 6.532s
query_history 0 1 0.3 kB 7.007s
query_history_star 0 0 6.989s
secrets 0 2 0.4 kB 7.472s
entity_event 0 0 7.464s
file 0 0 8.040s
seed_assignment 0 0 7.973s
folder 0 0 8.446s
dashboard_version 0 406 14.8 MB 4.523s
team_member 0 22 1.2 kB 3.185s
tag 0 0 3.709s
user_auth 0 19 33.9 kB 4.238s
server_lock 0 4 0.2 kB 4.762s
cache_data 0 0 4.839s
alert_rule_tag 0 0 5.298s
short_url 0 2 0.3 kB 5.343s
alert_rule 0 15 26.0 kB 5.791s
alert_configuration 0 1 3.1 kB 5.826s
library_element_connection 0 0 6.253s
kv_store 0 5 0.5 kB 6.292s
permission 0 160 13.1 kB 6.720s
team_role 0 6 0.2 kB 6.786s
builtin_role 0 3 0.2 kB 7.238s
provenance_type 0 0 7.297s
alert_image 0 0 7.713s
correlation 0 0 7.783s
dashboard_public 0 0 8.211s
file_meta 0 0 8.265s
alert_configuration_history 0 19 78.7 kB 8.362s
--------------------------- --------- --------- --------- --------------
COPY Threads Completion 0 4 8.694s
Reset Sequences 0 55 0.837s
Create Foreign Keys 0 0 0.000s
Install Comments 0 0 0.000s
--------------------------- --------- --------- --------- --------------
Total import time ✓ 6285 17.5 MB 9.532s
Restart Grafana and you should now be able to login with your usual credentials and find all your dashboards, all backed by Postgres.