KS2012: memcg/mm: Improving kernel-memory accounting for memory cgroups の翻訳.
The 2012 Kernel Summit memcg/mm minisummit started with a session titled “Accounting other than user pages”, but the slot was mainly dedicated to the topic of improved kernel-memory accounting for memory cgroups. Glauber Costa has been working on kernel-memory accounting for some time. He began with a basic overview of the motivations for memory cgroups and how they can be used to limit memory usage of a group of processes within a container by accounting for all the pages used by user space. Glauber would like to see better accounting of certain workloads that have low user-memory usage but high kernel-memory usage (see this LWN article for some background). The problem is that, in the current kernel implementation, a single memory control group can occupy a lot of kernel memory without being held responsible for it. This can produce global memory pressure that pushes other cgroups to swap or even triggers the OOM killer. The goal is thus to better track usage of kernel memory, in order to prevent memory cgroups from causing these kinds of problems. Glauber mentioned earlier work he has done to track kernel memory used for TCP data. He is now turning his focus to other kinds of kernel memory that can possibly “explode”. His overall goal is to allow the use of OpenVZ with upstream kernels, rather than patched kernels. A use case of particular interest is hosting providers that use OpenVZ-based containers and heavily overcommit resources. In such scenarios, the kernel can't trust containers to cooperate with the rest of the system in order to keep the system alive. Thus, accurate accounting of resource usage within containers is very important. Glauber would like to account for general kernel memory usage, including usage of slab objects and kernel stack pages. To begin with, he is trying to merge support for tracking per-task kernel stack usage. (One reason for tracking kernel stack usage is that it could provide a mechanism to mitigate fork bombs and cap their effect inside a cgroup.) The rationale for starting out by tracking kernel stack usage is that it would be a reasonably non-invasive change (unlike tracking slab usage), but would still demonstrate all the memory cgroups changes that would be required for later, larger memory-accounting changes; this smaller piece of work would also serve to show what the user interface (i.e., the configuration files in the cgroup directories) would look like. Glauber went on to discuss the additional changes necessary to track usage of slab allocations. His description was quite detailed, but, very broadly speaking, slab pages allocated for a cgroup would be for the exclusive use of that cgroup. In essence, every cgroup would get its own slab cache for a certain object type so that pages allocated to a cache are always exclusively used by one memory cgroup. Slab metadata would be copied over to a cgroup-specific slab. Thus, for example, if a dentry was touched, this would lazily trigger the creation of a per-cgroup dentry cache for that cgroup. From that point on, all dentries allocated by that cgroup would come not from the global dentry cache, but rather from cgroup-specific cache. A side effect of that is that pages are filled with objects from the same cgroup. The intention of the proposed changes is to prevent unrelated cgroups pinning each other's memory—that is, preventing the situation where one cgroup, B, forces an unrelated cgroup, A, to keep memory resident that A is being charged for. For example, if cgroups A and B have both allocated objects in a slab page they share, and A is forced to shrink due to hitting its limits, then it might try freeing all of its slab objects, but still fail to free the slab page it is being charged for. Someone asked specifically about page-table pages, noting that it's easy to create an adverse workload that allocates many page tables while using very little user memory. Glauber felt that it would be easy to track these pages using a memory-cgroup-specific GFP (Get Free Page) flag. Glauber's current proposal does selective accounting of kernel memory allocation: only explicitly annotated allocations are tracked. Peter Zijlstra asked: why not account for every kernel memory allocation for by default? Glauber pointed out that there would be a performance cost with this, and he wants to avoid a situation where the cost of cgroups is very high. In some respects, charging cgroups by default would be easier to deal with, but it would be too slow to be useful. Some participants in the room asked that the group re-discuss kernel-plus-user memory accounting. This was discussed at the April 2012 LSF/MM meeting, but it was apparent that there was still some uncertainty as to whether it was the correct approach. Roughly, the details are as follows. There is a page counter that tracks how many pages have been allocated for exclusive use by the kernel (the kernel page counter, memory.kmem.usage_in_bytes) and a counter that counts both kernel and user page allocations (the kernel+user page counter, memory.usage_in_bytes). In conjunction with two corresponding limits that can be set by the system administrator, this implementation enables three different use cases: memory.kmem.limit_in_bytes == unlimited: This configuration says that the user is not interested in kernel-memory accounting at all, so only user memory is limited and accounted. This is the default and actually what the memory controller provided until now. memory.kmem.limit_in_bytes < memory.limit_in_bytes: This configuration enables fine-grained control over how much kernel memory can be allocated. Depending on the amount of user memory, it is more probable that the kmem limit is hit first and allocations would fail. Users of this setting should be aware that size of the kernel memory usage could differ between kernel releases. memory.kmem.limit_in_bytes >= memory.limit_in_bytes: This configuration allows capping both the kernel and the user memory without any details about how much memory is used for each of them. The user is just interested in the sum of both. In Glauber's opinion, having the two integrated counters as described above is simpler than having separate exclusive user and kernel counters. However, Michal Hocko pointed out that this makes OOM decisions difficult (i.e., the kernel may make wrong decisions, such that a program that is not using a lot of memory can be killed because of heavy kernel memory usage). Glauber defended his approach on the basis that the current OOM-killing decisions have the same problem, and if his approach makes these problems easier to trigger, then they are more likely to be found and fixed. There were no fundamental objections to Glauber's patch series to add support for accounting for pages allocated for the stack. However, three things were requested: Documentation of the use cases and how the counters are to be interpreted by the system administrator. It will be up to other interested users to object if their use cases are not addressed. Incorporation of feedback on the patches, including issues with naming and minor inconsistencies in the API. The patch series still needs “a bit of love”. A demonstration of the behavior in a NUMA environment in order to determine whether the implementation has problems with CPU cache-line bouncing. If a single counter is shared for an entire cgroup and processes in that cgroup run on multiple nodes then the cache line has to “bounce” on each write to keep the data coherent. It is desirable that Glauber tests in advance how kernel performance is affected if processes are running on multiple nodes. Potentially, every kernel allocation updates a counter shared between NUMA nodes and this would scale very badly. It's worth noting that NUMA is not the key factor here, but checking the behavior in a NUMA environment is a good test because NUMA makes the effects of cache-line bouncing much more visible. (Glauber has subsequently posted a round of benchmarks.) | The 2012 Kernel Summit memcg/mm minisummit が “Accounting other than user pages” というタイトルのセッションで始まった.しかし,時間枠は主にメモリ cgroup におけるカーネルメモリアカウンティングの改良という話題に時間が割かれた.Glauber Costa はここしばらくの間カーネルメモリアカウンティングのための作業を行ってきた.彼ははメモリcgroupの基本的な動機の概略と,どのようにしてユーザスペースが使う全てのページのアカウンティングによってコンテナ内のプロセスのグループのメモリ使用量を制限する事ができるのかから説明を始めた.Glauber はユーザメモリの使用量が少ないがカーネルメモリの使用量が多いある種の負荷のアカウンティングを良く分かるようにしたいと思った.この問題は,現在のカーネル実装での話では,単一のメモリコントロールグループは関与を保持しつづけることなく,大量のカーネルメモリを占有することができる.これはグローバルなメモリの圧迫を引き起こす可能性があり,他のグループをスワップに押しだしたり,OOM キラーのトリガになったりする.なので,メモリcgroupがこの種の問題が起こさないために,カーネルメモリの使用量を望ましい形でトラッキングする事である. Glauber は彼の以前の取り組みである TCP データの使うカーネルメモリのトラッキングについて述べた.現在彼は,使用量が急上昇する可能性のある他の種類のカーネルメモリにフォーカスを移している.彼の全体的な目標はアップストリームのカーネルでパッチを当てないで OpenVZ が使えるようになることである.特に関心のあるユースケースは OpenVZ ベースのコンテナを使っていて,激しいリソースのオーバーコミットを行っているホスティングプロバイダである.このようなシナリオでは,カーネルが生き残り続けるには,システムの他のリソースを使っているコンテナを信用することはできない.それゆえ,コンテナ内のリソース消費のアカウンティングを蓄積することはとても重要である. Glauber は全体的なカーネルメモリにたいするアカウントをしたいと思った.slab オブジェクトとカーネルスタックページを含む.最初に,彼はタスクごとのカーネルスタックの使用量のトラッキングのサポートをマージしようとした (カーネルスタックの使用量のトラッキングを行う理由の一つは fork bomb の軽減のためのメカニズムを提供でき,cgroup 内部への影響を制限できるからである).カーネルスタックの使用量をトラッキングすることから始める根拠は,他への影響がまずまず少ない変更であるということである (スラブ使用量のトラッキングと違って).しかし,後で必要となる全てのメモリcgroupの変更は大きなメモリアカウンティングの変更となる事が明らかであった.この取り組みの小さなピースはユーザインターフェース (ie. cgroupディレクトリ内の設定ファイル等) がどのように見えるかを示すのに役立つだろう. Glauber はスラブアロケーションの使用量をトラックするために必要な追加の変更についても議論にとりかかった.彼の説明は詳細であった.しかし,とてもおおまかに言うと,cgroup に対してアロケートされた slab ページは,そのcgroupが独占的に使用するだろうということである.本質的に,それぞれの cgroup は,キャッシュのためにアロケートされたページが常にその一つのメモリcgroupのために独占的に使われるのと同様に,任意のオブジェクトタイプのために自身のスラブキャッシュを取得するだろうということである.スラブメタデータはそのcgroup固有のスラブ上にコピーされるだろう.それゆえ,例えば,dentry がタッチされた場合,そのcgroupのためのcgroup用のdentryキャッシュの作成のゆっくりとしたトリガとなるだろう.この点から,そのcgroupによってアロケートされた全てのdentryは,グローバルなdentryキャッシュからは取得されず,cgroup固有のキャッシュから取得されるだろう.これの副次的効果としてページは同じcgroupに属するオブジェクトで満たされることがある.提案された変更の意図は関係ないcgroupがお互いのメモリをpinningすることを防げることである.これは,とあるcgroupのBが,関係のないcgroup Aがチャージを行い,確保を続けているメモリの使用を強要するような事を防げる.例えば,もしcgroup AとBがシェアするスラブページ内にオブジェクトを割り当てられ,Aはその制限に達したことにより無理矢理使用量を減少させられるような事になるような場合,その全てのスラブオブジェクトを解放しようとするだろうが,チャージをしたスラブページを解放するのは失敗し続けることになるだろう. 誰かが非常に少ないユーザメモリの使用で大量のページテーブルを割り当てるような不運な負荷を作成するのは簡単だということに言及し,ページテーブルページについて具体的に尋ねた.Glauber はメモリcgroup特有のGFP(Get Free Page)フラグを使ってこれらのページをトラックするのは簡単だと感じた. Glauberの現在の提案は,カーネルメモリアロケーションの選択的なアカウンティングを行う.明確にアノテートされたアロケーションだけがトラッキングされる.Peter Zijlstra は尋ねた.なぜデフォルトで全カーネルメモリのアロケーションをアカウントしないのか?と.Glauber は,これにはパフォーマンスのコストが存在するだろうと指摘した.そしてcgroupのコストが非常に高いシチュエーションを防ぎたいと思った.いくつかの視点から,デフォルトでcgroupのチャージを行うのは簡単だったが,それがつかえるようになるには非常に遅くなるだろう. 部屋の参加者の中には,グループがカーネル + ユーザメモリのアカウンティングについて再議論する事を要求した者もいた.これは 2012 年 4 月の LSF/MM meeting で議論されたが,これはそれが正しいアプローチかどうかまだ確実ではない点が存在する事が明らかだった. 大体,詳細は以下のようなものである.カーネルが専用に使うためにアロケートされたページがどれぐらいあるかをトラックするページカウンタ (カーネルページカウンタ,memory.kmem.usage_in_bytes) と,カーネルとユーザページのアロケーションの両方をカウントするカウンタ (カーネル + ユーザページカウンタ,memory.usage_in_bytes) が存在する.システム管理者が設定できる二つの類似の制限次第で,この実装で三つの異なるユースケースが有効になる. memory.kmem.limit_in_bytes == unlimited: この設定は,ユーザはカーネルメモリのアカウンティングには全く興味がないことが言える.ユーザメモリだけが制限され,アカウントされる.これはデフォルトであり,今までのメモリコントローラが提供してきたものである. memory.kmem.limit_in_bytes < memory.limit_in_bytes: この設定はカーネルメモリがどのくらいアロケートされているかのきめの細かいコントロールを可能にする.ユーザメモリの量に依存して,kmem のリミットに最初にヒットして,アロケーションが失敗することが起こりそうである.この設定を行うユーザは,カーネルメモリの使用量のサイズがカーネルリリース間で異なる可能性があることをしっておくべきである. memory.kmem.limit_in_bytes >= memory.limit_in_bytes: この設定はカーネルなのかユーザなのか詳細には触れないままにどのくらいのメモリが使われているのかについて制限することが可能となる.ユーザは両方の和にのみ興味がある. Glauber の意見では,上で述べたような二つの統合されたカウンターを持つことは,分割した専用のカーネル,ユーザメモリのカウンタを持つよりもシンプルになるということである.しかし,Michal Hocko は,これは OOM の決定を難しくすると指摘した (i.e., カーネルは間違った決定を下すかもしれない.多数のメモリを使わないプログラムが大量のカーネルメモリの使用が原因でkillされるというような).Glauber は,現在の OOM-killing の決定も同じ問題を持っていおり,もし彼のアプローチがこれらの問題をより簡単に引き起こすのであれば,すぐにそれが判明して修正される可能性が高いということを根拠に自身の意見を擁護した. There were no fundamental objections to Glauber's patch series to add support for accounting for pages allocated for the stack. However, three things were requested: Documentation of the use cases and how the counters are to be interpreted by the system administrator. It will be up to other interested users to object if their use cases are not addressed. Incorporation of feedback on the patches, including issues with naming and minor inconsistencies in the API. The patch series still needs “a bit of love”. A demonstration of the behavior in a NUMA environment in order to determine whether the implementation has problems with CPU cache-line bouncing. If a single counter is shared for an entire cgroup and processes in that cgroup run on multiple nodes then the cache line has to “bounce” on each write to keep the data coherent. It is desirable that Glauber tests in advance how kernel performance is affected if processes are running on multiple nodes. Potentially, every kernel allocation updates a counter shared between NUMA nodes and this would scale very badly. It's worth noting that NUMA is not the key factor here, but checking the behavior in a NUMA environment is a good test because NUMA makes the effects of cache-line bouncing much more visible. (Glauber has subsequently posted a round of benchmarks.) |