ЁЯПЧя╕П рдзрдбрд╛ 08 тАФ Migrations: рд╢рд╛рд│рд╛ рдЪрд╛рд▓реВ рдЕрд╕рддрд╛рдирд╛рдЪ рдЦреЛрд▓реАрдЪреЗ рдиреВрддрдиреАрдХрд░рдг
ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 18 рдкреИрдХреА рдзрдбрд╛ 08 ┬╖ рдорд╛рдЧреЗ: lesson-07-concurrency ┬╖ рдкреБрдвреЗ: lesson-09-backups-recovery
ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ
рдзрдбреЗ 01тАУ07, рдЖрдгрд┐ рд╢рд╛рд│рд╛ рдмрдВрдж рди рдХрд░рддрд╛ рдЦреЛрд▓реАрдЪрд╛ рдЖрдХрд╛рд░ рдХрд╕рд╛ рдмрджрд▓рддреЛ: versioned
migration scripts, logbook (schema_migrations), рдЖрдгрд┐ zero-downtime
рдмрджрд▓рд╛рд╕рд╛рдареА expand тЖТ migrate тЖТ contract рдкрджреНрдзрдд. рдЦрд▒реНрдпрд╛ files:
- db/migrate.py тАФ рдиреВрддрдиреАрдХрд░рдг рдХрд░рдгрд╛рд░реА рдЯреАрдо, 30 рдУрд│реА
- db/migrations/ тАФ
001_init,002_add_house,003_index_grades_subject
ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ
рд╢рд╛рд│реЗрд▓рд╛ students рдиреЛрдВрджрд╡рд╣реАрдд рдПрдХ рдирд╡рд╛ column рд╣рд╡рд╛ рдЖрд╣реЗ: house (red, blueтАж). рдЦреЛрд▓реА рдЪрд╛рд▓реВ рдЖрд╣реЗ; clerks рдЖрддреНрддрд╛ рддреНрдпрд╛рдд рд▓рд┐рд╣реАрдд рдЖрд╣реЗрдд. рддреБрдореНрд╣реА рдЖрдард╡рдбрд╛рднрд░ рд╢рд╛рд│рд╛ рдмрдВрдж рдХрд░реВ рд╢рдХрдд рдирд╛рд╣реА.
рдореНрд╣рдгреВрди рдиреВрддрдиреАрдХрд░рдг рдХреНрд░рдорд╛рдВрдХ рдЕрд╕рд▓реЗрд▓реНрдпрд╛ work orders ЁЯПЧя╕П рдореНрд╣рдгреВрди рдХреЗрд▓реЗ рдЬрд╛рддреЗ, git рдордзреНрдпреЗ
рдареЗрд╡рд▓реЗрд▓реЗ, рдХреНрд░рдорд╛рдиреЗ, рдкреНрд░рддреНрдпреЗрдХреА рдПрдХрджрд╛рдЪ рд▓рд╛рдЧреВ рдХреЗрд▓реЗрд▓реЗ, рдЖрдгрд┐ рдПрдХрд╛ logbook рдордзреНрдпреЗ
рдиреЛрдВрджрд╡рд▓реЗрд▓реЗ тАФ schema_migrations тАФ рдореНрд╣рдгрдЬреЗ рдЦреЛрд▓реАрдЪреА рдХреЛрдгрддреАрд╣реА рдкреНрд░рдд рд╕рд╛рдВрдЧреВ рд╢рдХрддреЗ "рдорд╛рдЭреНрдпрд╛рд╡рд░
work orders 001 рддреЗ 003 рдЭрд╛рд▓реНрдпрд╛ рдЖрд╣реЗрдд." рдЯреАрдо рджреЛрдирджрд╛ рдЪрд╛рд▓рд╡рд╛, рджреБрд╕рд▒реНрдпрд╛рдВрджрд╛ рддреА рдХрд╛рд╣реАрдЪ рдХрд░рдд
рдирд╛рд╣реА.
рдЖрдгрд┐ rename рдХрдзреАрдЪ рдПрдХрд╛ рдкрд╛рдпрд░реАрдд рд╣реЛрдд рдирд╛рд╣реА. clerks рд▓рд┐рд╣реАрдд рдЕрд╕рддрд╛рдирд╛рдЪ class рдЪреЗ рдирд╛рд╡
section рдХрд░рд╛рдпрдЪреЗ рдЕрд╕реЗрд▓ рддрд░:
- Expand тАФ
classрдЪреНрдпрд╛ рд╢реЗрдЬрд╛рд░реАsectionрдЬреЛрдбрд╛ (рдЬреБрдирд╛ code рддреНрдпрд╛рдХрдбреЗ рджреБрд░реНрд▓рдХреНрд╖ рдХрд░рддреЛ). - Migrate тАФ рджреЛрдиреНрд╣реАрдд рд▓рд┐рд╣рд┐рдгрд╛рд░рд╛ code ship рдХрд░рд╛; рдЬреБрдиреНрдпрд╛ rows backfill рдХрд░рд╛.
sectionрд╡рд╛рдЪрдгрд╛рд░рд╛ code ship рдХрд░рд╛.- Contract тАФ
classрдХрд╛рдвреВрди рдЯрд╛рдХрд╛.
рдкреНрд░рддреНрдпреЗрдХ рдкрд╛рдпрд░реАрдд рд╢рд╛рд│рд╛ рдЪрд╛рд▓реВ рд░рд╛рд╣рддреЗ; рдкреНрд░рддреНрдпреЗрдХ рдкрд╛рдпрд░реАрдЪреА рдкреНрд░рддреАрд╡рд░ рд░рдВрдЧреАрдд рддрд╛рд▓реАрдо рдХрд░рддрд╛ рдпреЗрддреЗ.
ЁЯЧ║я╕П рдЖрдХреГрддреА
flowchart LR
g["ЁЯУЬ db/migrations/<br/>001_init.sql ┬╖ 002_add_house.sql ┬╖ 003_indexтАжsql"]
m["ЁЯПЧя╕П migrate.py<br/>apply pending, in order, once"]
log["ЁЯз╛ schema_migrations<br/>name ┬╖ applied_at"]
room["ЁЯЧДя╕П school.db<br/>now has students.house"]
g -->|"1"| m -->|"2 each in a transaction"| room
m -->|"3 record"| log
ec["expand тЖТ migrate тЖТ contract:<br/>add new ┬╖ write both ┬╖ backfill ┬╖ read new ┬╖ drop old"]
room -.->|"4 for renames"| ec
тЭУ рдХрд╛рдп
- Migration: schema (рдХрд┐рдВрд╡рд╛ data) version N рд╡рд░реВрди N+1 рд╡рд░ рдмрджрд▓рдгрд╛рд░реА script. рдХреНрд░рдорд╛рдВрдХ рдХрд┐рдВрд╡рд╛ timestamp рдЕрд╕рд▓реЗрд▓реА; рддрд┐рдЪреА рдЧрд░рдЬ рдЕрд╕рд▓реЗрд▓реНрдпрд╛ code рд╕реЛрдмрдд commit рдХреЗрд▓реЗрд▓реА.
- Logbook: рд▓рд╛рдЧреВ рдЭрд╛рд▓реЗрд▓реНрдпрд╛ migrations рдЪрд╛ table. tool рдлрдХреНрдд рдЬреЗ рдХрдореА рдЖрд╣реЗ рддреЗрдЪ рд▓рд╛рдЧреВ рдХрд░рддреЗ. Tools: Flyway, Liquibase, Alembic, Prisma Migrate, Rails тАФ рд╕рдЧрд│реЗ рд╣реАрдЪ рдкрджреНрдзрдд.
- Reversible: рд╢рдХреНрдп рдЕрд╕реЗрд▓ рддреЗрд╡реНрд╣рд╛
downрдкрд╛рдпрд░реА рд▓рд┐рд╣рд╛; рд╢рдХреНрдп рдирд╕реЗрд▓ рддреЗрд╡реНрд╣рд╛ (column рдХрд╛рдврдгреЗ), рдкреНрд░рддреАрд╡рд░ рд░рдВрдЧреАрдд рддрд╛рд▓реАрдо рдХрд░рд╛ рдЖрдгрд┐ backup рдареЗрд╡рд╛ (рдзрдбрд╛ 09). - Expand/contract: рдЖрдзреА рдЬреЛрдбрдгрд╛рд░реЗ рдмрджрд▓ (default рд╕рд╣ рдирд╡рд╛ column, рдирд╡рд╛ table),
рдирд╖реНрдЯ рдХрд░рдгрд╛рд░реЗ рд╢реЗрд╡рдЯреА, рдЖрдгрд┐ рдордзреНрдпреЗ code deploy. live table рд╡рд░ column рдХрдзреАрдЪ рдПрдХрд╛
рдкрд╛рдпрд░реАрдд
RENAMEрдХрд░реВ рдирдХрд╛. - рдореЛрдареНрдпрд╛ tables рд╡рд░рдЪреЗ Backfills рдерд╛рдВрдмреЗ рдШреЗрдд batches рдордзреНрдпреЗ рдЪрд╛рд▓рддрд╛рдд тАФ рдПрдХрдЪ рдкреНрд░рдЪрдВрдб
UPDATEрд╕рд░реНрд╡рд╛рдВрд╕рд╛рдареА рдиреЛрдВрджрд╡рд╣реА lock рдХрд░рддреЛ (рдзрдбрд╛ 07). - SQLite рдЯреАрдк:
ALTER TABLEрдорд░реНрдпрд╛рджрд┐рдд рдЖрд╣реЗ (add column, rename); Postgres default рд╕рд╣ column рд▓рдЧреЗрдЪ рдЬреЛрдбреВ рд╢рдХрддреЛ, рдкрдг рдХрд╛рд╣реА рдмрджрд▓ рд╕рдВрдкреВрд░реНрдг table рдкреБрдиреНрд╣рд╛ рд▓рд┐рд╣рд┐рддрд╛рдд тАФ рдореЛрдареНрдпрд╛ рдмрджрд▓рд╛рдЖрдзреА рддреБрдордЪреНрдпрд╛ database рдЪреНрдпрд╛ notes рд╡рд╛рдЪрд╛.
ЁЯдФ рдХрд╛
рдХрд╛рд░рдг "рдХреЛрдгреАрддрд░реА рд╢реБрдХреНрд░рд╡рд╛рд░реА production рд╡рд░ рд╣рд╛рддрд╛рдиреЗ ALTER TABLE рдЪрд╛рд▓рд╡рд▓реЗ" рд╣рд╛рдЪ рддреЛ рдорд╛рд░реНрдЧ
рдЖрд╣реЗ рдЬреНрдпрд╛рдиреЗ schemas рдЕрдЬреНрдЮрд╛рдд рд╣реЛрддрд╛рдд рдЖрдгрд┐ rollbacks рдлрдХреНрдд рдЖрдард╡рдгреАрдд рд░рд╛рд╣рддрд╛рдд. git рдордзреАрд▓
migrations рдЦреЛрд▓реАрдЪрд╛ рдЖрдХрд╛рд░ review рдХрд░рддрд╛ рдпреЗрдгрд╛рд░рд╛, рдкреБрдиреНрд╣рд╛ рдХрд░рддрд╛ рдпреЗрдгрд╛рд░рд╛ рдЖрдгрд┐ рддрд╛рд▓реАрдо рдХрд░рддрд╛
рдпреЗрдгрд╛рд░рд╛ рдмрдирд╡рддрд╛рдд тАФ рдЖрдгрд┐ rename outage рдмрдиреВ рдирдпреЗ рдпрд╛рдЪрд╛ expand/contract рд╣рд╛ рдПрдХрдореЗрд╡ рдорд╛рд░реНрдЧ рдЖрд╣реЗ.
ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)
db/migrate.py logbook рдмрдирд╡рддреЗ, db/migrations/*.sql рдЪреА рдпрд╛рджреА рдХрд░рддреЗ, рд▓рд╛рдЧреВ рди рдЭрд╛рд▓реЗрд▓реА
рдкреНрд░рддреНрдпреЗрдХ file рдПрдХрд╛ transaction рдордзреНрдпреЗ рд▓рд╛рдЧреВ рдХрд░рддреЗ рдЖрдгрд┐ рддрд┐рдЪреА рдиреЛрдВрдж рдХрд░рддреЗ. 002_add_house
рд╣реА expand рдкрд╛рдпрд░реА рдЖрд╣реЗ; 003 рдирд╡реНрдпрд╛ рдкреНрд░рд╢реНрдирд╛рд▓рд╛ рд▓рд╛рдЧрдгрд╛рд░рд╛ index рдЬреЛрдбрддреЗ.
ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛
python3 db/demo.py >/dev/null # a fresh room (schema.sql already includes 001's tables)
python3 db/migrate.py status # тП│ all three pending
python3 db/migrate.py # applied 001, 002, 003
python3 db/migrate.py # nothing to do тАФ idempotent
python3 -c "import sqlite3; c=sqlite3.connect('db/school.db'); print(c.execute('SELECT name, house FROM students LIMIT 2').fetchall()); print(c.execute('SELECT name FROM schema_migrations').fetchall())"
# your migration: expand тЖТ contract for a rename, in two files
printf -- '-- expand: new column next to the old one\nALTER TABLE homework ADD COLUMN heading TEXT;\nUPDATE homework SET heading = title;\n' > db/migrations/004_homework_heading_expand.sql
python3 db/migrate.py && python3 -c "import sqlite3; print(sqlite3.connect('db/school.db').execute('SELECT title, heading FROM homework').fetchall())"
# (005 тАФ the contract step that drops `title` тАФ only after every reader uses `heading`; write it, don't run it yet)
тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ
status рдЖрдзреА рддреАрди тП│ рджрд╛рдЦрд╡рддреЛ, рдордЧ рддреАрди тЬЕ; рджреБрд╕рд░рд╛ run рдлрдХреНрдд up to date рдЫрд╛рдкрддреЛ; рдкреНрд░рддреНрдпреЗрдХ рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреАрд▓рд╛ house = 'red' рдЖрд╣реЗ; рддреБрдордЪреА 004 рдПрдХрджрд╛рдЪ рд▓рд╛рдЧреВ рд╣реЛрддреЗ рдЖрдгрд┐ heading рд╣реЗ title рдЪреЗ рдкреНрд░рддрд┐рдмрд┐рдВрдм рдЕрд╕рддреЗ. logbook рдордзреНрдпреЗ рдЪрд╛рд░ рдирд╛рд╡реЗ рджрд┐рд╕рддрд╛рдд.
ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ
рдЦреЛрд▓реАрдиреЗ рдкреБрдиреНрд╣рд╛ рди рдмрд╛рдВрдзрддрд╛ рдЪрд╛рд░ рд╡реЗрд│рд╛ рдЖрдХрд╛рд░ рдмрджрд▓рд▓рд╛, рдкреНрд░рддреНрдпреЗрдХ рдмрджрд▓рд╛рдЪреА рдиреЛрдВрдж рдЭрд╛рд▓реА, рдПрдХрд╣реА рджреЛрдирджрд╛ рд▓рд╛рдЧреВ рдЭрд╛рд▓рд╛ рдирд╛рд╣реА тАФ рдЖрдгрд┐ рддреБрдореНрд╣реА рдХрдзреАрдЪ рдХреЛрдгрддреНрдпрд╛рд╣реА clerk рд▓рд╛ рди рдЕрдбрд╡рдгрд╛рд▒реНрдпрд╛ rename рдЪрд╛ expand рдЕрд░реНрдзрд╛ рднрд╛рдЧ рд▓рд┐рд╣рд┐рд▓рд╛.
тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛
- рдЖрдзреАрдЪ рд▓рд╛рдЧреВ рдЭрд╛рд▓реЗрд▓реА migration edit рдХрд░рдгреЗ тАФ рдирд╡реА рд▓рд┐рд╣рд╛; рдЗрддрд┐рд╣рд╛рд╕ рдлрдХреНрдд рдкреБрдвреЗ рдЬреЛрдбрд▓рд╛ рдЬрд╛рддреЛ
- live table рд╡рд░ рдПрдХрд╛ рдкрд╛рдпрд░реАрдд rename рдХрд┐рдВрд╡рд╛ drop рдХрд░рдгреЗ
- рдореЛрдареНрдпрд╛ table рд╡рд░ рдПрдХрдЪ рдкреНрд░рдЪрдВрдб
UPDATEрдореНрд╣рдгреВрди backfill - рдЖрдЬрдЪреНрдпрд╛ data рдЪреНрдпрд╛ рдЖрдХрд╛рд░рд╛рд╡рд░ рдЕрд╡рд▓рдВрдмреВрди рдЕрд╕рд▓реЗрд▓реНрдпрд╛, рдкрдг рддреЛ рди рддрдкрд╛рд╕рдгрд╛рд▒реНрдпрд╛ migrations
- рдирд╖реНрдЯ рдХрд░рдгрд╛рд▒реНрдпрд╛ рдкрд╛рдпрд░реАрдЖрдзреА рдкреНрд░рддреАрд╡рд░ рд░рдВрдЧреАрдд рддрд╛рд▓реАрдо рди рдХрд░рдгреЗ (рдзрдбрд╛ 09 рд╣реА рд╕реБрд░рдХреНрд╖рд╛ рдЬрд╛рд│реА рдЖрд╣реЗ)
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: zero-downtime deploys рд╣реА Kubernetes рдЪреА рд╢рд┐рд╕реНрдд рд╣реЛрдгреНрдпрд╛рдЖрдзреА schema рдЪреА рд╢рд┐рд╕реНрдд рдЖрд╣реЗ. рд░реЛрдЬ ship рдХрд░рдгрд╛рд░реА рдкреНрд░рддреНрдпреЗрдХ team CI рдордзреНрдпреЗ migrations рдЪрд╛рд▓рд╡рддреЗ, default рдореНрд╣рдгреВрди expand/contract рд╡рд╛рдкрд░рддреЗ, рдЖрдгрд┐ рд╣рд╛рддрд╛рдиреЗ рдЪрд╛рд▓рд╡рд▓реЗрд▓рд╛
ALTERрдореНрд╣рдгрдЬреЗ incident рдорд╛рдирддреЗ.
тПня╕П рдкреБрдвреЗ
рдиреВрддрдиреАрдХрд░рдг рдЪреБрдХреВ рд╢рдХрддреЗ; рд╢реБрдХреНрд░рд╡рд╛рд░рдЪреНрдпрд╛ рджреБрдкрд╛рд░реАрд╣реА. Backups рдЖрдгрд┐ recovery тАФ RPO, RTO, рдЖрдгрд┐ рддреБрдореНрд╣реА рдЦрд░реЛрдЦрд░ рдХрд░рддрд╛ рддреА restore рдЪреА рд░рдВрдЧреАрдд рддрд╛рд▓реАрдо.
git checkout lesson-09-backups-recovery