# Reset PostgreSQL / Timescale (Docker) a obnova z dumpu

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/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 `-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.

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` může běžet až do smazání volume; nebo celý stack sestřelíš v jednom kroku níže.)

---

## 1) Na zdroji: dump

Z kořene projektu (lokálně nebo na stroji, kde běží zdrojová DB):

```bash
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
```

Alternativa z repa: `./scripts/export_ems_db.sh ./ems.dump`.

Přenos na server (příklad):

```bash
scp ./ems.dump root@server:/tmp/ems.dump
```

---

## 2) Ověření zdroje (a případně cíle)

```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';"
```

---

## 3) Zastavit stack a smazat jen DB volume

```bash
docker compose down
docker volume ls | grep db_data
```

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`:

```bash
docker volume rm ems_db_data
```

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** 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ě).

---

## 8) Flyway migrace (EMS)

```bash
cd /opt/ems-deploy
docker compose --env-file .env run --rm flyway migrate
```

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
docker compose --env-file .env up -d
```

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).

---

## 10) Rychlá kontrola

```bash
docker compose exec -T -e PGPASSWORD="$DB_PASSWORD" db \
  psql -U "$DB_USER" -d ems -c "SELECT count(*) FROM ems.site;"
```

---

## 11) Úplně čistá varianta bez mazání stávajícího volume (rollback)

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 exec -T -e PGPASSWORD="$DB_PASSWORD" db \
  psql -U "$DB_USER" -d postgres -c "DROP DATABASE IF EXISTS ems WITH (FORCE);"
docker compose exec -T -e PGPASSWORD="$DB_PASSWORD" db \
  psql -U "$DB_USER" -d postgres -c "CREATE DATABASE ems OWNER \"${DB_USER}\";"
```

Pak znovu krok 7 (restore) a Flyway.

---

## 13) Robustnější strategie (odkaz)

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.

---

## 14) Shrnutí příkazů (server `/opt/ems-deploy`)

```bash
cd /opt/ems-deploy
docker compose --env-file .env stop backend postgrest frontend
docker compose --env-file .env down
docker volume rm <JMÉNO_db_data>   # z: docker volume ls | grep db_data

docker compose --env-file .env up -d db
# 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();"

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`.
