Ачааллж байна...
Түвшин 5-т тавтай морилно уу — мэргэжлийн дадлын түвшин. Өмнөх дөрвөн түвшинд та SQL-ийг эзэмшиж, өгөгдлийн санг зохион байгуулж, хүчирхэг query бичиж, PostgreSQL-тэй Python-оос ажиллаж сурсан. Энэ түвшин өөр төрлийн асуултад хариулна: эдгээр бүх ур чадварыг бодит, урт хугацааны төсөлд хэрхэн хариуцлагатай хэрэглэх вэ?
Эхний асуулт Бүлэг 4-ийн ALTER хичээлээс үлдсэн. Тэнд бид шинэ багана нэмж, нэр сольж, хүснэгтийг дахин бүтээсэн — өгөгдлийн сангийн дотор бүх зүйл хялбар байсан. Гэвч хичээлийн төгсгөлд "бүтцийн өөрчлөлт өгөгдлийн сангийн дотроо хурдан, энгийн, харин түүнийг ашигладаг бүх зүйлтэй уялдуулах нь хэцүү" гэж дүгнэсэн. Таны програм гурван газар ажилладаг — өөрийн компьютер, туршилтын server, жинхэнэ сайт — бол өөрчлөлт бүрийг гурван удаа, яг ижил дарааллаар хийх ёстой. Гурван сарын дараа "энэ багана хэзээ, яагаад нэмэгдсэн бэ" гэсэн асуултад хариулт хаана ч бичигдээгүй.
Энэ хичээлд эдгээр асуудлыг нэг бүрчлэн харж, тэдгээрийн шийдэл болох migration-ийн санааг танилцуулна. "Хөгжүүлэгчийн хэрэгсэл" сургалтад Git нь кодын өөрчлөлт бүрийг түүхтэй нь хадгалж, багийн гишүүд хоорондоо нийцтэй ажиллах боломж олгодгийг сурсан. Migration бол яг тэр сэтгэлгээг өгөгдлийн сангийн бүтцэд хэрэглэх арга. Энэ хичээлд ямар ч шинэ хэрэгсэл суулгахгүй — санааг ойлгоход л зориулна. Дараагийн хичээлд Alembic хэрэгслээр түүнийг хэрэгжүүлнэ.
Төрийн байгууллагын бүртгэлийн дэвтрийг төсөөлөөд үзээрэй — улсын хэмжээнд хорин аймгийн салбарт ижил маягтын дэвтэр хэрэглэгддэг. Нэг өдөр маягтад "Утасны дугаар" гэсэн шинэ багана нэмэх шийдвэр гарлаа. Хэрэв төв газрын ажилтан зүгээр л утсаар салбар бүрт "баганаа нэмчих" гэж хэлбэл юу болох вэ? Зарим салбар баруун талд, зарим нь зүүн талд нэмнэ; нэг нь "Утас", нөгөө нь "Утасны дугаар" гэж нэрлэнэ; нэг салбарын ажилтан чөлөөтэй байж мэдээгүй үлдэнэ. Нэг жилийн дараа хорин дэвтэр хорин өөр хэлбэртэй болно.
Иймээс албан байгууллагууд маягтын өөрчлөлтийг дугаарласан албан тушаалаар хийдэг: "Тушаал №7: бүртгэлийн маягтын 6-р баганын дараа 'Утасны дугаар' гэсэн багана нэм." Тушаал бүр яг юу өөрчлөгдөхийг тодорхой бичдэг, дугаартай, архивт хадгалагддаг. Салбар бүр "бид хэддүгээр тушаал хүртэл хэрэгжүүлсэн" гэдгээ бүртгэлдээ тэмдэглэдэг. Шинэ салбар нээгдэхэд №1-ээс эхлэн бүх тушаалыг дарааллаар нь хэрэгжүүлж, бусадтай яг ижил маягттай болно.
Энэ бол migration — дэвтрийн маягтыг албан ёсоор шинэчлэх тушаал. Migration файл бүр нэг бүтцийн өөрчлөлтийг агуулж, дугаартай, Git-д хадгалагддаг, өгөгдлийн сан бүр "хэддүгээр migration хүртэл хэрэгжсэн" гэдгээ өөртөө тэмдэглэдэг.
create_all хангалтгүй болох мөчFastAPI сургалтад таны програм эхлэхэд SQLModel.metadata.create_all(engine) гэсэн мөр ажилладаг байсан. Бүлэг 1-ийн анхны хичээлд энэ мөр ар талдаа CREATE TABLE илгээдэг гэж харсан. Энэ нь хүснэгт байхгүй үед төгс ажилладаг. Харин хүснэгт аль хэдийн байхад загварт шинэ талбар нэмбэл юу болох вэ? Үүнийг Бүлэг 11-ийн pg-practice хавтсанд туршъя.
Таны Номын сангийн API-д номд үнэлгээ (rating) нэмэх шийдвэр гарлаа гэж бодъё. create_all_test.py:
python
import os
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Book(SQLModel, table=True):
__tablename__ = "books"
id: int | None = Field(default=None, primary_key=True)
title: str
year: int | None = None
rating: int | None = None # шинэ талбар
engine = create_engine(os.environ["DATABASE_URL"], echo=True)
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
books = session.exec(select(Book)).all()
print(books)Энэ код Бүлэг 10-т үүсгэсэн library өгөгдлийн сангийн books хүснэгтийг SQLModel-ээр тайлбарлаж, загварт rating гэсэн шинэ талбар нэмсэн. Дараа нь create_all дуудаж, номнуудыг уншина. Ажиллуулна (DATABASE_URL нь library руу заасан байх ёстой):
Echo гаралтын эхэнд create_all-ийн ажлыг харна:
INFO sqlalchemy.engine.Engine select relname from pg_class c join pg_namespace n ...
INFO sqlalchemy.engine.Engine [generated in 0.00018s] {'table_name': 'books', ...}
INFO sqlalchemy.engine.Engine COMMITcreate_all books хүснэгт байгаа эсэхийг шалгаад, байгааг олоод, юу ч хийсэнгүй. Дараа нь SELECT:
sqlalchemy.exc.ProgrammingError: (psycopg.errors.UndefinedColumn) column books.rating does not exist
LINE 1: SELECT books.id, books.title, books.year, books.rating FROM ...
^Энэ мессеж юу гэж хэлж байна вэ гэвэл (Бүлэг 11-ийн давхаргаар): “books.rating гэдэг багана байхгүй.” Python-ы загвар rating-тай, өгөгдлийн сан rating-гүй — хоёр зөрчилдсөн. create_all бол нэр нь заасан шигээ "бүгдийг үүсгэ" — байхгүйг нь үүсгэдэг, байгааг нь хэзээ ч өөрчилдөггүй. Тэр ALTER TABLE огт илгээдэггүй.
Энэ нь create_all-ийн алдаа биш, харин түүний зориулалтын хязгаар. Хүснэгтийг өөрчлөх нь эрсдэлтэй ажил: багана нэмэх, устгах, нэр солих бүр өгөгдөлд нөлөөлж болно (Бүлэг 4-т DROP COLUMN өгөгдлийг бүрмөсөн устгадгийг харсан). Иймээс SQLAlchemy санаатайгаар үүнийг автоматаар хийдэггүй. FastAPI сургалтад та SQLite файлаа устгаад дахин эхлүүлэх замаар энэ асуудлыг тойрч гарч байсан байж магадгүй — суралцах үед энэ нь хэвийн. Гэвч жинхэнэ хэрэглэгчдийн өгөгдөлтэй жинхэнэ сайт дээр өгөгдлийн санг устгаж болохгүй. Хүснэгтийг өгөгдөлтэй нь хадгалсан хэвээр, хяналттайгаар өөрчлөх арга хэрэгтэй.
Жишээ 1: гараар ALTER хийх — гурван газрын асуудал
Хамгийн энгийн шийдэл мэт санагдах зүйл: psql-ээр ороод ALTER TABLE books ADD COLUMN rating INTEGER; гэж бичих. Таны компьютер дээр энэ нь ажиллана — Анхны жишээний алдаа арилна. Тэгвэл асуудал юунд байна вэ?
Бодит төслийн өдрийг дүрсэлье. Даваа гарагт та өөрийн компьютерийн өгөгдлийн санд psql-ээр rating баганыг нэмж, кодоо Git-д commit хийж, GitHub руу илгээлээ. Мягмар гарагт таны багийн гишүүн кодыг татаж аваад програмаа асаахад Анхны жишээний алдааг авна — түүний өгөгдлийн санд rating байхгүй. Тэр танаас асууна, та тушаалыг санаж хэлнэ — гэхдээ та INTEGER гэж бичсэн үү, REAL үү? CHECK тавьсан уу? Лхагва гарагт кодыг туршилтын server-т байршуулна — хэн нэгэн тэнд ч psql-ээр орж баганыг нэмэх ёстой. Пүрэв гарагт жинхэнэ сайтад байршуулахад хүн бүр мартсан — сайт хэрэглэгч бүрт алдаа өгнө.
Энэ түүх гурван асуудлыг харуулж байна. Нэгдүгээрт, код ба бүтэц салсан — Git кодын өөрчлөлтийг хадгалсан, гэвч түүнд хэрэгтэй бүтцийн өөрчлөлт хэн нэгний толгойд л байсан. Хоёрдугаарт, орчин бүрийг гараар шинэчлэх — хүний санах ойд найдсан ажил эрт орой мартагдана. Гуравдугаарт, түүх байхгүй — “manай books хүснэгт яг ямар өөрчлөлтүүдийг туулсан бэ” гэсэн асуултад хариулах баримт бичиг алга. Бүлэг 6-ийн давхардлын гажуудлын нэгэн адил энэ нь "өгөгдлийн сангийн хуулбарууд хоорондоо зөрөх" асуудал — гэхдээ өгөгдлийн бус, бүтцийн түвшинд.
Жишээ 2: migration файлууд
Migration-ий шийдэл нь бүтцийн өөрчлөлт бүрийг файл болгон бичиж, Git-д кодтой хамт хадгалах явдал. Номын сангийн төслийн migration-ууд ийм харагдаж болох байсан (энэ бол санааны жишээ — одоохондоо ажиллуулах шаардлагагүй):
migrations/
├── 001_create_initial_tables.sql
├── 002_add_rating_to_books.sql
└── 003_add_phone_to_members.sql002_add_rating_to_books.sql файлын агуулга:
sql
-- upgrade
ALTER TABLE books ADD COLUMN rating INTEGER CHECK (rating BETWEEN 1 AND 5);
-- downgrade
ALTER TABLE books DROP COLUMN rating;Файл юу гэж хэлж байна вэ гэвэл: “upgrade — books хүснэгтэд 1-ээс 5-ын хооронд байх ёстой rating бүхэл тоон багана нэм; downgrade — тэр баганыг устга.” Migration бүр хоёр чиглэлтэй: урагш (upgrade) — өөрчлөлтийг хийх, хойш (downgrade) — буцаах. Дугаар нь дарааллыг тогтоодог: 002 нь 001-ийн үүсгэсэн books хүснэгтэд хамаарах тул заавал түүний дараа ажиллах ёстой. Файлын нэр нь өөрчлөлтийг товч тайлбарладаг — Git-ийн commit мессеж шиг.
Одоо Жишээ 1-ийн түүхийг давтъя. Даваа гарагт та migration 002-ыг бичиж, загварт rating нэмж, хоёуланг нь нэг commit-д хийнэ. Мягмар гарагт багийн гишүүн кодыг татаад нэг тушаал ажиллуулна — "хэрэгжээгүй migration-уудыг хэрэгжүүл" — түүний өгөгдлийн сан 002-ыг хэрэгжүүлж, rating баганатай болно. Тушаалыг санах, хэн нэгнээс асуух шаардлагагүй: файл Git-д байна. Туршилтын server ба жинхэнэ сайт ч яг ижил тушаалыг байршуулах процессын нэг хэсэг болгон автоматаар ажиллуулна. Гурван асуудал гурвуулаа шийдэгдсэн: код ба бүтэц нэг commit-д, орчин бүр нэг тушаалаар, бүх түүх файлуудад.
Жишээ 3: өгөгдлийн сан өөрийн хувилбарыг мэддэг
"Хэрэгжээгүй migration-уудыг хэрэгжүүл" гэсэн тушаал аль нь хэрэгжсэн, аль нь хэрэгжээгүйг яаж мэдэх вэ? Зүйрлэлийн салбарууд "бид хэддүгээр тушаал хүртэл хэрэгжүүлсэн" гэдгээ бүртгэлдээ тэмдэглэдэг шиг, өгөгдлийн сан бүр өөрийн дотор жижиг тусгай хүснэгт хадгалдаг. Санааны хувьд ийм харагдана:
schema_version
version
-------
002Энэ хүснэгт юу гэж хэлж байна вэ гэвэл: "энэ өгөгдлийн санд 002 хүртэлх migration-ууд хэрэгжсэн." Migration хэрэгслийг ажиллуулахад тэр энэ хүснэгтийг уншиж, файлуудын жагсаалттай харьцуулна: 001, 002 хэрэгжсэн, 003 хэрэгжээгүй — тэгэхээр зөвхөн 003-ыг ажиллуулж, хүснэгтийг 003 болгон шинэчилнэ. Таны компьютер 003-т, туршилтын server 002-т, жинхэнэ сайт 001-д байж болно — тушаал тус бүрт хэрэгтэй migration-уудыг л ажиллуулна. Шинээр үүссэн хоосон өгөгдлийн сан 001-ээс эхлэн бүгдийг дарааллаар нь хэрэгжүүлж, бусадтай яг ижил бүтэцтэй болно — зүйрлэлийн шинэ салбар. Дараагийн хичээлд Alembic энэ хүснэгтийг alembic_version нэрээр автоматаар үүсгэж, удирдахыг харна.
Жишээ 4: PostgreSQL-ийн migration transaction-д багтдаг
Migration-ий нэг аюул нь Бүлэг 4-ийн хүснэгт дахин бүтээх скриптийн асуудалтай ижил: олон алхамтай migration дундаа бүтэлгүйтвэл өгөгдлийн сан хагас өөрчлөгдсөн байдалд үлдэх. Бүлэг 12-ын эхний хичээлд “PostgreSQL-д CREATE TABLE, DROP TABLE ч transaction-д багтдаг” гэж дурдсан. Үүнийг psql-ээр нотолъё. library өгөгдлийн санд холбогдоод:
library=# BEGIN;
BEGIN
library=*# ALTER TABLE books ADD COLUMN rating INTEGER;
ALTER TABLE
library=*# \d books\d books-ийн үр дүнгийн баганын жагсаалтад одоо rating | integer гэсэн мөр бий. Хүснэгтийн бүтэц өөрчлөгдсөн — гэхдээ transaction дотор. Одоо бодлоо өөрчилье:
library=*# ROLLBACK;
ROLLBACK
library=# \d booksrating багана алга. Энэ тушаалууд юу гэж хэлж байна вэ гэвэл: “transaction эхлүүл; books-д rating багана нэм; бүтцийг харуул; бүгдийг цуцал; бүтцийг дахин харуул.” Хүснэгтийн бүтцийн өөрчлөлт өгөгдлийн өөрчлөлттэй адил бүгд-эсвэл-юу-ч-үгүй байдлаар цуцлагдлаа. Энэ нь migration-д маш чухал: нэг migration гурван багана нэмж, нэг хүснэгт үүсгэх гэж байгаад гурав дахь алхам дээр алдаа гарвал бүх migration цуцлагдаж, өгөгдлийн сан хөндөгдөөгүй үлдэнэ. Migration хэрэгслүүд PostgreSQL дээр migration бүрийг transaction-д ажиллуулдаг. Шударга нэмэлт: бүх өгөгдлийн сан ийм чадвартай биш — зарим нь бүтцийн өөрчлөлтийг тэр даруй хадгалдаг тул тэнд migration бүтэлгүйтвэл гараар засах шаардлагатай болдог. PostgreSQL энэ талаар хамгийн найдвартайн нэг.
Migration-ийг ашиглах хэдэн дүрэм бий. Тэдгээр нь Git-ийн дадлуудтай маш төстэй — "Хөгжүүлэгчийн хэрэгсэл" сургалтын сэтгэлгээ өгөгдлийн санд шилжиж байна.
Бүтцийн өөрчлөлт бүр migration-аар. Жинхэнэ сайт эсвэл туршилтын server-ийн өгөгдлийн санд psql-ээр гараар ALTER бүү хий. Таны компьютер дээрх туршилт ч migration болох ёстой — эс бөгөөс таны өгөгдлийн сан бусдаас "зөрнө."
Хэрэгжсэн migration-ийг бүү засварла. Migration 002 туршилтын server ба жинхэнэ сайтад хэрэгжсэний дараа түүний файлыг өөрчилбөл өөрчлөлт тэдгээрт хэзээ ч хүрэхгүй — тэд 002-ыг аль хэдийн "хийсэн" гэж тэмдэглэсэн. Алдаа олдвол шинэ migration (004) бичиж засна. Энэ нь Git-д түүхийг дахин бичихгүй, шинэ commit хийдэгтэй адил.
Migration ба кодыг хамт commit хий. Загварт rating нэмсэн commit нь rating-ийн migration-ийг агуулах ёстой. Ингэснээр кодын аль ч хувилбарыг татаж авсан хүн түүнд таарах бүтцийг гаргаж авч чадна.
Downgrade-ийн шударга хязгаар. Migration-ий хойш чиглэл бүтцийг буцааж чадна, харин өгөгдлийг үргэлж биш. Жишээ 2-ын downgrade rating баганыг устгадаг — тэгвэл хэрэглэгчдийн өгсөн бүх үнэлгээ алга болно. Дахин upgrade хийхэд багана буцаж ирнэ, гэхдээ хоосон. Downgrade бол туршилт, алдааг хурдан засахад тустай хэрэгсэл, харин жинхэнэ өгөгдөлтэй орчинд backup-ийн орлох биш — Бүлэг 15-д backup-ийг бодитоор хийнэ.
Хэрэв сонирхож байвал Анхны жишээний create_all_test.py-ийг ажиллуулахаасаа өмнө psql-ээр BEGIN; ALTER TABLE books ADD COLUMN rating INTEGER; хийгээд (COMMIT хийлгүйгээр) Python скриптийг ажиллуулж үзээрэй. SELECT ажиллах уу? (Сэжүүр: Бүлэг 12-ын тусгаарлагдмал байдал — хадгалаагүй өөрчлөлт бусад холболтод харагдах уу?) Дараа нь psql-д ROLLBACK хийнэ. Мөн өөрийн FastAPI сургалтын төслийн загваруудыг харж, өнөөг хүртэл хийсэн бүтцийн өөрчлөлтүүдийг migration файлын нэр болгон жагсааж үзээрэй — 001-ээс эхлэн. Эдгээр нь зөвхөн санал бөгөөд дараагийн хичээлүүд тэдгээрийг хийсэн гэж тооцохгүй.
create_all-д бүтцийн өөрчлөлтийг найдах. Анхны жишээнд харсанчлан шинэ талбар нэмэхэд column ... does not exist гарна. Энэ мессеж нь “Python-ы загвар ба өгөгдлийн сан зөрчилдсөн” гэсэн дохио — шийдэл нь migration, create_all биш. Бодит төслүүдэд migration хэрэглэж эхэлсний дараа create_all-ийг ерөөсөө ашиглахаа больдог — энэ сургалтын төгсгөлийн төсөлд яагаад гэдгийг тайлбарлана.
Гараар хийсэн өөрчлөлтөөс үүссэн зөрүү. Хэн нэгэн жинхэнэ сайтын өгөгдлийн санд psql-ээр багана нэмбэл migration-ууд түүний тухай мэдэхгүй. Дараа нь ижил баганыг нэмэх migration ирэхэд column "rating" of relation "books" already exists гэсэн алдаа гарна — “books relation-ий rating багана аль хэдийн байна.” Бүтцийн бүх өөрчлөлтийг migration-аар хий.
Хэрэгжсэн migration-ийг засварлах. Таны компьютерт зассан migration сайхан ажиллана, гэвч бусад орчинд хуучин хувилбар нь хэрэгжсэн тул өгөгдлийн сангууд чимээгүйхэн зөрнө. Ийм зөрүү хэдэн сарын дараа, тайлбарлахад хамгийн хэцүү үед илэрдэг. Шинэ migration бич.
Migration-ийг кодоос тусад нь commit хийх. Хэрэв загварын өөрчлөлт нэг commit-д, migration нь хоёр дахь commit-д байвал хэн нэгэн эхний commit-ийг л татаж авбал Анхны жишээний алдаатай тулна. Хамт хадгал.
Downgrade-ийг backup гэж үзэх. Downgrade нь бүтцийг буцаана, устгасан өгөгдлийг биш. Өгөгдөл устгадаг migration (DROP COLUMN, DROP TABLE) хэрэгжүүлэхээс өмнө жинхэнэ орчинд backup хийх нь мэргэжлийн дадал.
Энэ хичээлд та migration-ий санааг ойлголоо. create_all нь байхгүй хүснэгтийг л үүсгэж, байгааг хэзээ ч өөрчилдөггүй тул загварт шинэ талбар нэмэхэд column does not exist алдаа гарна. Гараар ALTER хийх нь код ба бүтцийг салгаж, орчин бүрийг санах ойд найдан шинэчлүүлж, ямар ч түүх үлдээдэггүй. Migration бол бүтцийн өөрчлөлт бүрийг upgrade ба downgrade хоёр чиглэлтэй, дугаарласан файл болгон Git-д кодтой хамт хадгалах арга — дэвтрийн маягтыг албан ёсоор шинэчлэх тушаал. Өгөгдлийн сан бүр хэддүгээр migration хүртэл хэрэгжсэнээ өөрийн дотор тэмдэглэдэг тул нэг тушаал зөвхөн хэрэгжээгүйг нь ажиллуулж, бүх орчныг ижил бүтэцтэй болгодог. PostgreSQL-д бүтцийн өөрчлөлт transaction-д багтдаг тул бүтэлгүйтсэн migration бүрэн цуцлагдана. Хэрэгжсэн migration-ийг засахгүй, шинэ migration бичнэ; downgrade нь бүтцийг буцаадаг ч өгөгдлийг үргэлж биш.
Дараагийн хичээлд энэ санааг Python-ы хамгийн өргөн хэрэглэгддэг migration хэрэгсэл — Alembic-ээр хэрэгжүүлнэ. Alembic нь SQLAlchemy-г бүтээсэн хүмүүсийн хийсэн хэрэгсэл тул SQLModel-тэй байгалиараа ажилладаг. Бид түүнийг суулгаж, тохируулж, анхны migration-оо үүсгэж, үүссэн файлыг мөр бүрээр нь монголоор уншина.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.