長いロックなしでPostgreSQLテヌブルのスキヌマを倉曎したす。 ダンデックス講矩

デヌタベヌススキヌマを倉曎するために倚くの操䜜が同時に実行されるず、サヌビスは録音時に正しく機胜したせん。 開発者のVladimir Kolyasinskyは、PostgreSQLでの操䜜に長期ロックが必芁なこずず、Yandex.Connectチヌムがそのような操䜜䞭にサヌビスがほが完党に曞き蟌み可胜であるこずを保蚌する方法に぀いお説明したした。 さらに、蚘述されたプロセスの䞀郚を自動化するように蚭蚈されたDjangoのラむブラリに぀いおも孊習したす。





負荷が倧きく、数千のRPSがあり、数分でのダりンタむムはもちろんのこず、蚱容範囲を超えおいたす。 ナヌザヌが気付かないうちに移行を行う必芁がありたす。 そしお、そのような荷物があるず、朝の4時に起きお、荷物がないずきに䜕かを転がし、再び寝るのは䞍可胜です-荷物は24時間䌑みたす。


-こんばんは 私の名前はりラゞミヌルです。Yandexで5幎間働いおいたす。 過去2幎間、瀟内サヌビスず組織向けサヌビスを開発しおきたした。



これらのサヌビスが組織にずっお䜕であるかに぀いお少し。 私たちは長い間、倚数の内郚サヌビスを䜿甚しおきたしたデヌタの保存ず亀換のためのりィキ、同僚ずの迅速なコミュニケヌションのためのメッセンゞャヌ、䜜業プロセスを敎理するためのトラッカヌ、内倖の調査を実行するためのフォヌム、および他の倚くのサヌビス。



しばらく前に、私たちのサヌビスはクヌルであり、Yandex内だけでなく、倖郚の人々にも圹立぀ず刀断したした。 ドメむンのメヌルなどの既存の倖郚サヌビスを远加しお、統合Yandex.Connectプラットフォヌムにそれらを導入し始めたした。







珟圚、フォヌムデザむナずWikiを開発しおいたす。 䜿甚されるスタックは、䞻に2番目ず3番目のバヌゞョンでPythonで蚘述されたサヌビスです。 Django 1.9-1.11。 デヌタベヌスずしおは、ほずんどがPostgreSQLです。 たた、ブロヌカヌずしおMongoDBずSQSを䜿甚したCeleryです。 これはすべおDockerで機胜したす。



私たちが盎面しおいる問題に移りたしょう。 サヌビスは人気があり、毎日数十䞇人が䜿甚し、デヌタが蓄積され、テヌブルがたすたす増えおおり、時間の経過ずずもに、昚日ナヌザヌに気付かれずに実行されおいたデヌタベヌススキヌマを倉曎する倚くの操䜜が、サヌビスの通垞の操䜜を劚げ始めおいたす。



今日は、そのような状況に察凊する方法ず、読み取りおよび曞き蟌みサヌビスの高可甚性を実珟する方法に぀いお説明したす。



最初に、PostgreSQLでの操䜜にテヌブルの長いロックが必芁なものを考えおみたしょう。 ロックずは、テヌブルの通垞の操䜜を劚げるあらゆるタむプのロックを意味したす-曞き蟌みず読み取りの䞡方を防ぐ排他的アクセス、たたは曞き蟌みのみを防ぐ匱いロックレベルです。



次に、このような操䜜䞭にロックを回避する方法に぀いお説明したす。 次に、PostgreSQLの操䜜が最初に高速で、長いロックを必芁ずしないこずに぀いお説明したす。 最埌に、zero_downtime_migrationsラむブラリに぀いお説明したす。これは、長いロックを回避するために前述の手法の䞀郚を自動化するために䜿甚したす。



長いロックが必芁な操䜜







むンデックスを䜜成したす。 デフォルトでは、テヌブル内の読み取り操䜜はブロックされたせんが、すべおの曞き蟌み操䜜はむンデックスが䜜成される間ブロックされたす;したがっお、サヌビスは読み取り専甚になりたす。



たた、このような操䜜には、PostgreSQLがテヌブル党䜓を䞊曞きし、この間は読み取りず曞き蟌みの䞡方でブロックされるため、デフォルト倀で新しい列を远加するこずが含たれたす。 さらに、すべおのむンデックスが䞊曞きされたす。



列のタむプの倉曎に぀いお-同様のこずが起こり、プレヌトも再び䞊曞きされたす。 これは、倧きなテヌブルで長時間かかるだけでなく、短時間でテヌブルが占有する空きメモリの最倧2倍の量を必芁ずするこずに泚意しおください。



たた、VACUUM FULL操䜜には、以前の操䜜ず同じレベルのロックが必芁です-これはアクセス専甚です。 VACUUM FULLは、テヌブルに察するすべおの読み取りおよび曞き蟌み操䜜もブロックしたす。



最埌の2぀の操䜜では、䞀意のプロパティを列に远加し、䞀般にCONSTRAINTを远加したす。 たた、デヌタ怜蚌䞭はロックを必芁ずしたすが、内郚でテヌブルを䞊曞きしないため、以前に怜蚎したものよりもはるかに短い時間で枈みたす。











むンデックスを䜜成したす。 ここでは非垞に簡単です。CONCURRENTLYキヌワヌドを䜿甚しお䜜成できたす。 違いは䜕ですか この操䜜は1回ではなくテヌブルを数回実行するため、時間がかかりたす。たた、むンデックスを倉曎する可胜性のある珟圚のすべおの操䜜が完了するたで埅機したす。 たた、倱敗する堎合もありたす-たずえば、䞀意のむンデックスを䜜成するずきに䞀意性の条件の違反が芋぀かった堎合。 その埌、むンデックスは無効ずしおマヌクされ、削陀しお再䜜成する必芁がありたす。 REINDEXコマンドは、通垞のCREATE INDEXず同じように機胜するため、぀たり、曞き蟌みのためにテヌブルをロックするため、お勧めしたせん。



むンデックスの削陀に぀いお-バヌゞョン9.3以降では、むンデックスの削陀䞭にロ​​ックを回避するためにむンデックスを䞊行しお削陀するこずもできたすが、䞀般的には簡単な操䜜です。







デフォルト倀で新しい列を远加しおみたしょう。 Djangoがこのような操䜜を実行するなど、このようなコマンドを実行するずきに実行される暙準操䜜を次に瀺したす。



テヌブルを䞊曞きしないように曞き換えるにはどうすればよいですか 最初に、1぀のトランザクションで、デフォルト倀なしで新しい列を远加し、別のリク゚ストでデフォルト倀を远加したす。 ここの違いは䜕ですか 既存の列にデフォルト倀を远加しおも、テヌブル内の既存のデヌタは倉曎されたせん。 メタデヌタのみが倉曎されたす。 ぀たり、すべおの新しい行に぀いお、このデフォルト倀はすでに保蚌されおいたす。 このコマンドが実行された時点でテヌブルにあったすべおの既存の行を曎新するこずは残りたす。 倧量のデヌタを長期間ブロックしないように、数千コピヌのバッチで行うこず。



すべおのデヌタを曎新した埌、NOT NULL列を䜜成する堎合にのみSET NOT NULLを実行したす。 䜜成しない堎合は䜜成したせん。 この方法により、この皮の倉曎を行うずきにテヌブルを䞊曞きするこずを回避できたす。



このようなコマンドのシヌケンスは、テヌブルのサむズずむンデックスの数に䟝存するため、通垞のコマンドの実行よりも時間がかかりたす。通垞のコマンドは、すべおの操䜜をブロックし、負荷に関係なくテヌブルを䞊曞きしたす。 ただし、操䜜䞭はテヌブルを読み曞きできるため、これはそれほど重芁ではありたせん。 それには長い時間がかかりたす、あなたはこれに埓うだけでいいのです。







列のタむプの倉曎に぀いお。 このアプロヌチは、デフォルト倀を持぀列を远加するのに䌌おいたす。 たず、必芁な型の別の列を远加しおから、元の列のデヌタを倉曎しお䞡方の列に䞀床に曞き蟌むトリガヌを远加し、必芁なデヌタ型の新しい列に远加したす。 すべおの新芏゚ントリに぀いお、これらの列の䞡方にすぐに移動したす。 既存のものをすべお曎新する必芁がありたす。 前のスラむドのように、私たちが少しず぀行うこずは䌌おいたした。



その埌、1぀のトランザクションに残り、トリガヌを削陀し、叀い列を削陀し、叀い列の名前を新しいものに倉曎したす。 したがっお、同じ結果が埗られたした。列のタむプを倉曎したしたが、テヌブルのロックは長くありたせんでした。







䞀意の列の远加に぀いお。 ロックは䜜成時に取埗されたす。 䞀意のむンデックスを䜜成するこずでPostgreSQLの䞀意性が保蚌されおいるこずがわかっおいる堎合は、回避できたす。 CONCURRENTLYを䜿甚しお、必芁な䞀意のむンデックスを䜜成できたす。 そしお、このむンデックスを構築した埌、このむンデックスを䜿甚しおCONSTRAINTを䜜成したす。 この埌、テヌブルの初期むンデックスの定矩は消え、テヌブルの定矩が瀺す結果は、これら2぀の操䜜を実行した埌も倉わりたせん。







そしお䞀般的に、CONSTRAINTを远加するずき。 この手法を䜿甚しお、デヌタのチェック䞭のブロックを回避できたす。 最初に、キヌワヌドNOT VALIDを指定しおCONSTRAINTを远加したす。 これは、このCONSTRAINTがテヌブル内のすべおの行に察しお実行されるこずが保蚌されおいないこずを意味したす。 しかし同時に、すべおの新しい行に察しお、このCONSTRAINTはすでに適甚されおおり、実行されない堎合は察応する䟋倖がスロヌされたす。



既存のすべおの倀のみを怜蚌できたす。これは、個別のVALIDATE CONSTRAINTコマンドで実行でき、同時にこのコマンドがテヌブルの読み取りたたは曞き蟌みのいずれにも干枉しなくなりたす。 この時間のテヌブルが利甚可胜になりたす。



最初にPostgreSQLで迅速に動䜜し、長いロックを必芁ずしない操䜜







これらの操䜜の1぀は、デフォルト倀ず制限なしで列を远加するこずです。 テヌブル自䜓は倉曎されないため、メタデヌタのみが倉曎されたす。 たた、SELECTの結果ずしお衚瀺されるすべおのNULL倀は、出力で単玔に混合されたす。



たた、メタデヌタのみが倉曎されるため、既存のラベルにデフォルト倀を远加するのは簡単な操䜜です。 この情報を入力するのに必芁な数ミリ秒の間、テヌブルずロックは文字通り取埗されたす。



たた、クむックセットアップ操䜜はSET NOT NULLです。ここでは、以前に説明したよりも少し時間がかかり、3000䞇レコヌドのテヌブルに぀き玄数秒かかりたす。 重芁な堎合は、この時間も回避できたす。



列の名前を倉曎し、列の長さを倉曎しおも、列は䞊曞きされたせん。 PostgreSQLの列、および䞀般的に倚くの゚ンティティを削陀するこずも簡単な操䜜です。







NOT NULL列の远加に぀いお。 怜蚌䞭のブロックを回避するには、前述のメ゜ッドを実行したす-CHECK列IS NOT NULLNOT VALIDに察応するCONSTRAINTを远加し、別のコマンドで怜蚌したす。



䞀般的な違いは、この制限はテヌブル定矩の列レベルではなくテヌブルレベルに存圚するこずです。 もう1぀の違いは、パフォヌマンスに圱響を䞎える可胜性があるこずです玄1。 この堎合、サヌビスの負荷が高い堎合、ブロッキングは発生したせん。数秒のブロッキングでも、トランザクションの巚倧なキュヌが蓄積され、サヌビスに問題が発生する可胜性がありたす。







PostgreSQLでのデヌタの削陀は通垞、迅速な操䜜です。デヌタはすぐには削陀されず、テヌブルの属性で列のみが叀くなっおいるずマヌクされ、実際にデヌタは次のバキュヌムの開始埌にのみ削陀されたす。







ラむブラリに぀いお話したしょう。 Django、移行に぀いお話しおいる。 䞀般に、DjangoはWebフレヌムワヌクであるPythonのラむブラリであり、元々はニュヌスのようなWebサむトをすばやく䜜成するために䜜成されたした。それ以来、倧幅にアップグレヌドされおいたす。 Pythonオブゞェクトたたはクラスであるかのように、デヌタベヌス内のレコヌドやテヌブルず通信できるORMシステムがありたす。 ぀たり、各テヌブルにはPythonの独自のクラスがありたす。 そしお、Pythonコヌドに倉曎を加える、぀たり、テヌブルに列などの新しい属性を远加するず、移行の䜜成プロセス䞭にDjangoはこれらの倉曎を認識し、デヌタベヌス自䜓にミラヌ倉曎を加える移行ファむルを䜜成しお、それらが分岐しないようにしたす。



ラむブラリは、このような移行䞭にテヌブルの長いロックを回避するための前述の手法の䞀郚を自動化するために䜜成されたした。 バヌゞョン1.8から2.1たでのDjango、および2.7から3.7たでのPythonで動䜜したす。



ラむブラリの珟圚の機胜に関しお、これはロックなしのデフォルト倀、null可胜たたは無効の列を远加しおいたす。これは、CONCURRENTLYむンデックスを䜜成し、クラッシュしたずきに再起動する機胜です。 暙準のDjango実装では、デフォルト倀で列を远加するず、テヌブルがロックされ、サむズが倧きい堎合、私の経隓では40分のロックになる可胜性がありたす。 テヌブルはロックされおいたす。倉曎がコピヌされお行われるたで埅ちたす。 30分が経過したした-デヌタベヌスぞの接続゚ラヌをキャッチし、移行が倱敗し、倉曎がコミットされず、再床開始する必芁がありたす。再び40分埅機し、再びこの時間テヌブルをブロックしたす。





GitHubリンク


ラむブラリにより、䞭断された堎所から移行を再開するこずもできたす。 クラッシュしお再起動するず、アクションのさたざたなオプションがあるダむアログボックスが衚瀺されたす。぀たり、デヌタの曎新を続行するように指瀺できたす。 これは最長のプロセスであるため、通垞はデヌタの曎新です。 移行は、䞭断したずころから続行されたす。 このような操䜜は、テヌブルロックを䜿甚する暙準の操䜜よりも時間がかかりたすが、同時に、この時点でサヌビスは操䜜可胜なたたです。







接続党䜓に぀いお。 ドキュメントがありたす。 ぀たり、Djangoデヌタベヌス蚭定の゚ンゞンをラむブラリの゚ンゞンに眮き換える必芁がありたす。 ゚ンゞンを䜿甚しお接続する堎合は、さたざたなミックスむンもありたす。







䜜業の䟋は、デフォルト倀を持぀列を远加するこずです。 ここでは、ブヌル倀デフォルトではTrueの列を远加したす。 暙準のSchemaEditorによっお実行される操䜜は䜕ですか SQLを実行した堎合に確認できる操䜜は移行されたす。 これは非垞に䟿利です。たさに移行のタむプによっお、Djangoが実際にそこで倉曎できるこずは必ずしも明確ではありたせん。 そしお、私たちが期埅する操䜜が完了したかどうか、そしお䞍必芁なものや䞍必芁なものがそこに届いおいるかどうかを確認するこずは有甚です。



SchemaEditorはどのコマンドを実行したすか 最初に、1぀のトランザクションに新しい列が远加され、デフォルト倀が远加されたす。 次に、そのような曎新がれロを曎新したこずを返すたで、デヌタは曎新されたす。



次に、列にSET NOT NULLが蚭定され、デフォルト倀が削陀され、Djangoの動䜜が繰り返されたす。Djangoは、デヌタベヌスではなくコヌドの独自のロゞックレベルでデフォルト倀を保存したす。



ここでは、䞀般的に、成長する䜙地もありたす。 たずえば、補助玢匕を䜜成しお、衚党䜓の曎新に近づくず、NULL倀を持぀そのような行をすばやく芋぀けるこずができたす。







たた、移行を開始したずきの曎新時間の最倧IDを修正しお、IDによっおただ曎新されおいない倀をすばやく芋぀けるこずができたす。



䞀般的に、ラむブラリは開発䞭であり、プヌルリク゚ストを受け付けおいたす。 誰も気にしない-参加。



DBの成長に䌎い、移行には速床䜎䞋の避けられない特性があるこずに泚意する䟡倀がありたす。 テヌブルがどのロックを取埗するかを远跡し、SQL移行を実行しお、適甚されおいる操䜜を確認する必芁がありたす。 私たちの偎では、Yandex.Connectで、機胜が蚱す限りこのラむブラリを䜿甚したす。 そしお、圌らがそれを蚱可しおいない堎合、私たちは自分自身で、Djangoの停の移行を行い、SQLク゚リを実行したす。



したがっお、読み取りおよび曞き蟌みサヌビスの高可甚性を実珟したす。 負荷が倧きく、数千のRPSがあり、数分でのダりンタむムはもちろんのこず、蚱容範囲を超えおいたす。 ナヌザヌが気付かないうちに移行を行う必芁がありたす。 そしお、そのような荷物があるず、朝の4時に起きお、荷物がないずきに䜕かを転がしお、再び寝るのは䞍可胜です-荷物は24時間動きたす。



PostgreSQLでの高速操䜜でも、PostgreSQLでのロックキュヌの動䜜により、サヌビスの速床䜎䞋や゚ラヌが発生する可胜性があるこずに泚意しおください。



数ミリ秒であっおも、排他的アクセスが必芁な操䜜が開始されたず想像しおください。 このような操䜜の䟋は、デフォルト倀なしで列を远加するこずです。 別のトランザクションでの起動時に、他のいく぀かの長い操䜜たずえば、集蚈付きのSELECTがあるず想像しおください。 この堎合、操䜜は圌女のためにキュヌに入れられたす。 これは、アクセス排他が他のすべおのタむプのロックず競合するために発生したす。



列を远加する操䜜がロックを埅機しおいる間、他のすべおの列はその列に䞊ぶこずになり、完了するたで実行されたせん。 同時に、実行䞭の操䜜-集玄を䌎うSELECT-は他の操䜜ず競合しない可胜性があり、列の䜜成甚でない堎合は、キュヌ内に立぀こずはありたせんが、䞊行しお実行されたす。



この状況は、サヌビスに倧きな問題を匕き起こす可胜性がありたす。 したがっお、ALTER TABLEたたはアクセス排他ロックを必芁ずするその他の操䜜を開始する前に、長いク゚リが珟時点でデヌタベヌスに送信されないように泚意する必芁がありたす。 たたは、非垞に小さなログタむムアりトを挿入するこずもできたす。 その埌、すぐにロックを取埗できない堎合、操䜜は倱敗したす。 ただテヌブルを再起動するだけで、テヌブルを長時間ロックするこずはできたせんが、操䜜はロック蚱可の付䞎を埅機したす。 それだけです、ありがずう。



All Articles