diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml new file mode 100644 index 0000000..e2d0689 --- /dev/null +++ b/.idea/data_source_mapping.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/db-forest-config.xml b/.idea/db-forest-config.xml new file mode 100644 index 0000000..3c4438f --- /dev/null +++ b/.idea/db-forest-config.xml @@ -0,0 +1,10 @@ + + + + . + ---------------------------------------- + 1:0:26ebef46-ff23-475b-8adc-082723263a02 + 2:0:65d65f76-7a9d-423e-8815-7d8efa5fd7f1 + . + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..5e9b2aa --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/db/migration/V035__postgrest_ems_anon_schema_usage.sql b/db/migration/V035__postgrest_ems_anon_schema_usage.sql new file mode 100644 index 0000000..53277a6 --- /dev/null +++ b/db/migration/V035__postgrest_ems_anon_schema_usage.sql @@ -0,0 +1,4 @@ +-- Po pg_restore --no-acl často chybí GRANT USAGE ON SCHEMA ems pro ems_anon. +-- PostgREST pak vrací: permission denied for schema ems (42501). + +GRANT USAGE ON SCHEMA ems TO ems_anon; diff --git a/db/views/R__z_postgrest_ems_anon_grants.sql b/db/views/R__z_postgrest_ems_anon_grants.sql index 3400dbe..218aa12 100644 --- a/db/views/R__z_postgrest_ems_anon_grants.sql +++ b/db/views/R__z_postgrest_ems_anon_grants.sql @@ -34,3 +34,5 @@ GRANT SELECT ON ems.market_price_stats TO ems_anon; GRANT SELECT ON ems.tuv_usage_stats TO ems_anon; GRANT SELECT ON ems.baseline_load_forecast_accuracy TO ems_anon; GRANT SELECT ON ems.vw_baseline_load_forecast_accuracy_daily TO ems_anon; + + diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 2b04ec8..5aa1036 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -25,7 +25,7 @@ services: retries: 5 flyway: - image: flyway/flyway:10 + image: flyway/flyway:12 depends_on: db: condition: service_healthy diff --git a/docker-compose.yml b/docker-compose.yml index 1c7a5e9..822e47f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,7 +18,7 @@ services: retries: 5 flyway: - image: flyway/flyway:10 + image: flyway/flyway:12 depends_on: db: condition: service_healthy diff --git a/docs/database-reset-and-restore.md b/docs/database-reset-and-restore.md index 3c061ab..e04c3f3 100644 --- a/docs/database-reset-and-restore.md +++ b/docs/database-reset-and-restore.md @@ -1,194 +1,282 @@ # Reset PostgreSQL / Timescale (Docker) a obnova z dumpu -Přesný postup pro **smazání dat databáze** (volume) a **nový import** `pg_dump -Fc` na serveru typu `/opt/ems-deploy`. Po resetu zmizí veškerá data v DB EMS. +Postup pro **smazání dat databáze** (volume), **nový čistý cluster** a **obnovu** z `pg_dump -Fc`. Platí stejně **na lokálu** i **na cílovém serveru** (např. `/opt/ems-deploy`): neprováděj in-place „upgrade“ starého datadisku jen přepnutím Docker image mezi major verzemi PostgreSQL. -**Související skripty v repu:** `scripts/import_ems_db.sh`, `scripts/export_ems_db.sh`. +**Související skripty v repu:** `scripts/export_ems_db.sh`, `scripts/import_ems_db.sh`. Obecný kontext self-hosted deploye: [deployment-self-hosted.md](deployment-self-hosted.md). --- +## Proč ne „jen změnit image“ z PG16 na PG18 + +Major upgrade PostgreSQL se má dělat **`pg_upgrade`** nebo **dump/restore**. Prosté přepnutí image u existujícího volume nestačí a může vést k poškozenému nebo nekompatibilnímu datadir. Oficiální dokumentace: [PostgreSQL: pg_upgrade](https://www.postgresql.org/docs/current/pgupgrade.html). + +V Docker Compose je **dump/restore** obvykle jednodušší než `pg_upgrade` (potřebuješ staré i nové binárky a dva datadiry najednou). Bezpečná cesta: **nový prázdný cluster** (nový volume) + **restore zálohy**. + +--- + +## Sladění verzí TimescaleDB + +Zdroj zálohy a cílový kontejner musí mít **sladěnou verzi rozšíření TimescaleDB** vůči tomu, co očekává image — jinak často narazíš na **catalog mismatch** při restore nebo po něm. Před migrací ověř na obou stranách: + +```bash +docker compose exec -T db psql -U "$DB_USER" -d ems -c "select version();" +docker compose exec -T db psql -U "$DB_USER" -d ems -c "select extname, extversion from pg_extension where extname='timescaledb';" +``` + +Kontext a restore workflow: [TimescaleDB: Troubleshooting (self-hosted)](https://docs.timescale.com/self-hosted/latest/troubleshooting/), [Upgrade TimescaleDB in Docker](https://docs.timescale.com/self-hosted/latest/upgrades/upgrade-docker/). + +V repozitáři je pro nové instalace očekávaný image např. `timescale/timescaledb:2.26.1-pg18` (viz `docker-compose.yml`). Pinni tag na cíli stejně jako u zdroje dumpu. + +--- + ## 0. Před začátkem -- Máš zálohu `.dump` (custom format), ideálně z `scripts/export_ems_db.sh` nebo ekvivalentního `pg_dump -Fc`. -- **`DB_USER` / `DB_PASSWORD` v `.env` na cíli** odpovídají tomu, co očekává init kontejneru `db` (po smazání volume se DB vytvoří znovu se stejnými údaji z `.env`). -- Timescale: image `timescale/timescaledb:latest-pg16` se v čase mění. Záloha ze **staršího** Timescale na **novější** imagi vyžaduje po restore `ALTER EXTENSION timescaledb UPDATE` — to už dělá `import_ems_db.sh`. Při problémech zvaž **pin** imagi (např. `:2.25.2-pg16`) stejně jako u zdroje dumpu. -- **`pg_restore` nikdy s `-j`** (paralelní restore) u Timescale. +- Máš zálohu `.dump` (custom format `-Fc`), ideálně podle kroku 1 níže nebo `./scripts/export_ems_db.sh`. +- **`DB_USER` / `DB_PASSWORD` v `.env`** na cíli odpovídají initu kontejneru `db` (po smazání volume se DB založí znovu z `.env`). +- **`pg_restore` nikdy s `-j`** (paralelní restore) u Timescale — může rozbít pořadí objektů u hypertable. ---- - -## 1. (Volitelně) Export z vývojového stroje - -Z kořene repa, kde běží lokální `docker compose` a `.env` s `DB_USER` / `DB_PASSWORD`: - -```bash -cd /cesta/k/ems-cursor -./scripts/export_ems_db.sh ~/ems_zaloha_$(date +%Y%m%d_%H%M%S).dump -``` - -Přenos na server (příklad): - -```bash -scp ~/ems_zaloha_*.dump root@server:/tmp/ems.dump -``` - ---- - -## 2. Na serveru: zastavit služby, které píší do DB - -Aby při mazání volume a restore nebyl konflikt: +Na serveru před mazáním volume často zastav služby, které píší do DB: ```bash cd /opt/ems-deploy docker compose --env-file .env stop backend postgrest frontend ``` -(`db` nech může běžet do kroku 4, nebo všechno zastavit — viz krok 3.) +(`db` může běžet až do smazání volume; nebo celý stack sestřelíš v jednom kroku níže.) --- -## 3. Zastavit stack a smazat datový volume Postgresu +## 1) Na zdroji: dump + +Z kořene projektu (lokálně nebo na stroji, kde běží zdrojová DB): ```bash -cd /opt/ems-deploy -docker compose --env-file .env down +docker compose exec -T db pg_dump \ + -U "$DB_USER" \ + -d ems \ + -Fc \ + --no-owner \ + --no-privileges \ + -f /tmp/ems.dump + +docker compose cp db:/tmp/ems.dump ./ems.dump +ls -lh ./ems.dump ``` -**Jméno volume** závisí na názvu Compose projektu (složka nebo `COMPOSE_PROJECT_NAME`). Zjisti ho: +Alternativa z repa: `./scripts/export_ems_db.sh ./ems.dump`. + +Přenos na server (příklad): ```bash -docker volume ls | grep -E 'db_data|ems' +scp ./ems.dump root@server:/tmp/ems.dump ``` -Typicky u deploye z `/opt/ems-deploy` uvidíš něco jako `ems-deploy_db_data` (prefix `{project}_db_data` k klíči `db_data:` z compose). - -**Smaž volume** (doplň přesný název z výpisu): - -```bash -docker volume rm ems-deploy_db_data -``` - -Tím zmizí **celý** obsah dat Postgresu v tom projektu. Ostatní Docker volume (pokud nějaké máš) se nedotknou. - --- -## 4. Naběhnout jen databázi +## 2) Ověření zdroje (a případně cíle) ```bash -cd /opt/ems-deploy -docker compose --env-file .env up -d db -docker compose --env-file .env ps +docker compose exec -T db psql -U "$DB_USER" -d ems -c "select version();" +docker compose exec -T db psql -U "$DB_USER" -d ems -c "select extname, extversion from pg_extension where extname='timescaledb';" ``` -Počkej, až je služba `db` **healthy** (`pg_isready` v healthchecku). - -Init image vytvoří prázdnou databázi **`ems`** a uživatele z `POSTGRES_USER` / `POSTGRES_PASSWORD`. - --- -## 5. Import dumpu (Timescale hooks + restore) - -Dump musí být na serveru, např. `/tmp/ems.dump`. +## 3) Zastavit stack a smazat jen DB volume ```bash -cd /opt/ems-deploy -bash app/scripts/import_ems_db.sh /tmp/ems.dump +docker compose down +docker volume ls | grep db_data ``` -Skript v kontejneru `db` postupně: +Smaž volume pro databázi. Název závisí na **Compose project name** (složka nebo `COMPOSE_PROJECT_NAME`), typicky `{projekt}_db_data`, např. `ems_db_data` nebo `ems-deploy_db_data`: -1. `CREATE EXTENSION IF NOT EXISTS timescaledb` -2. `SELECT timescaledb_pre_restore();` -3. `pg_restore` **bez** `-j` (`--clean --if-exists --no-owner --no-acl`) -4. `ALTER EXTENSION timescaledb UPDATE;` (srovnání katalogu se verzí v imagi) -5. `SELECT timescaledb_post_restore();` +```bash +docker volume rm ems_db_data +``` -Výjimky: `EMS_SKIP_TIMESCALE_RESTORE_HOOKS=1` v prostředí přeskočí body 2 a 5 (jen výjimečně). +Jestli nechceš hledat přesný název a máš jen compose-definované volumes: + +```bash +docker compose down -v +``` + +**Pozor:** `down -v` smaže volumes definované v daném compose souboru — lokální DB tím kompletně zahodíš (v tomto scénáři je to záměr, obnova jde z dumpu). + +--- + +## 4) Compose na cílové verzi Postgresu / Timescale + +V `docker-compose.yml` nastav konkrétní tag, např.: + +```yaml +services: + db: + image: timescale/timescaledb:2.26.1-pg18 +``` + +Nespoléhej na „upgrade“ starého volume změnou image — viz úvod. + +--- + +## 5) Nový prázdný cluster (jen `db`) + +```bash +docker compose up -d db +``` + +Počkej na **healthy** (`pg_isready` v healthchecku). Init vytvoří prázdnou databázi **`ems`** a uživatele z `POSTGRES_USER` / `POSTGRES_PASSWORD`. + +Ověření: + +```bash +docker compose exec -T db psql -U "$DB_USER" -d ems -c "select version();" +docker compose exec -T db psql -U "$DB_USER" -d ems -c "select extname, extversion from pg_extension where extname='timescaledb';" +``` + +--- + +## 6) Nahraj dump do kontejneru + +```bash +docker compose cp ./ems.dump db:/tmp/ems.dump +``` + +Na serveru může být cesta `/tmp/ems.dump` už na disku — pak `cp` z hosta odpovídá tvé cestě k souboru. + +--- + +## 7) Restore (Timescale pre / post) + +Nejdřív příprava podle dokumentace Timescale (full restore): + +```bash +docker compose exec -T db psql -U "$DB_USER" -d ems -c "select public.timescaledb_pre_restore();" +``` + +Restore: + +```bash +docker compose exec -T db pg_restore \ + -U "$DB_USER" \ + -d ems \ + --no-owner \ + --no-privileges \ + -v \ + /tmp/ems.dump +``` + +Dokončení: + +```bash +docker compose exec -T db psql -U "$DB_USER" -d ems -c "select public.timescaledb_post_restore();" +``` + +**Alternativa:** `bash scripts/import_ems_db.sh /tmp/ems.dump` (z kořene repa na serveru s naklonovanou aplikací) — skript doplní `CREATE EXTENSION`, `timescaledb_pre_restore`, `pg_restore` s `--clean --if-exists`, pak **`ALTER EXTENSION timescaledb UPDATE`** (srovnání katalogu zálohy s verzí v imagi) a `timescaledb_post_restore`. Na úplně čerstvém volume je ruční sekvence výše ekvivalentní v principu; skript je praktičtější při opakovaném importu na serveru. **Časté jevy:** -- `pg_restore: warning: errors ignored` — často kvůli FK na hypertable (`ALTER TABLE ONLY`). Po importu **vždy** Flyway (migrace **V034** doplní chybějící FK). -- Selhání **catalog version mismatch** — typicky vyřeší `ALTER EXTENSION` ve skriptu; jinak pin Timescale imagi nebo nový dump po upgrade zdroje. +- `pg_restore: warning: errors ignored` — často kvůli FK na hypertable (`ALTER TABLE ONLY`). Po importu **vždy** Flyway (migrace **V034** a okolí doplňují chybějící FK podle verze repa). +- **Catalog version mismatch** — sladit Timescale tag na cíli se zdrojem nebo pořídit nový dump po upgrade zdroje; skript `import_ems_db.sh` řeší část přes `ALTER EXTENSION timescaledb UPDATE`. + +Výjimka v prostředí: `EMS_SKIP_TIMESCALE_RESTORE_HOOKS=1` u skriptu přeskočí pre/post (jen výjimečně). --- -## 6. Flyway migrace - -Doplní schéma oproti dumpu (včetně oprav PK/chunků/FK z V032–V034 podle verze repa): +## 8) Flyway migrace (EMS) ```bash cd /opt/ems-deploy docker compose --env-file .env run --rm flyway migrate ``` ---- - -## 7. Celý stack +Lokálně z kořene repa (bez `--env-file`, pokud používáš výchozí `.env`): + +```bash +docker compose run --rm flyway migrate +``` + +--- + +## 9) Celý stack + +```bash +docker compose up -d +``` + +Na serveru např.: ```bash -cd /opt/ems-deploy docker compose --env-file .env up -d ``` -Nebo jednorázový deploy z aplikace: - -```bash -/opt/ems-deploy/deploy.sh -``` - -(`deploy.sh` dělá git sync, `flyway migrate`, `build`, `up -d` — pokud už jsi Flyway spustil v kroku 6, znovu obvykle jen doplní repeatable migrace.) +nebo `/opt/ems-deploy/deploy.sh` (git sync, flyway, build, `up -d` — pokud už je Flyway z kroku 8 hotový, znovu typicky jen doplní repeatable migrace). --- -## 8. Rychlá kontrola +## 10) Rychlá kontrola ```bash -docker compose -f /opt/ems-deploy/docker-compose.yml --env-file /opt/ems-deploy/.env ps -``` - -Volitelně SQL (z hosta přes `docker compose exec`): - -```bash -docker compose --env-file .env exec -T -e PGPASSWORD="$DB_PASSWORD" db \ +docker compose exec -T -e PGPASSWORD="$DB_PASSWORD" db \ psql -U "$DB_USER" -d ems -c "SELECT count(*) FROM ems.site;" ``` --- -## 9. Slabší varianta bez mazání volume +## 11) Úplně čistá varianta bez mazání stávajícího volume (rollback) -Pokud nechceš mazat celý volume, lze zkusit přepnout databázi uvnitř (méně typické u Timescale; u poškozeného katalogu chunků je **spolehlivější smazat volume**): +Můžeš vedle současného stacku spustit **druhý compose projekt** (jiné `COMPOSE_PROJECT_NAME` nebo jiná složka), **jiný volume** a **jiný mapovaný port**, např.: + +- starý stack na host portu `5432`; +- nový PG18 dočasně na `5433`; +- restore do nového, ověření; +- pak starý stack zastavit a volume zahodit, nebo porty přemapovat podle potřeby. + +Tím máš možnost vrátit se ke staré DB, dokud novou neověříš. + +--- + +## 12) Slabší varianta: drop databáze `ems` bez mazání celého volume + +Pokud nechceš mazat celý Postgres volume, lze zrušit jen databázi `ems` a znovu ji vytvořit (méně typické u hlubších problémů s katalogem Timescale; u poškozených chunků je spolehlivější **smazat volume**): ```bash -docker compose --env-file .env exec -T -e PGPASSWORD="$DB_PASSWORD" db \ +docker compose exec -T -e PGPASSWORD="$DB_PASSWORD" db \ psql -U "$DB_USER" -d postgres -c "DROP DATABASE IF EXISTS ems WITH (FORCE);" -docker compose --env-file .env exec -T -e PGPASSWORD="$DB_PASSWORD" db \ +docker compose exec -T -e PGPASSWORD="$DB_PASSWORD" db \ psql -U "$DB_USER" -d postgres -c "CREATE DATABASE ems OWNER \"${DB_USER}\";" ``` -Pak znovu **`import_ems_db.sh`** a **Flyway**. +Pak znovu krok 7 (restore) a Flyway. --- -## 10. Robustnější strategie (odkaz) +## 13) Robustnější strategie (odkaz) -Plný „schema odděleně od dat“ a obnova hypertable přes `create_hypertable` popisuje **Tiger Data / Timescale** v dokumentaci k backupu a restore. Tento repozitář používá praktický jednokrokový `-Fc` restore přes `import_ems_db.sh`; u opakovaných problémů zvaž jejich oficiální postup (pre-data dump bez `_timescaledb*`, data zvlášť). +Plný postup „schéma odděleně od dat“ a obnova hypertable přes `create_hypertable` popisuje Tiger Data / Timescale v dokumentaci k zálohám. Tento repozitář používá praktický jednosouborový `-Fc` restore; při opakovaných problémech zvaž jejich oficiální postup. --- -## 11. Shrnutí příkazů (kostra) +## 14) Shrnutí příkazů (server `/opt/ems-deploy`) ```bash -# server cd /opt/ems-deploy docker compose --env-file .env stop backend postgrest frontend docker compose --env-file .env down -docker volume rm # z docker volume ls +docker volume rm # z: docker volume ls | grep db_data docker compose --env-file .env up -d db -# počkat healthy +# počkat až je db healthy + +docker compose cp /tmp/ems.dump db:/tmp/ems.dump +docker compose --env-file .env exec -T db psql -U "$DB_USER" -d ems -c "select public.timescaledb_pre_restore();" +docker compose --env-file .env exec -T db pg_restore -U "$DB_USER" -d ems --no-owner --no-privileges -v /tmp/ems.dump +docker compose --env-file .env exec -T db psql -U "$DB_USER" -d ems -c "select public.timescaledb_post_restore();" -bash app/scripts/import_ems_db.sh /tmp/ems.dump docker compose --env-file .env run --rm flyway migrate docker compose --env-file .env up -d ``` + +Nebo místo tří `exec` řádků s restore použij `bash app/scripts/import_ems_db.sh /tmp/ems.dump` a pak Flyway + `up -d`.