forked from heroku/erlang-in-anger
-
Notifications
You must be signed in to change notification settings - Fork 9
/
107-memory-leaks-ja.tex
594 lines (458 loc) · 77.4 KB
/
107-memory-leaks-ja.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
%\chapter{Memory Leaks}
\chapter{メモリリーク}
\label{chap:memory-leaks}
% There are truckloads of ways for an Erlang node to bleed memory. They go from extremely simple to astonishingly hard to figure out (fortunately, the latter type is also rarer), and it's possible you'll never encounter any problem with them.
Erlangノードがメモリを垂れ流す原因は山ほどあります。それらは非常に簡単に見つけられるものから驚くほど見つけにくいものまであり(幸いにして、後者は稀です)、場合によってはそれらのメモリリークが何の問題も引き起こさないこともあります。
% You will find out about memory leaks in two ways:
メモリリークについては以下の2通りの方法で知ることができます:
\begin{enumerate*}
% \item A crash dump (see Chapter \ref{chap:crash-dumps});
\item クラッシュダンプ(\ref{chap:crash-dumps}を参照)
% \item By finding a worrisome trend in the data you are monitoring.
\item モニタリングしているデータにやっかいな傾向を見つける
\end{enumerate*}
% This chapter will mostly focus on the latter kind of leak, because they're easier to investigate and see grow in real time. We will focus on finding what is growing on the node and common remediation options, handling binary leaks (they're a special case), and detecting memory fragmentation.
この章では主に後者のようなリークに焦点を当てます。その理由は、こちらのほうが調査しやすく、増加がリアルタイムで見えるためです。ここでは、ノード上で何の数値が伸びているか、一般的な対処の選択肢、(特別なケースである)バイナリリーク、メモリの断片化の発見方法を扱います。
% \section{Common Sources of Leaks}
\section{よくあるリークの原因}
% Whenever someone calls for help saying "oh no, my nodes are crashing", the first step is always to ask for data. Interesting questions to ask and pieces of data to consider are:
誰かが「助けて!ノードがクラッシュしているの!」と助けを求めてきたとき、最初にするべきことは常にデータを求めることです。ここで重要な質問やデータには以下のようなものがあります:
\begin{itemize*}
% \item Do you have a crash dump and is it complaining about memory specifically? If not, the issue may be unrelated. If so, go dig into it, it's full of data.
\item クラッシュダンプはありますか、そしてそれは特にメモリについて文句を言っていますか?もし違うのならば、この問題はメモリとは関係がない可能性があります。もしそうであるならば、クラッシュダンプを掘り返しましょう。データが山のようにあります。
% \item Are the crashes cyclical? How predictable are they? What else tends to happen at around the same time and could it be related?
\item クラッシュは周期的に起こりますか?どのくらい予測可能ですか?クラッシュと同じくらいの時間にはほかに何が起こる傾向があり、関連していそうですか?
% \item Do crashes coincide with peaks in load on your systems, or do they seem to happen at more or less any time? Crashes that happen especially \emph{during} peak times are often due to bad overload management (see Chapter \ref{chap:overload}). Crashes that happen at any time, even when load goes down following a peak are more likely to be actual memory issues.
\item クラッシュは負荷のピークと同じくして起こりますか、それともほぼ無関係にどんなときでも起こりますか?特にピーク\emph{中に}起こるクラッシュはしばしば負荷のマネジメントの失敗から来ます(\ref{chap:overload}を参照)。どんな状況でも起こるクラッシュ、特にピーク後に負荷が減っても起こるようなクラッシュは本当にメモリに起因する問題である可能性が高いです。
\end{itemize*}
% If all of this seems to point towards a memory leak, install one of the metrics libraries mentioned in Chapter \ref{chap:runtime-metrics} and/or \otpapp{recon} and get ready to dive in.\footnote{See Chapter \ref{chap:connecting} if you need help to connect to a running node}
これらの質問ですべてメモリリークを指し示すような回答を得たならば、\ref{chap:runtime-metrics} 章にて説明されているメトリクスライブラリまたは \otpapp{recon} をインストールし、データを探索する準備をしましょう。\footnote{動作しているノードへの接続に手助けが必要な場合は \ref{chap:connecting} 章を参照。}
% The first thing to look at in any of these cases is trends. Check for all types of memory using \expression{erlang:memory()} or some variant of it you have in a library or metrics system. Check for the following points:
どのようなケースでも、最初に見るべきはデータの傾向です。\expression{erlang:memory()} またはライブラリやメモリシステムにある類似のものを使ってすべてのメモリの種類について調べましょう。以下の点を調べるとよいです:
\begin{itemize*}
% \item Is any type of memory growing faster than others?
\item ある種類のメモリ使用量が他のタイプのメモリ使用量よりも速く伸びていますか?
% \item Is there any type of memory that's taking the majority of the space available?
\item ある種類のメモリが利用可能なメモリ空間の大部分を占めていませんか?
% \item Is there any type of memory that never seems to go down, and always up (other than atoms)?
\item ある種類(アトムを除く)のメモリ使用量が全く減りそうになく、常に増えていませんか?
\end{itemize*}
% Many options are available depending on the type of memory that's growing.
増えているメモリ使用の種類に応じて使える選択肢は様々です。
% \subsection{Atom}
\subsection{アトム}
% \emph{Don't use dynamic atoms!} Atoms go in a global table and are cached forever. Look for places where you call \function{erlang:binary\_to\_term/1} and \function{erlang:list\_to\_atom/1}, and consider switching to safer variants (\expression{erlang:binary\_to\_term(Bin, [safe])} and\newline \function{erlang:list\_to\_existing\_atom/1}).
\emph{動的にアトムを生成してはいけません!}アトムはグローバルなテーブルに保存され永久にキャッシュされるためです。\function{erlang:binary\_to\_term/1} や \function{erlang:list\_to\_atom/1} を呼んでいる場所を探し、\expression{erlang:binary\_to\_term(Bin, [safe])} や \function{erlang:list\_to\_existing\_atom/1} のような同様の機能でより安全なものを用いるようにしましょう\footnote{訳注: \function{erlang:binary\_to\_atom/2} も同様に注意が必要です。これは \function{binary\_to\_existing\_atom/2} で代用できます。}。
% If you use the \otpapp{xmerl} library that ships with Erlang, consider open source alternatives\footnote{I don't dislike \href{https://github.com/paulgray/exml}{exml} or \href{https://github.com/willemdj/erlsom}{erlsom}} or figuring the way to add your own SAX parser that can be safe\footnote{See Ulf Wiger at \href{http://erlang.org/pipermail/erlang-questions/2013-July/074901.html}{http://erlang.org/pipermail/erlang-questions/2013-July/074901.html}}.
もしErlangに同梱されている \otpapp{xmerl} ライブラリを使っているのならば、オープンソースの代替品を使うか\footnote{\href{https://github.com/paulgray/exml}{exml} や \href{https://github.com/willemdj/erlsom}{erlsom} などは悪くないでしょう}、自分で安全なSAXパーサーを追加するか\footnote{Ulf Wiger の以下の記事を参照: \href{http://erlang.org/pipermail/erlang-questions/2013-July/074901.html}{http://erlang.org/pipermail/erlang-questions/2013-July/074901.html}} を検討してみてください。
% If you do none of this, consider what you do to interact with the node. One specific case that bit me in production was that some of our common tools used random names to connect to nodes remotely, or generated nodes with random names that connected to each other from a central server.\footnote{This is a common approach to figuring out how to connect nodes together: have one or two central nodes with fixed names, and have every other one log to them. Connections will then propagate automatically.} Erlang node names are converted to atoms, so just having this was enough to slowly but surely exhaust space on atom tables. Make sure you generate them from a fixed set, or slowly enough that it won't be a problem in the long run.
これらのことをしていない場合、ノードとどのようにインタラクションを行っているかを調査してみてください。実運用で問題を起こした一つの例として、我々の使っていたいくつかの共通ツールが外部のノードに接続するときにランダムな名前を使っていたり、中央サーバーを介してお互いにつながっている\footnote{これはノード同士をどのようにつなげるかという問題を解決するためによく使われるアプローチです。一つか二つの名前の固定された中央ノードを用意し、他のノードはそれらに接続するようにします。そうすると、自ずと接続は伝搬します。}ランダムな名前のノードを生成していました。Erlangノード名はアトムに変換されるため、このような仕組みがあるだけでゆっくりとしかし確実にアトムテーブルの空間を埋め尽くしていくのでした。固定された範囲の中で生成するか、長期的に問題にならないくらい遅いペースで生成するように気をつけましょう。
% \subsection{Binary}
\subsection{バイナリ}
% See Section \ref{sec:binaries}.
\ref{sec:binaries} 章を参照。
% \subsection{Code}
\subsection{コード}
% The code on an Erlang node is loaded in memory in its own area, and sits there until it is garbage collected. Only two copies of a module can coexist at one time, so looking for very large modules should be easy-ish.
Erlangノードのコードはメモリ上の独自の領域に読み込まれ、ガベージコレクションが行われるまでその領域を使い続けます。同じモジュールの異なるバージョンは同時に2つまでしか存在できないので、巨大なモジュールを探すことによって問題のある箇所を見つけるのは簡単なのではないでしょうか。
% 若干補いすぎ?
% If none of them stand out, look for code compiled with HiPE\footnote{\href{http://www.erlang.org/doc/man/HiPE\_app.html}{http://www.erlang.org/doc/man/HiPE\_app.html}}. HiPE code, unlike regular BEAM code, is native code and cannot be garbage collected from the VM when new versions are loaded. Memory can accumulate, usually very slowly, if many or large modules are native-compiled and loaded at run time.
もしどのモジュールも特に目立って大きくないならば、HiPE\footnote{\href{http://www.erlang.org/doc/man/HiPE\_app.html}{http://www.erlang.org/doc/man/HiPE\_app.html}}でコンパイルされたコードを探してみましょう。通常のBEAMコードとは違い、HiPEコードはネイティヴコードで、新しいバージョンが読み込まれたときでもVMからガベージコレクションされないという性質があります。たくさんの、または巨大なモジュールが実行時にネイティヴコンパイルされて読み込まれた場合、メモリ使用量は徐々に蓄積されていきます。
% Alternatively, you may look for weird modules you didn't load yourself on the node and panic if someone got access to your system!
または、自分でロードしたものでない怪しげなモジュールを探して、誰かがシステムへ侵入したとパニックすることもあるでしょう!
\subsection{ETS}
% ETS tables are never garbage collected, and will maintain their memory usage as long as records will be left undeleted in a table. Only removing records manually (or deleting the table) will reclaim memory.
ETSテーブルはガベージコレクションの対象にならず、レコードがテーブル上から削除されない限りメモリを使い続けます。レコードを手動で削除するかテーブルを削除することでしか空きメモリを取り戻すことができません。
% In the rare cases you're actually leaking ETS data, call the undocumented \function{ets:i()} function in the shell. It will print out information regarding number of entries (\expression{size}) and how much memory they take (\expression{mem}). Figure out if anything is bad.
本当にETSデータでメモリリークをしているという稀なケースでは、ドキュメント化されていない \function{ets:i()} 関数をシェルで呼んでみてください。これはエントリの数 (\expression{size}) とどのくらいメモリを使っているか (\expression{mem}) についての情報を出力します。これを使って何がおかしいかを探ってみましょう。
% It's entirely possible all the data there is legit, and you're facing the difficult problem of needing to shard your data set and distribute it over many nodes. This is out of scope for this book, so best of luck to you. You can look into compression of your tables if you need to buy time, however.\footnote{See the \href{http://www.erlang.org/doc/man/ets.html\#new-2}{\expression{compressed} option for \function{ets:new/2}}}
ETS上のすべてのデータが正当なもので、データセットを複数のノードにシャーディングして分配するという難しい問題に直面している、という可能性も全くもってありえます。これはこの本の範囲外なので、あなたの幸運を祈ります。テーブルの圧縮をすることで時間を稼ぐという選択肢もあります\footnote{\href{http://www.erlang.org/doc/man/ets.html\#new-2}{\function{ets:new/2} の \expression{compressed} オプション} を見てみてください}。
% \subsection{Processes}
\subsection{プロセス}
% There are a lot of different ways in which process memory can grow. Most interesting cases will be related to a few common cases: process leaks (as in, you're leaking processes), specific processes leaking their memory, and so on. It's possible there's more than one cause, so multiple metrics are worth investigating. Note that the process count itself is skipped and has been covered before.
プロセスメモリが増えるには多くの原因があります。最も興味深いケースはいくつかの頻出ケースに関連します。例えば、プロセス自身をリークしている、特定のプロセスがメモリをリークしている、といったケースです。複数の原因に由来している可能性はあるので、複数のメトリクスを調べる価値は高いでしょう。なお、プロセスのカウントについては既出なのでここでは扱いません。
% as in, の括弧はその次の箇条書きとの訳し分けで含めた
% \subsubsection{Links and Monitors}
\subsubsection{リンクとモニタ}
% Is the global process count indicative of a leak? If so, you may need to investigate unlinked processes, or peek inside supervisors' children lists to see what may be weird-looking.
グローバルでのプロセス数がプロセスのリークの兆候を示していますか?もしそうであれば、リンクされていないプロセスを調査するか、スーパーバイザの子プロセスリストを見て何かおかしいところがないかを探りましょう。
% Finding unlinked (and unmonitored) processes is easy to do with a few basic commands:
リンクされていない、またはモニタされていないプロセスを探すのは簡単ないくつかのコマンドでできます:
\begin{VerbatimEshell}
1> [P || P <- processes(),
[{_,Ls},{_,Ms}] <- [process_info(P, [links,monitors])],
[]==Ls, []==Ms].
\end{VerbatimEshell}
% This will return a list of processes with neither. For supervisors, just fetching \newline \expression{supervisor:count\_children(SupervisorPidOrName)} and seeing what looks normal can be a good pointer.
これはリンクもモニタもされていないプロセスのリストを返します。スーパーバイザについては、\expression{supervisor:count\_children(SupervisorPidOrName)} を見て正常な状態がどんなものかを見てみるのがよいでしょう。
% \subsubsection{Memory Used}
\subsubsection{メモリ使用}
% The per-process memory model is briefly described in Subsection \ref{subsec:memory-process-level}, but generally speaking, you can find which individual processes use the most memory by looking for their \term{memory} attribute. You can look things up either as absolute terms or as a sliding window.
プロセスごとのメモリモデルについては \ref{subsec:memory-process-level} 節で簡潔に紹介していますが、一般的には、どの個別のプロセスが一番メモリを使っているかはプロセスの \term{memory} 属性を見ることでわかります。この値は、メモリ使用量の絶対値として、または区間を指定してその中での変化率として見ることができます。
% sliding window -> 範囲指定と訳したけど自信がない
% For memory leaks, unless you're in a predictable fast increase, absolute values are usually those worth digging into first:
メモリリークについていえば、急激な上昇が予測可能でない限りにおいて、絶対値から順に見ていくのがよいでしょう:
\begin{VerbatimEshell}
1> recon:proc_count(memory, 3).
[{<0.175.0>,325276504,
[myapp_stats,
{current_function,{gen_server,loop,6}},
{initial_call,{proc_lib,init_p,5}}]},
{<0.169.0>,73521608,
[myapp_giant_sup,
{current_function,{gen_server,loop,6}},
{initial_call,{proc_lib,init_p,5}}]},
{<0.72.0>,4193496,
[gproc,
{current_function,{gen_server,loop,6}},
{initial_call,{proc_lib,init_p,5}}]}]
\end{VerbatimEshell}
% Attributes that may be interesting to check other than \term{memory} may be any other fields in Subsection \ref{subsec:digging-procs}, including \term{message\_queue\_len}, but \term{memory} will usually encompass all other types.
\term{message\_queue\_len} のような \term{memory} 以外の示唆的な属性は \ref{subsec:digging-procs} 節にて紹介していますが、だいたいの場合 \term{memory} でカバーできるでしょう。
% \subsubsection{Garbage Collections}
\subsubsection{ガベージコレクション}
\label{subsubsec:leak-gc}
% It is very well possible that a process uses lots of memory, but only for short periods of time. For long-lived nodes with a large overhead for operations, this is usually not a problem, but whenever memory starts being scarce, such spiky behaviour might be something you want to get rid of.
プロセスがものすごく短期間だけ大量のメモリを使うということは大いにありえます。操作のオーバーヘッドの大きい長命のノードではこれらはだいたいの場合問題ではありませんが、メモリが少なくなってきたときにはこのようなスパイク状の挙動こそが本当に対処したいものかもしれません。
% Monitoring all garbage collections in real-time from the shell would be costly. Instead, setting up Erlang's system monitor\footnote{\href{http://www.erlang.org/doc/man/erlang.html\#system\_monitor-2}{http://www.erlang.org/doc/man/erlang.html\#system\_monitor-2}} might be the best way to go at it.
リアルタイムですべてのガベージコレクションを監視するのはコストが高いです。代わりに、Erlangのシステムモニタ\footnote{\href{http://www.erlang.org/doc/man/erlang.html\#system\_monitor-2}{http://www.erlang.org/doc/man/erlang.html\#system\_monitor-2}}を使うのが一番よい方法かもしれません。
% Erlang's system monitor will allow you to track information such as long garbage collection periods and large process heaps, among other things. A monitor can temporarily be set up as follows:
Erlangのシステムモニタを使えば例えば長いガベージコレクションの期間や巨大なプロセスヒープなどの情報を追跡できます。モニタは以下のようにして設定できます:
\begin{VerbatimEshell}
1> erlang:system_monitor().
undefined
2> erlang:system_monitor(self(), [{long_gc, 500}]).
undefined
3> flush().
Shell got {monitor,<4683.31798.0>,long_gc,
[{timeout,515},
{old_heap_block_size,0},
{heap_block_size,75113},
{mbuf_size,0},
{stack_size,19},
{old_heap_size,0},
{heap_size,33878}]}
5> erlang:system_monitor(undefined).
{<0.26706.4961>,[{long_gc,500}]}
6> erlang:system_monitor().
undefined
\end{VerbatimEshell}
% The first command checks that nothing (or nobody else) is using a system monitor yet — you don't want to take this away from an existing application or coworker.
最初のコマンドは既にシステムモニタを使っていないかどうかをチェックします---使用中の他のアプリケーションや同僚からシステムモニタを奪いたくはないでしょう。
% The second command will be notified every time a garbage collection takes over 500 milliseconds. The result is flushed in the third command. Feel free to also check for \expression{\{large\_heap, NumWords\}} if you want to monitor such sizes.
二つ目のコマンドはガベージコレクションに500ミリ秒以上かかったときに通知されます。その結果は三つ目のコマンドでフラッシュされます。ヒープサイズを監視したい場合は \expression{\{large\_heap, NumWords\}} を使うとよいでしょう。
% Be careful to start with large values at first if you're unsure. You don't want to flood your process' mailbox with a bunch of heaps that are 1-word large or more, for example.
自信がない場合は大きな値でモニタリングを開始しましょう。例えば、1ワード以上のサイズを持つヒープの情報でプロセスのメールボックスを溢れさせたくはないですよね?
% Command 5 unsets the system monitor (exiting or killing the monitor process also frees it up), and command 6 validates that everything worked.
五つ目のコマンドはシステムモニタを解除し(モニタプロセスが終了またはkillされるとシステムモニタは同様に解除されます)、その次のコマンドでちゃんと解除されたかどうかを確認できます。
% You can then find out if such monitoring messages tend to coincide with the memory increases that seem to result in leaks or overuses, and try to catch culprits before things are too bad. Quickly reacting and digging into the process (possibly with \function{recon:info/1}) may help find out what's wrong with the application.
これを使って、監視メッセージがリークやメモリの過剰使用に由来していそうなメモリ使用量上昇と重なっていないかを見ることができ、事態が悪化する前に犯人をつきとめることができます。すばやく対応してプロセスの中身を調べる(\function{recon:info/1} などを使います)ことでアプリケーションの問題を発見できるでしょう。
% \subsection{Nothing in Particular}
\subsection{特に何もない場合}
% If nothing seems to stand out in the preceding material, binary leaks (Section \ref{sec:binaries}) and memory fragmentation (Section \ref{sec:memory-fragmentation}) may be the culprits. If nothing there fits either, it's possible a C driver, NIF, or even the VM itself is leaking. Of course, a possible scenario is that load on the node and memory usage were proportional, and nothing specifically ended up being leaky or modified. The system just needs more resources or nodes.
これまでに書かれた内容があてはまらない場合、バイナリリーク (\ref{sec:binaries} 章) やメモリのフラグメンテーション (\ref{sec:memory-fragmentation} 章) が原因である可能性があります。もしそのいずれでもなければ、Cドライバ、NIF、またはVM自身がメモリリークを起こしている可能性があります。もちろん、ノードの負荷とメモリ使用が比例していて特に何もメモリリークを起こしていないという可能性もあります。単にシステムはもっとリソースやノードを必要としているだけなのかもしれません。
%\section{Binaries}
\section{バイナリ}
\label{sec:binaries}
%Erlang's binaries are of two main types: ProcBins and Refc binaries\footnote{\href{http://www.erlang.org/doc/efficiency\_guide/binaryhandling.html\#id65798}{http://www.erlang.org/doc/efficiency\_guide/binaryhandling.html\#id65798}}. Binaries up to 64 bytes are allocated directly on the process's heap, and their entire life cycle is spent in there. Binaries bigger than that get allocated in a global heap for binaries only, and each process to use one holds a local reference to it in its local heap. These binaries are reference-counted, and the deallocation will occur only once all references are garbage-collected from all processes that pointed to a specific binary.
Erlangのバイナリは2つの主な型から成ります: ProcBinとrefcバイナリです\footnote{\href{http://www.erlang.org/doc/efficiency\_guide/binaryhandling.html\#id65798}{http://www.erlang.org/doc/efficiency\_guide/binaryhandling.html\#id65798}}。64バイトまでのバイナリはプロセスのヒープ上に直接割り当てられ、それらの全ライフサイクルはそのヒープ上で費やされます。それより大きいバイナリは、バイナリのためだけのグローバルヒープ上に割り当てられ、バイナリを使うプロセスは、ローカルヒープ内にローカル参照を保持します。これらのバイナリは参照をカウントされており、割り当ての解除は、ある特定のバイナリを参照しているすべてのプロセスのすべての参照がガベージコレクトされたときに1回だけ発生します。
%In 99\% of the cases, this mechanism works entirely fine. In some cases, however, the process will either:
99\%のケースでは、このメカニズムは正常に動作します。しかしながら、一部のケースでは、このプロセスはうまくいきません:
\begin{enumerate*}
%\item do too little work to warrant allocations and garbage collection;
\item メモリの割り当てとガベージコレクションを保証することについてほとんど機能しません。
%\item eventually grow a large stack or heap with various data structures, collect them, then get to work with a lot of refc binaries. Filling the heap again with binaries (even though a virtual heap is used to account for the refc binaries' real size) may take a lot of time, giving long delays between garbage collections.
\item 最終的には様々なデータ構造の大きなスタックやヒープを広げ、それらを集め、そして沢山のrefcバイナリと動きます。バイナリでヒープを満たすことは(たとえ仮想ヒープが、そのrefcバイナリの実際のサイズに対するカウントに使われていても)、長い時間がかかってしまうかもしれません。それは、ガベージコレクションの間に長い遅延を発生させます。
\end{enumerate*}
%\subsection{Detecting Leaks}
\subsection{リークを検出する}
%Detecting leaks for reference-counted binaries is easy enough: take a measure of all of each process' list of binary references (using the \expression{binary} attribute), force a global garbage collection, take another snapshot, and calculate the difference.
参照カウントされたバイナリによるリークを検出することは十分に簡単です。すべての各プロセスのバイナリ参照のリストを基準とし(\expression{binary}属性を使って)、ガベージコレクションを強制的に実行し、別のスナップショットを取得し、その差を計算します。
%This can be done directly with \function{recon:bin\_leak(Max)} and looking at the node's total memory before and after the call:
この作業は \function{recon:bin\_leak(Max)} を使い、その前後でノードの全体メモリ使用量を見ることで端的に実行することができます。
\begin{VerbatimEshell}
1> recon:bin_leak(5).
[{<0.4612.0>,-5580,
[{current_function,{gen_fsm,loop,7}},
{initial_call,{proc_lib,init_p,5}}]},
{<0.17479.0>,-3724,
[{current_function,{gen_fsm,loop,7}},
{initial_call,{proc_lib,init_p,5}}]},
{<0.31798.0>,-3648,
[{current_function,{gen_fsm,loop,7}},
{initial_call,{proc_lib,init_p,5}}]},
{<0.31797.0>,-3266,
[{current_function,{gen,do_call,4}},
{initial_call,{proc_lib,init_p,5}}]},
{<0.22711.1>,-2532,
[{current_function,{gen_fsm,loop,7}},
{initial_call,{proc_lib,init_p,5}}]}]
\end{VerbatimEshell}
%This will show how many individual binaries were held and then freed by each process as a delta. The value \expression{-5580} means there were 5580 fewer refc binaries after the call than before.
この例では、どれくらいの独立したバイナリが保持された後に開放されているかを差分として表しています。この \expression{-5580} は、この関数が実行される前と後で5580少ないrefcバイナリが存在したことを表しています。
%It is normal to have a given amount of them stored at any point in time, and not all numbers are a sign that something is bad. If you see the memory used by the VM go down drastically after running this call, you may have had a lot of idling refc binaries.
これが常時一定量あることを示しているのは普通のことですし、すべての数字が何かが悪いことを示しているわけではありません。もしあなたが、VMが使用しているメモリがこの関数の呼び出しよりも後に大幅に減っていることを確認したら、それはアイドル状態のrefcバイナリがあるということかもしれません。
%Similarly, if you instead see some processes hold impressively large numbers of them\footnote{We've seen some processes hold hundreds of thousands of them during leak investigations at Heroku!}, that might be a good sign you have a problem.
同様に、もしそうではなく、あなたがいくつかのプロセスがびっくりするほど大量のrefcバイナリを保持していることを見つけるのであれば\footnote{筆者はHerokuでのメモリリークの調査中に、いくつかのプロセスが10万件ほどのrefcバイナリを保持しているのを見つけました!}、それは問題があるというよい証拠となるかもしれません。
%You can further validate the top consumers in total binary memory by using the special \expression{binary\_memory} attribute supported in \otpapp{recon}:
さらに、あなたは \otpapp{recon} でサポートされている特別な \expression{binary\_memory} 属性を使うことによって、バイナリメモリの合計中最もメモリを消費しているものを検証することができます:
\begin{VerbatimEshell}
1> recon:proc_count(binary_memory, 3).
[{<0.169.0>,77301349,
[app_sup,
{current_function,{gen_server,loop,6}},
{initial_call,{proc_lib,init_p,5}}]},
{<0.21928.1>,9733935,
[{current_function,{erlang,hibernate,3}},
{initial_call,{proc_lib,init_p,5}}]},
{<0.12386.1172>,7208179,
[{current_function,{erlang,hibernate,3}},
{initial_call,{proc_lib,init_p,5}}]}]
\end{VerbatimEshell}
%This will return the \var{N} top processes sorted by the amount of memory the refc binaries reference to hold, and can help point to specific processes that hold a few large binaries, instead of their raw amount. You may want to try running this function \emph{before} \function{recon:bin\_leak/1}, given the latter garbage collects the entire node first.
この関数は \var{N} の、refcバイナリの参照が保持しているメモリ量によって並び替えられた上位のプロセスを返し、バイナリの実際の量を返す代わりに、いくつかの大きなバイナリを保持する特定のプロセスを示してくれます。
%\subsection{Fixing Leaks}
\subsection{リークを修正する}
%Once you've established you've got a binary memory leak using \function{recon:bin\_leak(Max)}, it should be simple enough to look at the top processes and see what they are and what kind of work they do.
\function{recon:bin\_leak(Max)} を使ってバイナリメモリリークを見つけることができたなら、そのトップのプロセスを見てそれがなにか、そしてどんな種類の仕事をしているものかを見つけることは十分にシンプルでしょう。
%Generally, refc binaries memory leaks can be solved in a few different ways, depending on the source:
一般的に、 refcバイナリのメモリリークはいくつかの手段によって解決でき、それは原因によって異なります:
\begin{itemize*}
%\item call garbage collection manually at given intervals (icky, but somewhat efficient);
\item ガベージコレクションを一定の間隔で手動で実行します(これはうずうずしますが、ある程度効果的です)
%\item stop using binaries (often not desirable);
\item バイナリを使うことをやめる(望ましいということはめったにありません)
%\item use \function{binary:copy/1-2}\footnote{\href{http://www.erlang.org/doc/man/binary.html\#copy-1}{http://www.erlang.org/doc/man/binary.html\#copy-1}} if keeping only a small fragment (usually less than 64 bytes) of a larger binary;\footnote{It might be worth copying even a larger fragment of a refc binary. For example, copying 10 megabytes off a 2 gigabytes binary should be worth the short-term overhead if it allows the 2 gigabytes binary to be garbage-collected while keeping the smaller fragment longer.}
\item もし大きなバイナリのうち小さな一部分(たいていの場合64バイト未満)のみを保持しているのであれば\footnote{refcバイナリよりも大きな領域をコピーするのに値するかもしれません。例えば、2ギガバイトのバイナリがその小さな一部分を保持し続けている間にガベージコレクトされるのであれば、2ギガバイトのバイナリのうち10メガバイトをコピーすることは小さなオーバーヘッドに値します。}、\function{binary:copy/1-2}\footnote{\href{http://www.erlang.org/doc/man/binary.html\#copy-1}{http://www.erlang.org/doc/man/binary.html\#copy-1}}を使います。
%\item move work that involves larger binaries to temporary one-off processes that will die when they're done (a lesser form of manual GC!);
\item 大きなバイナリを含むものを一時的な一度限りのプロセスに移します。このプロセスは実行を終えると死にます(手動のGCの簡単な方法です!)。
%\item or add hibernation calls when appropriate (possibly the cleanest solution for inactive processes).
\item もしくは適切なタイミングでハイバネーション呼び出しを追加します(アクティブではないプロセスに対する最もきれいな解決法かもしれません)。
\end{itemize*}
%The first two options are frankly not agreeable and should not be attempted before all else failed. The last three options are usually the best ones to be used.
最初の2つの操作は正直に言って同意できるものではなく、その他の方法が失敗する前に試すべきではありません。後の3つの選択肢は通常、実施するのに最もよいものでしょう。
%\subsubsection{Routing Binaries}
\subsubsection{ルーティングバイナリ}
%There's a specific solution for a specific use case some Erlang users have reported. The problematic use case is usually having a middleman process routing binaries from one process to another one. That middleman process will therefore acquire a reference to every binary passing through it and risks being a common major source of refc binaries leaks.
何人かの Erlang ユーザが報告した、ある特定のユースケースに対する解決方法があります。問題のあるユースケースは、たいてい、あるプロセスから他のプロセスへバイナリをルーティングする中間プロセスを持っているというものです。したがって、この中間プロセスはそれを通るすべてのバイナリに対する参照を獲得し、 refc バイナリリークのよくある主要な原因になる危険があります。
%The solution to this pattern is to have the router process return the pid to route to and let the original caller move the binary around. This will make it so that only processes that do \emph{need} to touch the binaries will do so.
このパターンに対する解決方法は、ルータプロセスにルーティング先の pid を返却させ、呼び出し元にバイナリを移動させることです。バイナリに触る必要があるプロセスのみがバイナリに触ることになるため、これは効果があるでしょう。
%A fix for this can be implemented transparently in the router's API functions, without any visible change required by the callers.
この問題に対する修正はルータの API 関数において、呼び出し側の目に見える変更が必要とされることなしにわかりやすく実装することができるでしょう。
%\section{Memory Fragmentation}
\section{メモリフラグメンテーション}
\label{sec:memory-fragmentation}
%Memory fragmentation issues are intimately related to Erlang's memory model, as described in Section \ref{subsec:erlang-memory-model}. It is by far one of the trickiest issues of running long-lived Erlang nodes (often when individual node uptime reaches many months), and will show up relatively rarely.
メモリフラグメンテーションの問題は\ref{subsec:erlang-memory-model}の章で記述されているようにErlangのメモリモデルと密接に関連しています。これは長時間動いているErlangノードの最も難しい問題(個々のノードの稼働時間が数ヶ月にも達する時によく起こる)の一つで、比較的まれに見られます。
%The general symptoms of memory fragmentation are large amounts of memory being allocated during peak load, and that memory not going away after the fact. The damning factor will be that the node will internally report much lower usage (through \function{erlang:memory()}) than what is reported by the operating system.
一般的なメモリフラグメンテーションの症状は、ピーク時に大量のメモリが割り当てられ、その後に解放されないというものです。ノードが内部的にレポートするメモリ使用量(\function{erlang:memory()}を通した)がOSがレポートするものに比べてとても少ない場合は、明らかにこれが要因です。
%\subsection{Finding Fragmentation}
\subsection{フラグメンテーションを見つける}
%The \module{recon\_alloc} module was developed specifically to detect and help point towards the resolution of such issues.
\module{recon\_alloc}モジュールはこのような問題を見つけたり、解決の助けとなるように開発されました。
%Given how rare this type of issue has been so far over the community (or happened without the developers knowing what it was), only broad steps to detect things are defined. They're all vague and require the operator's judgement.
この種の問題がコミュニティにとって稀であった(または開発者がそれが何であるか知らずに起こった)と考えると、手がかりを見つけるための様々な手順が定義されているだけです。これらはすべて曖昧で運用者の判断が必要です。
%\subsubsection{Check Allocated Memory}
\subsubsection{割り当て済みのメモリを確認する}
%Calling \function{recon\_alloc:memory/1} will report various memory metrics with more flexibility than \function{erlang:memory/0}. Here are the possibly relevant arguments:
\function{recon\_alloc:memory/1}を呼び出すことで様々なメモリのメトリクスを\function{erlang:memory/0}より柔軟にレポートします。これらは関連する引数です。
\begin{enumerate}
%\item call \expression{recon\_alloc:memory(usage)}. This will return a value between 0 and 1 representing a percentage of memory that is being actively used by Erlang terms versus the memory that the Erlang VM has obtained from the OS for such purposes. If the usage is close to 100\%, you likely do not have memory fragmentation issues. You're just using a lot of it.
\item \expression{recon\_alloc:memory(usage)}を呼び出す。これはErlang VMがOSから取得したメモリに対して、Erlang項としてアクティブに使われているメモリの割合を0から1の値として返します。使用率が100\%に近い場合は、おそらくメモリフラグメンテーションの問題ではありません。たくさんメモリを使っているだけです。
%\item check if \expression{recon\_alloc:memory(allocated)} matches what the OS reports.\footnote{You can call \expression{recon\_alloc:set\_unit(Type)} to set the values reported by \module{recon\_alloc} in bytes, kilobytes, megabytes, or gigabytes} It should match it fairly closely if the problem is really about fragmentation or a memory leak from Erlang terms.
\item \expression{recon\_alloc:memory(allocated)}とOSがレポートするものがマッチするか確認してください。\footnote{\module{recon\_alloc}でレポートされた値をバイト(bytes)、キロバイト(kilobytes)、メガバイト(megabytes)、ギガバイト(gigabytes)表記にするために\expression{recon\_alloc:set\_unit(Type)}を呼び出すことができます。}問題が本当にフラグメンテーションかErlang項のメモリリークの場合には、それはかなり近く一致する必要があります。
\end{enumerate}
%That should confirm if memory seems to be fragmented or not.
これでメモリがフラグメントしているかどうかを確認できるはずです。
%\subsubsection{Find the Guilty Allocator}
\subsubsection{問題のアロケータを見つける}
%Call \expression{recon\_alloc:memory(allocated\_types)} to see which type of util allocator (see Section \ref{subsec:erlang-memory-model}) is allocating the most memory. See if one looks like an obvious culprit when you compare the results with \expression{erlang:memory()}.
どの種別のアロケータ(\ref{subsec:erlang-memory-model}を参照)がほとんどのメモリを確保しているか見るために\expression{recon\_alloc:memory(allocated\_types)}を呼び出してください。結果を\expression{erlang:memory()}と比較して、明らかに犯人のように見えるものがあるか確認してください。
%Try \expression{recon\_alloc:fragmentation(current)}. The resulting data dump will show different allocators on the node with various usage ratios.\footnote{More information is available at \href{http://ferd.github.io/recon/recon\_alloc.html}{http://ferd.github.io/recon/recon\_alloc.html}}
\expression{recon\_alloc:fragmentation(current)}を試してください。データのダンプ結果はノード上のそれぞれ異なったアロケータを様々な使用比率とともに表示します。\footnote{\href{http://ferd.github.io/recon/recon\_alloc.html}{http://ferd.github.io/recon/recon\_alloc.html}に詳細な情報があります。}
%If you see very low ratios, check if they differ when calling \expression{recon\_alloc:fragmentation(max)}, which should show what the usage patterns were like under your max memory load.
とても少ない比率の場合、それらが\expression{recon\_alloc:fragmentation(max)}を呼び出した結果と異なるか確認してください。これは最大のメモリ負荷の状況下での使用量のパターンを表示するはずです。
%If there is a big difference, you are likely having issues with memory fragmentation for a few specific allocator types following usage spikes.
大きく異なる場合は、使用率にスパイクのある特定の種別のアロケータについてメモリフラグメンテーションの問題がある可能性があります。
%\subsection{Erlang's Memory Model}
\subsection{Erlangのメモリモデル}
\label{subsec:erlang-memory-model}
%\subsubsection{The Global Level}
\subsubsection{グローバルレベル}
%To understand where memory goes, one must first understand the many allocators being used. Erlang's memory model, for the entire virtual machine, is hierarchical. As shown in Figure \ref{fig:allocators}, there are two main allocators, and a bunch of sub-allocators (numbered 1-9). The sub-allocators are the specific allocators used directly by Erlang code and the VM for most data types:\footnote{The complete list of where each data type lives can be found in \href{https://github.com/erlang/otp/blob/maint/erts/emulator/beam/erl\_alloc.types}{erts/emulator/beam/erl\_alloc.types}}
メモリがどこへ行くのか理解するために、たくさんのメモリアロケータが使われていることを最初に理解しなければなりません。VM 全体のためのErlangのメモリモデルは階層的になっています。\ref{fig:allocators}の図にあるように、2つのメインとなるアロケータがあり、他にサブアロケータ(1−9までの番号が付けられた)があります。サブアロケータはErlangコードとVMから使われるほとんどのデータ型用の特定のアロケータです。\footnote{各データ型の完全なリストはこちらで見つけることができます。\href{https://github.com/erlang/otp/blob/maint/erts/emulator/beam/erl\_alloc.types}{erts/emulator/beam/erl\_alloc.types}}
\begin{figure}
\includegraphics{memory-allocs.pdf}%
%\caption{Erlang's Memory allocators and their hierarchy. Not shown is the special \emph{super carrier}, optionally allowing to pre-allocate (and limit) all memory available to the Erlang VM since R16B03.}%
\caption{Erlangのメモリアロケータとそれらの階層。R16B03 からオプションでErlang VMのために利用可能なすべてのメモリを事前に確保すること(加えて制限も)が許されている特別な\emph{スーパーキャリア}は図示していません。}%
\label{fig:allocators}
\end{figure}
\begin{enumerate*}
%\item \term{temp\_alloc}: does temporary allocations for short use cases (such as data living within a single C function call).
\item \term{temp\_alloc}: 短期間のユースケース用(1つのCの関数呼び出しで使われるデータのような)の一時的なアロケーション。
%\item \term{eheap\_alloc}: heap data, used for things such as the Erlang processes' heaps.
\item \term{eheap\_alloc}: Erlangプロセスのヒープなどに使われるヒープデータ。
%\item \term{binary\_alloc}: the allocator used for reference counted binaries (what their 'global heap' is). Reference counted binaries stored in an ETS table remain in this allocator.
\item \term{binary\_alloc}: リファレンスカウントされたバイナリ(Erlang プロセスで共有される「グローバルなヒープ」そのもの)に使われるアロケータ。ETSテーブルに格納されたリファレンスカウントされたバイナリはこのアロケータに残ります。
%\item \term{ets\_alloc}: ETS tables store their data in an isolated part of memory that isn't garbage collected, but allocated and deallocated as long as terms are being stored in tables.
\item \term{ets\_alloc}: ETSテーブルはガベージコレクトされないメモリの独立した部分へデータを格納しますが、項がテーブルに格納されている限り、割り当ておよび割り当て解除されます。
%\item \term{driver\_alloc}: used to store driver data in particular, which doesn't keep drivers that generate Erlang terms from using other allocators. The driver data allocated here contains locks/mutexes, options, Erlang ports, etc.
\item \term{driver\_alloc}: とりわけドライバーデータを格納するために使われ、他のアロケータを使ってErlang項を生成したドライバーは保持しません。ここで割り当てられたドライバーデータはロック/ミューテックス、オプション、Erlangポートなどを含みます。
%\item \term{sl\_alloc}: short-lived memory blocks will be stored there, and include items such as some of the VM's scheduling information or small buffers used for some data types' handling.
\item \term{sl\_alloc}: 短期間生存するメモリブロックがここに保存されます。VMのスケジューリング情報の一部や、いくつかのデータ型の処理に使う小さなバッファを含みます。
%\item \term{ll\_alloc}: long-lived allocations will be in there. Examples include Erlang code itself and the atom table, which stay there.
\item \term{ll\_alloc}: 長期間生存するアロケーションはここにあります。例えばErlangコード自身やアトムのテーブルが存在します。
%\item \term{fix\_alloc}: allocator used for frequently used fixed-size blocks of memory. One example of data used there is the internal processes' C struct, used internally by the VM.
\item \term{fix\_alloc}: 頻繁に使われる固定サイズのメモリブロックのために使われるアロケータ。ここで使われるデータの1つとしてはVMで内部的に使われているプロセスのC構造体があります。
%\item \term{std\_alloc}: catch-all allocator for whatever didn't fit the previous categories. The process registry for named process is there.
\item \term{std\_alloc}: 上記カテゴリに適合しないものを全てキャッチするアロケータ。名前付きのプロセス用のプロセスレジストリがここにあります。
\end{enumerate*}
%By default, there will be one instance of each allocator per scheduler (and you should have one scheduler per core), plus one instance to be used by linked-in drivers using async threads. This ends up giving you a structure a bit like in Figure \ref{fig:allocators}, but split it in \var{N} parts at each leaf.
デフォルトでは、スケジューラごとに各アロケータのインスタンスが1つずつ(そしてコアごとに1つのスケジューラを持つはずです)、加えて非同期のスレッドを使うlinked-in driversで使われるインスタンスが一つあります。これは\ref{fig:allocators}の図にあるような構造をあなたに与えますが、それぞれの葉で\var{N}個の部分に分かれます。
%Each of these sub-allocators will request memory from \term{mseg\_alloc} and \term{sys\_alloc} depending on the use case, and in two possible ways. The first way is to act as a multiblock carrier (\term{mbcs}), which will fetch chunks of memory that will be used for many Erlang terms at once. For each \term{mbc}, the VM will set aside a given amount of memory (about 8MB by default in our case, which can be configured by tweaking VM options), and each term allocated will be free to go look into the many multiblock carriers to find some decent space in which to reside.
これらのサブアロケータはユースケースに応じて2つの可能な方法で\term{mseg\_alloc}と\term{sys\_alloc}からメモリを要求します。最初の方法はマルチブロックキャリア(\term{mbcs})として行動します。それは多数のErlang項のために使われるメモリの塊を一度に取得します。それぞれの\term{mbc}のために、VMは与えられたメモリ量(我々の場合はデフォルトでは8MBですが、VMオプションを調整して設定できます)を確保し、それぞれの割り当てられた項は格納されるための十分な空間を探すために、自由にたくさんのマルチブロックキャリアの中を見に行けます。
%Whenever the item to be allocated is greater than the single block carrier threshold (\term{sbct})\footnote{\href{http://erlang.org/doc/man/erts\_alloc.html\#M\_sbct}{http://erlang.org/doc/man/erts\_alloc.html\#M\_sbct}}, the allocator switches this allocation into a single block carrier (\term{sbcs}). A single block carrier will request memory directly from \term{mseg\_alloc} for the first \term{mmsbc}\footnote{\href{http://erlang.org/doc/man/erts\_alloc.html\#M\_mmsbc}{http://erlang.org/doc/man/erts\_alloc.html\#M\_mmsbc}} entries, and then switch over to \term{sys\_alloc} and store the term there until it's deallocated.
割り当てられるアイテムがシングルブロックキャリア(\term{sbct})\footnote{\href{http://erlang.org/doc/man/erts\_alloc.html\#M\_sbct}{http://erlang.org/doc/man/erts\_alloc.html\#M\_sbct}}の閾値より大きい場合はいつでも、アロケータはこのアロケーションをシングルブロックキャリア(\term{sbcs})に切り替えます。シングルブロックキャリアは最初の\term{mmsbc}\footnote{\href{http://erlang.org/doc/man/erts\_alloc.html\#M\_mmsbc}{http://erlang.org/doc/man/erts\_alloc.html\#M\_mmsbc}}のエントリに対して\term{mseg\_alloc}から直接メモリをリクエストし、その時に\term{sys\_alloc}へ切り替え、割り当てが解除されるまで項をそこへ格納します。
%So looking at something such as the binary allocator, we may end up with something similar to Figure \ref{fig:allocation-1-normal}
バイナリアロケータの例も見てみると、\ref{fig:allocation-1-normal}の図のようなものになるでしょう。
\begin{figure}
\includegraphics{allocation-1-normal.pdf}%
%\caption{Example memory allocated in a specific sub-allocator}%
\caption{特定のサブアロケータに割り当てられたメモリの例}%
\label{fig:allocation-1-normal}
\end{figure}
\FloatBarrier
%Whenever a multiblock carrier (or the first \term{mmsbc}\footnote{\href{http://erlang.org/doc/man/erts\_alloc.html\#M\_mmsbc}{http://erlang.org/doc/man/erts\_alloc.html\#M\_mmsbc}} single block carriers) can be reclaimed, \term{mseg\_alloc} will try to keep it in memory for a while so that the next allocation spike that hits your VM can use pre-allocated memory rather than needing to ask the system for more each time.
マルチブロックキャリア(または最初の\term{mmsbc}\footnote{\href{http://erlang.org/doc/man/erts\_alloc.html\#M\_mmsbc}{http://erlang.org/doc/man/erts\_alloc.html\#M\_mmsbc}}シングルブロックキャリア)が再利用できる時はいつでも、\term{mseg\_alloc}はVMに届く次のアロケーションスパイクが、毎回より多くのメモリのためシステムに問い合わせる必要はなく、事前に割り当てたメモリを使うことができるように、しばらくの間それをメモリ内に留めようとします。
%You then need to know the different memory allocation strategies of the Erlang virtual machine:
次にErlang VMの色々なメモリアロケーション戦略を知る必要があります。
\begin{enumerate*}
%\item Best fit (\term{bf})
\item Best fit (\term{bf})
%\item Address order best fit (\term{aobf})
\item Address order best fit (\term{aobf})
%\item Address order first fit (\term{aoff})
\item Address order first fit (\term{aoff})
%\item Address order first fit carrier best fit (\term{aoffcbf})
\item Address order first fit carrier best fit (\term{aoffcbf})
%\item Address order first fit carrier address order best fit (\term{aoffcaobf})
\item Address order first fit carrier address order best fit (\term{aoffcaobf})
%\item Good fit (\term{gf})
\item Good fit (\term{gf})
%\item A fit (\term{af})
\item A fit (\term{af})
\end{enumerate*}
%Each of these strategies can be configured individually for each \term{alloc\_util} allocator\footnote{\href{http://erlang.org/doc/man/erts\_alloc.html\#M\_as}{http://erlang.org/doc/man/erts\_alloc.html\#M\_as}}
これらの戦略はそれぞれの\term{alloc\_util}アロケータ\footnote{\href{http://erlang.org/doc/man/erts\_alloc.html\#M\_as}{http://erlang.org/doc/man/erts\_alloc.html\#M\_as}}ごとに個別に設定できます。
\begin{figure}
\includegraphics[max height=7cm]{allocation-strategy-1.pdf}%
\centering%
%\caption{Example memory allocated in a specific sub-allocator}%
\caption{特定のサブアロケータに割り当てられたメモリの例}%
\label{fig:allocation-strategy-1}
\end{figure}
\FloatBarrier
%For \emph{best fit} (\term{bf}), the VM builds a balanced binary tree of all the free blocks' sizes, and will try to find the smallest one that will accommodate the piece of data and allocate it there. In Figure \ref{fig:allocation-strategy-1}, having a piece of data that requires three blocks would likely end in area 3.
\emph{best fit}(\term{bf})用に、VMは全てのフリーブロックのサイズで平衡バイナリ木を構築し、データが格納できる最も小さいものを探そうとし、それを割り当てます。\ref{fig:allocation-strategy-1}の図では、3つのブロックを必要とするデータがあるとするとエリア3になるでしょう。
%\emph{Address order best fit} (\term{aobf}) will work similarly, but the tree instead is based on the addresses of the blocks. So the VM will look for the smallest block available that can accommodate the data, but if many of the same size exist, it will favor picking one that has a lower address. If I have a piece of data that requires three blocks, I'll still likely end up in area 3, but if I need two blocks, this strategy will favor the first \term{mbcs} in Figure \ref{fig:allocation-strategy-1} with area 1 (instead of area 5). This could make the VM have a tendency to favor the same carriers for many allocations.
\emph{Address order best fit}(\term{aobf})は似たように働きますが、代わりに木はブロックのアドレスに基づきます。VMはデータが格納できる利用可能な最も小さなブロックを探しますが、もし同じサイズのものがたくさんある場合は、ブロックのアドレスがより低いものを選びます。3つのブロックを必要とするデータがある場合、またエリア3となりますが、もし2つのブロックが必要な場合は、この戦略では\ref{fig:allocation-strategy-1}の図にある最初の\term{mbcs}のエリア1(エリア5ではなく)になります。これによりVMは多くの割り当てに対し、同じキャリアを優先する傾向を持つようになります。
%\emph{Address order first fit} (\term{aoff}) will favor the address order for its search, and as soon as a block fits, \term{aoff} uses it. Where \term{aobf} and bf would both have picked area 3 to allocate four blocks in Figure \ref{fig:allocation-strategy-1}, this one will get area 2 as a first priority given its address is lowest. In Figure \ref{fig:allocation-strategy-2}, if we were to allocate four blocks, we'd favor block 1 to block 3 because its address is lower, whereas \term{bf} would have picked either 3 or 4, and \term{aobf} would have picked 3.
\emph{Address order first fit}(\term{aoff})は検索時にアドレス順を優先し、ブロックに収まるならすぐに\term{aoff}がそれを使います。\ref{fig:allocation-strategy-1}の図で4つのブロックを割りあてるために\term{aobf}とbfの両方共がエリア3を選びますが、これはエリア2をアドレスが最も低いために最優先して取得します。\ref{fig:allocation-strategy-2}の図で、もし4つのブロックを割り当てるなら、この戦略ではアドレスが低いためにブロック3ではなくブロック1を選び、一方\term{bf}は3か4を選び、\term{aobf}は3を選ぶでしょう。
\begin{figure}
\includegraphics[max height=7cm]{allocation-strategy-2.pdf}%
\centering%
%\caption{Example memory allocated in a specific sub-allocator}%
\caption{特定のサブアロケータに割り当てられたメモリの例}%
\label{fig:allocation-strategy-2}
\end{figure}
\FloatBarrier
%\emph{Address order first fit carrier best fit} (\term{aoffcbf}) is a strategy that will first favor a carrier that can accommodate the size and then look for the best fit within that one. So if we were to allocate two blocks in Figure \ref{fig:allocation-strategy-2}, \term{bf} and \term{aobf} would both favor block 5, \term{aoff} would pick block 1. \term{aoffcbf} would pick area 2, because the first \term{mbcs} can accommodate it fine, and area 2 fits it better than area 1.
\emph{Address order first fit carrier best fit}(\term{aoffcbf})はサイズを格納できる最初のキャリアを選び、その中で最も適合するものを探すという戦略です。\ref{fig:allocation-strategy-2}の図で2つブロックを割り当てたい場合、\term{bf}と\term{aobf}は共にブロック5となるが、\term{aoff}はブロック1を選ぶでしょう。\term{aoffcbf}は最初の\term{mbcs}に格納できて、エリア2よりエリア1の方がより良く収まるのでエリア2を選びます。
%\emph{Address order first fit carrier address order best fit} (\term{aoffcaobf}) will be similar to \term{aoffcbf}, but if multiple areas within a carrier have the same size, it will favor the one with the smallest address between the two rather than leaving it unspecified.
\emph{Address order first fit carrier address order best fit}(\term{aoffcaobf})は\term{aoffcbf}と似ていますが、もしキャリアにある複数のエリアが同じサイズである場合に、それを不特定にしたままではなく、その2つの中でアドレスが最も小さいものを選びます。
%\emph{Good fit} (\term{gf}) is a different kind of allocator; it will try to work like best fit (\term{bf}), but will only search for a limited amount of time. If it doesn't find a perfect fit there and then, it will pick the best one encountered so far. The value is configurable through the \term{mbsd}\footnote{\href{http://www.erlang.org/doc/man/erts\_alloc.html\#M\_mbsd}{http://www.erlang.org/doc/man/erts\_alloc.html\#M\_mbsd}} VM argument.
\emph{Good fit}(\term{gf})は異なる種類のアロケータで、best fit(\term{bf})と同様の働きを試みますが、限られた時間だけ探します。もし完全に収まるところを探せなかったら、これまでに探せたものの中で最も良いものを選びます。この値(時間)はVMの\term{mbsd}\footnote{\href{http://www.erlang.org/doc/man/erts\_alloc.html\#M\_mbsd}{http://www.erlang.org/doc/man/erts\_alloc.html\#M\_mbsd}}引数を通して設定可能です。
%\emph{A fit} (\term{af}), finally, is an allocator behaviour for temporary data that looks for a single existing memory block, and if the data can fit, \term{af} uses it. If the data can't fit, \term{af} allocates a new one.
最後に\emph{A fit}(\term{af})は一時的なデータのためのアロケータ挙動で、一つの存在しているメモリブロックを探し、データが収まるなら\term{af}はそれを使います。もしデータが収まらなければ、\term{af}は新しいものを割り当てます。
%Each of these strategies can be applied individually to every kind of allocator, so that the heap allocator and the binary allocator do not necessarily share the same strategy.
これらの戦略はそれぞれ個別に全ての種類のアロケータに適用することができ、ヒープアロケータとバイナリアロケータで同じ戦略を共有する必要はありません。
%Finally, starting with Erlang version 17.0, each \term{alloc\_util} allocator on each scheduler has what is called a \emph{\term{mbcs} pool}. The \term{mbcs} pool is a feature used to fight against memory fragmentation on the VM. When an allocator gets to have one of its multiblock carriers become mostly empty,\footnote{The threshold is configurable through \href{http://www.erlang.org/doc/man/erts\_alloc.html\#M\_acul}{http://www.erlang.org/doc/man/erts\_alloc.html\#M\_acul}} the carrier becomes \emph{abandoned}.
Erlangのバージョン17.0からついにそれぞれのスケジューラの\term{alloc\_util}アロケータごとに\emph{\term{mbcs}プール}と呼ばれるものを持つようになりました。\term{mbcs}プールはVMのメモリフラグメンテーションと戦うために使われる機能です。アロケータの持つマルチブロックキャリアの1つがほとんど空になると\footnote{\href{http://www.erlang.org/doc/man/erts\_alloc.html\#M\_acul}{http://www.erlang.org/doc/man/erts\_alloc.html\#M\_acul}を通して閾値を設定できます}、そのキャリアは\emph{放棄された}状態になります。
%This abandoned carrier will stop being used for new allocations, until new multiblock carriers start being required. When this happens, the carrier will be fetched from the \term{mbcs} pool. This can be done across multiple \term{alloc\_util} allocators of the same type across schedulers. This allows the VM to cache mostly-empty carriers without forcing deallocation of their memory.\footnote{In cases this consumes too much memory, the feature can be disabled with the options \term{+MBacul 0}.} It also enables the migration of carriers across schedulers when they contain little data, according to their needs.
この放棄されたキャリアは新しいマルチブロックキャリアが必要とされるまで、新しい割り当てのために使われなくなります。もしそれが起こると(新しいマルチブロックキャリアが必要になる)、キャリアは\term{mbcs}プールから取得されます。これはスケジューラを超えて同じ種類の複数の\term{alloc\_util}アロケータに渡って行われます。これはVMにメモリの割り当て解除を強制することなしに、ほとんど空のキャリアをキャッシュすることを許可します\footnote{これはあまりにもたくさんのメモリを消費する場合がありますが、\term{+MBacul 0}オプションによってこの機能を無効にできます。}。それはまたキャリアが少ないデータを保持していて、それらが必要とされる場合に、スケジューラをまたがったキャリアのマイグレーションを有効にできます。
%\subsubsection{The Process Level}
\subsubsection{プロセスレベル}
\label{subsec:memory-process-level}
%On a smaller scale, for each Erlang process, the layout still is a bit different. It basically has this piece of memory that can be imagined as one box:
より小さなスケールでは、それぞれのErlangプロセスごとにレイアウトはまだ少し異なっています。それは基本的に1つの箱として想像できるメモリを持っています。
\begin{VerbatimText}
[ ]
\end{VerbatimText}
%On one end you have the heap, and on the other, you have the stack:
片側の端にはヒープを、その反対側にはスタックを持ちます。
\begin{VerbatimText}
[ヒープ | | スタック]
\end{VerbatimText}
%In practice there's more data (you have an old heap and a new heap, for generational GC, and also a virtual binary heap, to account for the space of reference-counted binaries on a specific sub-allocator not used by the process — \term{binary\_alloc} vs. \term{eheap\_alloc}):
実際にはそこにはもっとたくさんのデータがあります(世代別GCのための古いヒープ、新しいヒープに、プロセスから使われていない特定のサブアロケータにあるリファレンスカウントされたバイナリの領域を考慮した仮想のバイナリヒープも持ちます --- \term{binary\_alloc}対\term{eheap\_alloc})。
\begin{VerbatimText}
[ヒープ || スタック]
\end{VerbatimText}
%The space is allocated more and more up until either the stack or the heap can't fit in anymore. This triggers a minor GC. The minor GC moves the data that can be kept into the old heap. It then collects the rest, and may end up reallocating more space.
その領域はスタックかヒープが何も格納できなくなるまでもっと割り当てられます。これはマイナーGCを引き起こします。マイナーGCはオールド領域のヒープに保持できるデータを動かします。そして残りを回収し、さらなる領域を再割り当てするかもしれません。
%After a given number of minor GCs and/or reallocations, a full-sweep GC is performed, which inspects both the new and old heaps, frees up more space, and so on. When a process dies, both the stack and heap are taken out at once. reference-counted binaries are decreased, and if the counter is at 0, they vanish.
与えられた回数のマイナーGCや再割り当ての後で、新しいヒープと古いヒープを検査し、より多くの領域を解放するなどフルスイープGCが行われます。プロセスが死んだ時、スタックとヒープの両方は一度に解放されます。リファレンスカウントされたバイナリはカウントが減り、カウンターが0になるとそれらは消滅します。
%When that happens, over 80\% of the time, the only thing that happens is that the memory is marked as available in the sub-allocator and can be taken back by new processes or other ones that may need to be resized. Only after having this memory unused — and the multiblock carrier unused also — is it returned to \term{mseg\_alloc} or \term{sys\_alloc}, which may or may not keep it for a while longer.
それが起こると時間の80\%以上はメモリはサブアロケータで利用可能とマークされ、新しいプロセスまたはリサイズが必要な他のプロセスによって取り戻せるようになります。このメモリが使われていない --- マルチブロックキャリアもまた使われていない --- 状態になった後でのみ、それは\term{mseg\_alloc}または\term{sys\_alloc}へ返され、しばらくの間それが保持されたりされなかったりします。
%\subsection{Fixing Memory Fragmentation with a Different Allocation Strategy}
\subsection{異なるアロケーション戦略でメモリフラグメンテーションを修正する}
%Tweaking your VM's options for memory allocation may help.
メモリアロケーションに関するVMのオプションを調整することが助けになるかもしれません。
%You will likely need to have a good understanding of what your type of memory load and usage is, and be ready to do a lot of in-depth testing. The \module{recon\_alloc} module contains a few helper functions to provide guidance, and the module's documentation\footnote{\href{http://ferd.github.io/recon/recon\_alloc.html}{http://ferd.github.io/recon/recon\_alloc.html}} should be read at this point.
あなたはメモリ負荷とメモリ使用量の種類が何かについてよく理解し、大量の徹底的なテストを覚悟する必要があります。\module{recon\_alloc}モジュールはガイダンスを提供するためのいくつかの役立つ機能を含んでおり、この時点でモジュールのドキュメント\footnote{\href{http://ferd.github.io/recon/recon\_alloc.html}{http://ferd.github.io/recon/recon\_alloc.html}}を読むべきです。
%You will need to figure out what the average data size is, the frequency of allocation and deallocation, whether the data fits in \term{mbcs} or \term{sbcs}, and you will then need to try playing with a bunch of the options mentioned in \module{recon\_alloc}, try the different strategies, deploy them, and see if things improve or if they impact times negatively.
あなたは平均的なデータサイズがどれくらいかや、アロケーションとアロケーション解除の頻度、データが\term{mbcs}か\term{sbcs}に収まるかどうかについて把握する必要があります。その上で\module{recon\_alloc}のたくさんのオプションを試し、別の戦略を試し、それらをデプロイし、改善したかマイナスの影響があったかを見る必要があります。
%This is a very long process for which there is no shortcut, and if issues happen only every few months per node, you may be in for the long haul.
これは近道のないとても長いプロセスで、しかも問題はノードごとに数ヶ月ごとにしか起こりませんので、あなたは長期間それに携わることになるでしょう。
%\section{Exercises}
\section{演習}
%\subsection*{Review Questions}
\subsection*{復習問題}
\begin{enumerate}
%\item Name some of the common sources of leaks in Erlang programs.
\item Erlangのプログラムにおいてよくあるリークの原因をいくつか挙げてください。
%\item What are the two main types of binaries in Erlang?
\item Erlangでの主要な2種類のバイナリはなんでしょうか。
%\item What could be to blame if no specific data type seems to be the source of a leak?
\item どの特定のデータ型もリークの原因でないように思われる場合、何が原因になりうるでしょうか。
%\item If you find the node died with a process having a lot of memory, what could you do to find out which one it was?
\item 多くのメモリを保持したプロセスとともにノードが死んだとき、どのプロセスが死んだかをどうやって見つけられるでしょうか。
%\item How could code itself cause a leak?
\item コードはどうやってリークを起こせるでしょうか。
%\item How can you find out if garbage collections are taking too long to run?
\item ガベージコレクションの実行に時間がかかりすぎているかどうかをどうやって見つけられるでしょうか。
\end{enumerate}
%\subsection*{Open-ended Questions}
\subsection*{自由回答問題}
\begin{enumerate}
%\item How could you verify if a leak is caused by forgetting to kill processes, or by processes using too much memory on their own?
\item プロセスの殺し忘れ、あるいはプロセスのメモリの使いすぎによって起こされたリークはどうやって確認できるでしょうか。
%\item A process opens a 150MB log file in binary mode to go extract a piece of information from it, and then stores that information in an ETS table. After figuring out you have a binary memory leak, what should be done to minimize binary memory usage on the node?
\item あるプロセスが150MBのログファイルをバイナリモード開いてある情報を取り出し、その情報をETSテーブルに保存しようとしています。バイナリメモリリークがあるとわかった場合に、そのノード上でのバイナリメモリの使用量を最小にするには何をすべきでしょうか。
%\item What could you use to find out if ETS tables are growing too fast?
\item ETSテーブルのサイズの成長が速すぎるのを検出するためには何を使ったら良いでしょうか。
%\item What steps should you go through to find out that a node is likely suffering from fragmentation? How could you disprove the idea that is could be due to a NIF or driver leaking memory?
\item ノードがフラグメンテーションで苦労しそうかを検出するためにどのような手順を踏めばよいでしょうか。またそれがNIFやドライバーがメモリリークの原因であろうという意見にどう反論できるでしょうか。
%\item How could you find out if a process with a large mailbox (from reading \term{message\_queue\_len}) seems to be leaking data from there, or never handling new messages?
\item (\term{message\_queue\_len}の値を見ることで)メールボックスが大きくなっているプロセスがデータをリークさせている、あるいは新規メッセージをまったく処理できていなさそうかを、どうやって検出できるでしょうか。
%\item A process with a large memory footprint seems to be rarely running garbage collections. What could explain this?
\item 大きなメモリフットプリントのプロセスがガベージコレクションをほとんど実行していなさそうに見えます。これはどう説明できるでしょうか。
%\item When should you alter the allocation strategies on your nodes? Should you prefer to tweak this, or the way you wrote code?
\item ノードに対するアロケーション戦略はいつ変更すべきでしょうか。この設定は手で調整すべきでしょうか、あるいはコードで記述すべきでしょうか。
\end{enumerate}
%\subsection*{Hands-On}
\subsection*{ハンズオン}
\begin{enumerate}
%\item Using any system you know or have to maintain in Erlang (including toy systems), can you figure out if there are any binary memory leaks on there?
\item あなたが知っているあるいは運用しなければならないどんなErlangシステム(トイシステム含む)でもいいので、それを使ってそこにバイナリメモリリークがあるかどうかを判断してください。
\end{enumerate}