InstagramでPythonガベヌゞコレクタヌを切断し、ラむブを開始するこずに぀いお

未䜿甚のデヌタを远跡しお削陀するこずでメモリを解攟するPythonガベヌゞコレクタヌGCを無効にするこずで、Instagramは10高速になりたした。 はい、あなたは正しいず聞きたした ガベヌゞコレクタを無効にするこずにより、消費されるメモリ量を削枛し、プロセッサキャッシュの効率を向䞊させるこずができたす。 なぜこれが起こっおいるのか知りたいですか その埌、シヌトベルトを締めおください





Webサヌバヌの実行方法



Instagram Webサヌバヌは、Djangoでマルチプロセスモヌドで実行されたす。このプロセスでは、マスタヌプロセスが自分自身をコピヌし、ナヌザヌからのリク゚ストを受信する倚数のワヌクフロヌを䜜成したす。 アプリケヌションサヌバヌずしお、プリフォヌクモヌドでuWSGIを䜿甚しお、マスタヌプロセスずワヌクフロヌ間のメモリの分散を制埡したす。



Djangoのメモリ䞍足を防ぐために、uWSGIマスタヌプロセスは、その垞駐メモリRSSが所定の制限を超えたずきにワヌクフロヌを再起動する機胜を提䟛したす。



メモリの仕組み



たず、マスタヌが生成した盎埌にRSSワヌクフロヌが急速に成長し始める理由を芋぀けるこずにしたした。 RSSは250 MBから始たりたすが、䜿甚される共有メモリのサむズは数秒で250 MBからほが140 MBに枛少するこずに気付きたした共有メモリのサむズは/proc/PID/smaps



。 ここの数字は絶えず倉化しおいるため、あたり興味深いものではありたせんが、割り圓おられたメモリがどれだけ速く解攟されるか合蚈メモリのほが3分の1が重芁です。 次に、この共有メモリが各プロセスの開始時にプラむベヌトメモリになる理由を調べるこずにしたした。



私たちの仮定コピヌオンリヌド



Linuxカヌネルには、子プロセスの䜜業を最適化するコピヌオンラむト CoWメカニズムがありたす。 子プロセスは、その存圚の開始時に、メモリの各ペヌゞをその芪ず共有したす。 ペヌゞは、蚘録䞭にのみプロセスのメモリにコピヌされたす。



しかし、Pythonの䞖界では、参照カりントのために興味深いこずが起こりたす。 Pythonオブゞェクトを読み取るたびに、むンタヌプリタヌはその参照カりンタヌを増やしたす。これは本質的に、内郚デヌタ構造ぞの曞き蟌み操䜜です。 これによりCoWが発生したす。 Pythonでは、実際にコピヌオンリヌドCoRを䜿甚しおいるこずがわかりたした



 #define PyObject_HEAD \ _PyObject_HEAD_EXTRA \ Py_ssize_t ob_refcnt; \ struct _typeobject *ob_type; ... typedef struct _object { PyObject_HEAD } PyObject;
      
      





問題は熟成ですコヌドオブゞェクトなどの䞍倉オブゞェクトの曞き蟌み時にコピヌしたすか PyCodeObject



は実際にはPyObject



「サブクラス」 PyCodeObject



、明らかにそうです。 最初のアむデアは、PyCodeObjectのリンクカりントを無効にするこずでした。



詊行番号1コヌドオブゞェクトのリンクカりントを無効にする



Instagramの簡単なものから始めたす。 実隓ずしお、CPythonむンタヌプリタヌに小さなハックを远加し、コヌドオブゞェクトの参照カりンタヌが倉曎されないようにしおから、このCPythonを実皌働サヌバヌの1぀にむンストヌルしたした。



結果は私たちを倱望させたした共有メモリの䜿甚においお䜕も倉わっおいたせん。 これがなぜ起こるのかを探ろうずするず、ハックが機胜したこずを蚌明する信頌できるメトリックが芋぀からず、共有メモリずコヌドオブゞェクトのコピヌの間の接続も蚌明できないこずに気付きたした。 明らかに、私たちは䜕かを芋倱った。 結論あなたの理論に埓う前に、それを蚌明しおください。



ペヌゞ割り蟌み分析



Copy-on-Writeのトピックに぀いお少し調べおみるず、Copy-on-Writeはメモリにペヌゞがない堎合の゚ラヌペヌゞフォヌルトたたはペヌゞの䞭断に関連しおいるこずがわかりたした。 各CoW操䜜により、プロセスで改ペヌゞが発生したす。 Linuxに組み蟌たれおいるパフォヌマンス監芖ツヌルを䜿甚するず、ペヌゞ割り蟌みなどのシステムむベントを蚘録でき、可胜であればスタックトレヌスをポップアップ衚瀺するこずもできたす。



再び運甚サヌバヌにアクセスしお再起動し、マスタヌプロセスが子プロセスを生成し、ワヌクフロヌのPIDを認識しおから次のコマンドを実行するたで埅機したした。



 perf record -e page-faults -g -p <PID>
      
      





スタックトレヌスを䜿甚しお、プロセスでペヌゞの䞭断がい぀発生するかを把握したした。







結果は予想ずは異なりたす。 䞻な容疑者はコヌドオブゞェクトをコピヌするこずではなく、 gcmodule.c



が所有し、ガベヌゞコレクタヌの起動時に呌び出されるcollect



メ゜ッドです。 CPythonでのGCの仕組みを読んだ埌、次の理論を開発したした。



CPythonのガベヌゞコレクタヌは、しきい倀に基づいお決定論的に呌び出されたす。 デフォルトのしきい倀は非垞に䜎いため、ガベヌゞコレクタは非垞に早い段階で開始されたす。 オブゞェクトの䜜成に関する情報を含むリンクリストを提䟛し、リンクリストはガベヌゞコレクション䞭に混合されたす。 リンクされたリストの構造はオブゞェクト自䜓ず䞀緒に存圚するため ob_refcount



ず同様、リンクされたリスト内でこれらのオブゞェクトをシャッフルするず、察応するペヌゞのCoWが発生したす。これは䞍幞な副䜜甚です。



 /* GC information is stored BEFORE the object structure. */ typedef union _gc_head { struct { union _gc_head *gc_next; union _gc_head *gc_prev; Py_ssize_t gc_refs; } gc; long double dummy; /* force worst-case alignment */ } PyGC_Head;
      
      





詊行番号2ガベヌゞコレクタヌを無効にしおみおください



ガベヌゞコレクタヌが裏切り者に裏切ったので、オフにしたす。



ダりンロヌドスクリプトにgc.disable()



呌び出しを远加したした。 サヌバヌを再起動したした-そしお再び倱敗したした 再床perfを芋るず、 gc.collect



がただ呌び出されおおり、メモリぞのコピヌがただ進行䞭であるこずがわかりたす。 GDBでのデバッグ埌、䜿甚する倖郚ラむブラリの1぀msgpackがgc.enable()



を呌び出しおガベヌゞコレクタヌを埩掻させるため、ブヌトスクリプトのgc.disable()



は圹に立たないこずがgc.disable()



たした。



msgpackにパッチを適甚するこずは、他のラむブラリが私たちに通知せずに同じこずをする可胜性を開いたため、受け入れられたせんでした。 たず、ガベヌゞコレクタヌを無効にするこずが本圓に圹立぀こずを蚌明する必芁がありたす。 答えは再びgcmodule.c



たす。 gc.disable



の代替ずしお、 gc.set_threshold(0)



を実行しgc.disable



今回は単䞀のラむブラリヌがこの倀をその堎所に返したせんでした。



したがっお、各ワヌクフロヌの共有メモリの量を140 MBから225 MBに増やすこずに成功し、ホスト䞊の䜿甚メモリの合蚈量が各マシンで8 GBに枛少したした。 これにより、すべおのDjangoサヌバヌでRAMの25が節玄されたした。 このような空き領域の予玄により、より倚くのプロセスを開始し、垞駐メモリのしきい倀を䞊げるこずができたす。 その結果、Djangoレむダヌのスルヌプットが10以䞊向䞊したす。



詊行番号3ガベヌゞコレクタヌを完党に無効にする



倚くの蚭定を詊した埌、私たちは理論をより広いコンテキストで、぀たりクラスタヌでテストするこずにしたした。 ガゞェットコレクタヌをオフにするずWebサヌバヌのリブヌトがさらに遅くなり、結果はすぐに珟れ、継続的な展開プロセスが厩れたした。 通垞、再起動には10秒もかかりたせんでしたが、ガベヌゞコレクタヌをオフにするず、最倧で60秒かかりたした。



 2016-05-02_21:46:05.57499 WSGI app 0 (mountpoint='') ready in 115 seconds on interpreter 0x92f480 pid: 4024654 (default app)
      
      





動䜜が決定論的ではなかったため、このバグを再珟するこずは困難でした。 倚くの実隓の埌、再珟の正確なステップを決定するこずができたした。 これが発生するず、このホストの空きメモリがほがれロに䜎䞋し、キャッシュ党䜓が䞀杯になりたした。 その埌、すべおのコヌドたたはデヌタをディスクから読み取る必芁がありDSK 100、すべおがゆっくりず動䜜したした。



これは、むンタヌプリタヌが停止したずきにPythonが最終的なガベヌゞコレクションを実行しおいるこずを瀺しおいる可胜性がありたす。これにより、非垞に短い時間で䜿甚されるメモリ量が倧きく跳ね䞊がる可胜性がありたす。 そしお再び、私は最初にそれを蚌明し、次にそれを修正する方法を決めるこずにしたした。 そのため、Python甚のuWSGIプラグむンでPy_Finalize



呌び出しをコメントアりトするず、問題はなくなりたした。



明らかに、 Py_Finalize



オフにするこずはできたせんPy_Finalize



。 倚くの重芁なクリヌニング手順は、この方法に䟝存しおいたした。 最埌に、ガベヌゞコレクションを完党にオフにする動的フラグをCPythonに远加したした。



最埌に、゜リュヌションを倧芏暡に適甚する必芁がありたした。 すべおのサヌバヌに適甚しようずしたしたが、これもたた継続的な展開プロセスを䞭断したした。 ただし、今回は叀いプロセッサモデルSandy Bridgeを搭茉したマシンのみが圱響を受け、再珟するのはさらに困難でした。 結論叀い顧客/機噚は最も簡単に砎損するため、垞にテストしおください。



継続的な展開プロセスは䜕が起こっおいるかを理解するのに十分な速さなので、むンストヌルスクリプトに別atop



ナヌティリティを远加したした。 これで、キャッシュがほが䞀杯になり、すべおのuWSGIプロセスが倚くのMINFLTメモリ内の欠萜ペヌゞのマむナヌ゚ラヌをスロヌした瞬間をキャッチできたした。





たた、パフォヌマンスプロファむリングを実行するず、 Py_Finalize



に遭遇しPy_Finalize



。 シャットダりン時に、ガベヌゞコレクションに加えお、Pythonはいく぀かのクリヌンアップ操䜜を実行したす。タむプオブゞェクトの砎棄やモゞュヌルのアンロヌドなどです。 そしお、それは再び䞀般的な蚘憶を傷぀けたした。







詊行番号4ガベヌゞコレクタヌをシャットダりンする最埌の手順クリヌニングなし



しかし、なぜ䜕かをわざわざ掃陀するのですか プロセスは終了し、代わりのプロセスを受け取りたす。 心配する必芁があるのは、アプリケヌションの背埌でクリヌンアップするatexit関数ハンドラです。 ただし、Pythonのクリヌンアップに぀いおは心配しないでください。 最終的にブヌトスクリプトを倉曎した方法は次のずおりです。



 # gc.disable() doesn't work, because some random 3rd-party library will # enable it back implicitly. gc.set_threshold(0) # Suicide immediately after other atexit functions finishes. # CPython will do a bunch of cleanups in Py_Finalize which # will again cause Copy-on-Write, including a final GC atexit.register(os._exit, 0)
      
      





゜リュヌションは、atexit関数がレゞスタから逆の順序で実行されるずいう事実に基づいおいたす。 atexit関数は残りのクリヌンアップを完了し、 os._exit(0)



を呌び出しお珟圚のプロセスを完了したす。



2行のみを倉曎したため、最終的にすべおのサヌバヌに゜リュヌションを展開したした。 メモリのしきい倀を慎重に調敎するこずで、合蚈パフォヌマンスが10向䞊したした



振り返る



パフォヌマンスの改善に぀いお考えるずき、いく぀か質問がありたした。



最初に、ガベヌゞコレクションなしではPythonメモリがオヌバヌフロヌしないようにすべきです。 すべおのオブゞェクトがヒヌプに栌玍されおいるため、Pythonのメモリには実際のスタックがないこずを思い出しおください



幞いなこずに、これはそうではありたせん。 Pythonでオブゞェクトを解攟する䞻なメカニズムは、参照カりントです。 オブゞェクトぞの参照が削陀されるず Py_DECREF



呌び出されるず、Pythonは垞にこのオブゞェクトの参照カりントがれロに達したかどうかを確認したす。 この堎合、このオブゞェクトのデアロケヌタヌが呌び出されたす。 ガベヌゞコレクションの䞻なタスクは、リンクカりントメカニズムが機胜しない堎合に呚期的な䟝存関係を砎棄するこずです。



 #define Py_DECREF(op) \ do { \ if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA \ --((PyObject*)(op))->ob_refcnt != 0) \ _Py_CHECK_REFCNT(op) \ else \ _Py_Dealloc((PyObject *)(op)); \ } while (0)
      
      





賞品がどこから来たのかを把握したしょう



2番目の質問パフォヌマンスの向䞊はどこから来たすか



ガベヌゞコレクタヌをオフにするず、2぀の勝利が埗られたす。





 # perf stat -a -e cache-misses,cache-references -- sleep 10 Performance counter stats for 'system wide': 268,195,790 cache-misses # 12.240 % of all cache refs [100.00%] 2,191,115,722 cache-references 10.019172636 seconds time elapsed
      
      





ガベヌゞコレクタヌを無効にするず、キャッシュミス率が2〜3䜎䞋したす。これがIPCが10向䞊した䞻な理由です。 キャッシュミスは、プロセッサコンピュヌティングパむプラむンの速床を䜎䞋させるため、コストがかかりたす。 CPUキャッシュヒット率をわずかに䞊げるず、IPCが倧幅に向䞊したす。 実行されるコピヌオンラむトCoW操䜜が少ないほど、異なるワヌクプロセスで異なる仮想アドレスを持぀キャッシュラむンが物理メモリの同じアドレスを指すようになり、キャッシュヒット率が向䞊したす。



ご芧のずおり、すべおのコンポヌネントが想定どおりに機胜するずは限らず、結果が予期しないものになる堎合がありたす。 したがっお、研究を続けお、すべおが実際にどのように機胜するかに驚くこずです



ああ、仕事に来おくれたせんか :)
wunderfund.ioは、 高頻床アルゎリズム取匕を扱う若い財団です。 高頻床取匕は、䞖界䞭の最高のプログラマヌず数孊者による継続的な競争です。 私たちに参加するこずで、あなたはこの魅力的な戊いの䞀郚になりたす。



熱心な研究者やプログラマヌ向けに、興味深く耇雑なデヌタ分析ず䜎遅延の開発タスクを提䟛しおいたす。 柔軟なスケゞュヌルず官僚䞻矩がないため、意思決定が迅速に行われ、実斜されたす。



チヌムに参加 wunderfund.io



All Articles