forked from heroku/erlang-in-anger
-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy path103-overload-ja.tex
440 lines (325 loc) · 74.1 KB
/
103-overload-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
%\chapter{Planning for Overload}
\chapter{過負荷のための計画をたてる}
\label{chap:overload}
%By far, the most common cause of failure I've encountered in real-world scenarios is due to the node running out of memory. Furthermore, it is usually related to message queues going out of bounds.\footnote{Figuring out that a message queue is the problem is explained in Chapter \ref{chap:crash-dumps}, specifically in Section \ref{sec:crash-full-mailboxes}} There are plenty of ways to deal with this, but knowing which one to use will require a decent understanding of the system you're working on.
私が実際に遭遇した最も一般的な障害原因は、圧倒的に稼働中ノードのOutOfMemoryです。さらにそれは通常、境界外に出るメッセージキューに関連します。 \footnote{メッセージキューが問題になる事例は \ref{chap:crash-dumps} 章、特に \ref{sec:crash-full-mailboxes} 節で説明されます。} これに対処する方法はたくさんありますが、自身が開発しているシステムを適切に理解することで、どう対処するかを決めることができます。
%To oversimplify things, most of the projects I end up working on can be visualized as a very large bathroom sink. User and data input are flowing from the faucet. The Erlang system itself is the sink and the pipes, and wherever the output goes (whether it's a database, an external API or service, and so on) is the sewer system.
事象をとても単純化するために、私が取り組むプロジェクトのほとんどは、大きな浴室のシンクとして視覚化することができます。ユーザーとデータ入力が蛇口から流れています。Erlangシステム自体はシンクとパイプであり、出力(データベース、外部API、外部サービスなど)は下水道です。
\begin{figure}[h!]
\includegraphics[max height=7cm]{sink.pdf}%
\centering%
% \caption{Your system is like a sink, and the true bottleneck, at the last drain, needs to be identified before you spend all your time and money making the sink and pipes bigger.}%
\label{fig:tracing-venn}
\end{figure}
%When an Erlang node dies because of a queue overflowing, figuring out who to blame is crucial. Did someone put too much water in the sink? Are the sewer systems backing up? Did you just design too small a pipe?
キューがオーバーフローしてErlangノードが死んでしまった場合、誰の責任か解明することが重要です。誰かがシンクに水を入れすぎましたか? 下水道が渋滞していますか? あまりにも小さなパイプを設計しましたか?
%Determining what queue blew up is not necessarily hard. This is information that can be found in a crash dump. Finding out why it blew up is trickier. Based on the role of the process or run-time inspection, it's possible to figure out whether causes include fast flooding, blocked processes that won't process messages fast enough, and so on.
どのキューが爆発したかを判断することは必ずしも困難ではありません。これはクラッシュダンプから見つけられる情報です。ただし爆破原因の解明は少し複雑です。プロセスやruntimeの調査に基づいて、高速にキューが溢れたのか、ブロックされたプロセスがメッセージを十分高速に処理できないか、などの原因を把握することができます。
%The most difficult part is to decide how to fix it. When the sink gets clogged up by too much waste, we will usually start by trying to make the bathroom sink itself larger (the part of our program that crashed, at the edge). Then we figure out the sink's drain is too small, and optimize that. Then we find out the pipes themselves are too narrow, and optimize that. The overload gets pushed further down the system, until the sewers can't take it anymore. At that point, we may try to add sinks or add bathrooms to help with the global input level.
最も難しい部分は、それをどのように修正するか決めることです。シンクがあまりにも詰まると、私たちは通常、浴室をもっと大きくすることから始めます(クラッシュしたプログラムの近辺から)。次に、シンクのドレインが小さすぎると分かり、それを最適化します。それから、パイプそのものが狭すぎることが分かり、それを最適化します。下水道がそれ以上受け取ることができなくなるまで過負荷はシステムのさらに下に押し込まれます。そのタイミングで、システム全体への入力処理を助けるために、シンクを追加したり、浴槽を追加したりすることもあります。
%Then there's a point where things can't be improved anymore at the bathroom's level. There are too many logs sent around, there's a bottleneck on databases that \emph{need} the consistency, or there's simply not enough knowledge or manpower in your organization to improve things there.
そうこうしたところで、もはや浴室の範囲内では事象を改善できないこともあります。あまりにも多くのログが送信されるため、一貫性を必要とするデータベースにボトルネックがあったり、または単に事象を解決するための組織内の知識や人材が不足していることもあります。
%By finding that point, we identified what the \emph{true bottleneck} of the system was, and all the prior optimization was nice (and likely expensive), but it was more or less in vain.
こういった点を見つけることで、システムの \emph{真のボトルネック} が何であるかを特定し、過去の全ての良いと思っていた(そして対応コストが高いかもしれない)最適化は、多かれ少なかれ無駄であることも特定できます。
%We need to be more clever, and so things are moved back up a level. We try to massage the information going in the system to make it either lighter (whether it is through compression, better algorithms and data representation, caching, and so on).
私たちはより賢くなる必要があります。そして、世もレベルアップしています。私たちは、システムに入る情報を(圧縮、より良いアルゴリズムとデータ表現、キャッシングなどを使って)軽量化しようとします。
%Even then, there are times where the overload will be too much, and we have to make the hard decisions between restricting the input to the system, discarding it, or accepting that the system will reduce its quality of service up to the point it will crash. These mechanisms fall into two broad strategies: back-pressure and load-shedding.
それでも過負荷が来ることがあります、そしてシステムへの入力を制限するか、入力を廃棄するか、システムがクラッシュするサービスレベルを受け入れるか、を決めなければなりません。これらのメカニズムは、2つの幅広いストラテジーに分類されます:バックプレッシャーと負荷分散。
%We'll explore them in this chapter, along with common events that end up causing Erlang systems to blow up.
この章では、Erlangシステムの爆発を引き起こす一般的なイベントとともに、それらを追求します。
%\section{Common Overload Sources}
\section{よくある過負荷の原因}
%There are a few common causes of queues blowing up and overload in Erlang systems that most people will encounter sooner or later, no matter how they approach their system. They're usually symptomatic of having your system grow up and require some help scaling up, or of an unexpected type of failure that ends up cascading much harder than it should have.
どんなにうまく取り組んでもたいていの人が遅かれ早かれ遭遇する、キューを爆発させErlangシステムを過負荷にさせるよくある原因がいくつかあります。
そうした原因は通常システムを大きくさせスケールアップさせる何かしらの助けが必要な兆候を表していたり、はたまた想定よりもずっと厳しく連鎖してしまう予期せぬ障害だったりします。
%\subsection{error\_logger Explodes}
\subsection{error\_loggerの爆発}
%Ironically, the process in charge of error logging is one of the most fragile ones. In a default Erlang install, the \module{error\_logger}\footnote{Defined at \href{http://www.erlang.org/doc/man/error\_logger.html}{http://www.erlang.org/doc/man/error\_logger.html}} process will take its sweet time to log things to disk or over the network, and will do so much more slowly than errors can be generated.
皮肉なことにエラーログの責任を担うプロセスがもっとも壊れやすい部分の一つです。
デフォルトのErlangのインストールでは、\module{error\_logger}\footnote{\href{http://www.erlang.org/doc/man/error\_logger.html}{http://www.erlang.org/doc/man/error\_logger.html}で定義されています。}プロセスは優雅にディスクやネットワーク越しにログを書き込み、それはエラーが生成されるよりもずっと遅い速度での書き込みなってしまいます。
%This is especially true of user-generated log messages (not for errors), and for crashes in large processes. For the former, this is because \module{error\_logger} doesn't really expect arbitrary levels of messages coming in continually. It's for exceptional cases only and doesn't expect lots of traffic. For the latter, it's because the entire state of processes (including their mailboxes) gets copied over to be logged. It only takes a few messages to cause memory to bubble up a lot, and if that's not enough to cause the node to run Out Of Memory (OOM), it may slow the logger enough that additional messages will.
特に(エラーではなく)ユーザーが生成したログメッセージや大きなプロセスがクラッシュしたときのログではこうしたことが起きます。
前者に関しては、\module{error\_logger}は任意のレベルのメッセージが継続的にやってくることを想定していないからです。
例外的な場合のみを想定していて、大量のトラフィックが来ることは想定していないのです。
後者に関しては、(プロセスのメールボックスも含めた)プロセス全体の状態がログされるためにコピーされるからです。
たった数行のメッセージでもメモリを大量に増やす原因となりますし、もしそれがノードをOut Of Memory (OOM)にさせるに至らなくても、追加のメッセージの書き込み速度を下げるには十分です。
%The best solution for this at the time of writing is to use \href{https://github.com/basho/lager}{\otpapp{lager}} as a substitute logging library.
これに対する現行執筆時点での最適解は\href{https://github.com/basho/lager}{\otpapp{lager}}を代替のログライブラリとして使うことです\footnote{訳注: 現在 lager はコミュニティーにより \href{https://github.com/erlang-lager/lager}{https://github.com/erlang-lager/lager} として開発、メンテナンスされています}。
%While \otpapp{lager} will not solve all your problems, it will truncate voluminous log messages, optionally drop OTP-generated error messages when they go over a certain threshold, and will automatically switch between asynchronous and synchronous modes for user-submitted messages in order to self-regulate.
\otpapp{lager}がすべての問題を解決するわけではない一方で、分量が多いログメッセージを切り詰めて、補足的にある閾値を超えたときにはOTPが生成したエラーメッセージを破棄して、ユーザーが送ったメッセージ用に自主規制のために自動的に非同期モードと同期モードを切り替えます。
%It won't be able to deal with very specific cases, such as when user-submitted messages are very large in volume and all coming from one-off processes. This is, however, a much rarer occurrence than everything else, and one where the programmer tends to have more control.
ユーザーが送ったメッセージのサイズが非常に大きくかつワンオフのプロセスからやってくる、というような非常に固有の状況には対応出来ません。
しかしながら、これは他の状況に比べるとずっと起こりにくいもので、プログラマーがずっと管理しやすい状況です。
%\subsection{Locks and Blocking Operations}
\subsection{ロックとブロック操作}
%Locking and blocking operations will often be problematic when they're taking unexpectedly long to execute in a process that's constantly receiving new tasks.
ロック操作とブロック操作は新しいタスクを継続的に受け取るプロセス内で予想外に実行が長くなってしまうときに、しばしば問題になります。
%One of the most common examples I've seen is a process blocking while accepting a connection or waiting for messages with TCP sockets. During blocking operations of this kind, messages are free to pile up in the message queue.
私が経験してきたもっともよくある例は、接続を受け入れる間、あるいはTCPソケットでメッセージを待つ間プロセスがブロックするというものです。
このようなブロック操作の間、メッセージは好きなだけメッセージキューに積み上がっていきます。
%One particularly bad example was in a pool manager for HTTP connections that I had written in a fork of the \href{https://github.com/ferd/lhttpc}{\module{lhttpc}} library. It worked fine in most test cases we had, and we even had a connection timeout set to 10 milliseconds to be sure it never took too long\footnote{10 milliseconds is very short, but was fine for collocated servers used for real-time bidding.}. After a few weeks of perfect uptime, the HTTP client pool caused an outage when one of the remote servers went down.
特に悪い状況の例が、\href{https://github.com/ferd/lhttpc}{\module{lhttpc}}ライブラリのフォークの中で私が書いたHTTP接続用のプールマネージャー内にありました。
私達のテストケースでは概ねうまく動いていて、さらに接続のタイムアウトも10ミリ秒に設定して、接続待ちが長くなりすぎないようにしていました。\footnote{10ミリ秒は非常に短いですが、リアルタイムビッディングに使われる共用サーバーでは問題ありません。}
数週間完璧に稼働したあとに、リモートサーバーの一つが落ちたときにHTTPクライアントプールが供給できない状態になりました。
%The reason behind this degradation was that when the remote server would go down, all of a sudden, all connecting operations would take at least 10 milliseconds, the time before which the connection attempt is given up on. With around 9,000 messages per second to the central process, each usually taking under 5 milliseconds, the impact became similar to roughly 18,000 messages a second and things got out of hand.
このデグレの背後にある原因は、リモートサーバーが落ちたときに、突然すべての接続操作が最低10ミリ秒かかるようになったことでした。この10ミリ秒は接続試行が断念されるまでの時間です。中央のプロセスに秒間9,000メッセージが届くようになったあたりでは、各接続試行は通常5ミリ秒以下で、この障害と同様の状況になったのは、およそ秒間18,000メッセージが届くようになったあたりで手に負えなくなりました。
%The solution we came up with was to leave the task of connecting to the caller process, and enforce the limits as if the manager had done it on its own. The blocking operations were now distributed to all users of the library, and even less work was required to be done by the manager, now free to accept more requests.
私達がいたった解決策は呼び出し元のプロセスに接続のタスクを譲って、プールマネージャー自体で制限したかのように制限を強制することにしました。
これでブロック操作はライブラリの全ユーザーに分散され、プールマネージャーによって行われるべき仕事はずっと少なくなり、より多くのリクエストを受け付けられるようになりました。
%When there is \emph{any} point of your program that ends up being a central hub for receiving messages, lengthy tasks should be moved out of there if possible. Handling predictable overload\footnote{Something you know for a fact gets overloaded in production} situations by adding more processes — which either handle the blocking operations or instead act as a buffer while the "main" process blocks — is often a good idea.
プログラム内でメッセージを受信するための中央的なハブになっている場所が\emph{一つでも}あれば、できれば時間がかかるタスクはそこから取り除くべきでしょう。
より多くのプロセスを追加することで予測可能な過負荷\footnote{本番環境で事実に基づいて過負荷になるもの}に対応する---ブロック操作に対処する、またはかわりに''main''プロセスがブロックする間のバッファとして機能する---のは良いアイデアです。
%There will be increased complexity in managing more processes for activities that aren't intrinsically concurrent, so make sure you need them before programming defensively.
本質的には並行ではない処理に対してより多くのプロセスを管理する複雑さが増すので、守りのプログラミングを始める前に確実にその実装が必要であることを確認しましょう。
%Another option is to transform the blocking task into an asynchronous one. If the type of work allows it, start the long-running job and keep a token that identifies it uniquely, along with the original requester you're doing work for. When the resource is available, have it send a message back to the server with the aforementioned token. The server will eventually get the message, match the token to the requester, and answer back, without being blocked by other requests in the mean time.\footnote{The \otpapp{redo} application is an example of a library doing this, in its \href{https://github.com/heroku/redo/blob/master/src/redo\_block.erl}{redo\_block} module. The [under-documented] module turns a pipelined connection into a blocking one, but does so while maintaining pipeline aspects to the caller — this allows the caller to know that only one call failed when a timeout occurs, not all of the in-transit ones, without having the server stop accepting requests.}
他の選択肢としては、ブロッキングタスクを非同期なものに変形させることです。もし処理がそうした変更を受け入れられるものであれば、長時間実行されるジョブを起動して、そのジョブの一意な識別子としてのトークンともともとのリクエスト元を保持します。
リソースが使えるようになったら、リソースからサーバーに対して先述のトークンと一緒にメッセージを送り返させます。
結果サーバーはメッセージを受け取り、トークンとリクエスト元を対応させ、その間他のリクエストにブロックされることなく結果を返します。\footnote{\otpapp{redo}アプリケーションはこうした処理を行うライブラリの例です。redoの\href{https://github.com/heroku/redo/blob/master/src/redo\_block.erl}{redo\_block}モジュールにその処理があります。このあまりドキュメント化されていないモジュールは、パイプライン接続をブロッキング接続にしますが、呼び出し元に対してパイプラインの面を維持している間だけそうします。---これによって呼び出し元はタイムアウトが発生したときに、サーバーがリクエストの受け入れを止めることなく、すべての未達の呼び出しが失敗したわけでなく1つの呼び出しだけが失敗したとわかります。}
%This option tends to be more obscure than using many processes and can quickly devolve into callback hell, but may use fewer resources.
この選択肢は多くのプロセスを使う方法に比べてあいまいなものになりがちで、すぐにコールバック地獄になりえますが、使うリソースは少なくなるでしょう。
%\subsection{Unexpected Messages}
\subsection{予期せぬメッセージ}
%Messages you didn't know about tend to be rather rare when using OTP applications. Because OTP behaviours pretty much expect you to handle anything with some clause in \function{handle\_info/2}, unexpected messages will not accumulate much.
OTPアプリケーションを使っている場合は知らないメッセージを受け取ることはまれです。
なぜならOTPビヘイビアはすべてを\function{handle\_info/2}にある節で処理することを期待しているので、予期せぬメッセージがたまることはあまりないでしょう。
%However, all kinds of OTP-compliant systems end up having processes that may not implement a behaviour, or processes that go in a non-behaviour stretch where it overtakes message handling. If you're lucky enough, monitoring tools\footnote{See Section \ref{sec:global-view}} will show a constant memory increase, and inspecting for large queue sizes\footnote{See Subsection \ref{subsec:digging-procs}} will let you find which process is at fault. You can then fix the problem by handling the messages as required.
しかしながら、あらゆるOTP互換システムはビヘイビアを実装していないプロセスやメッセージハンドリングを頑張るビヘイビアではない方向で実装してしまったプロセスを持つことになります。あなたが十分に幸運であれば、監視ツール\footnote{\ref{sec:global-view}の節を見ましょう}が定常的なメモリ増加を示していて、大きなキューサイズを点検することで\footnote{\ref{subsec:digging-procs}の節を見ましょう}どのプロセスに問題があるかわかるでしょう。そのあと、メッセージを必要なように処理することで問題を修正できます。
%\section{Restricting Input}
\section{入力を制限する}
%Restricting input is the simplest way to manage message queue growth in Erlang systems. It's the simplest approach because it basically means you're slowing the user down (applying \emph{back-pressure}), which instantly fixes the problem without any further optimization required. On the other hand, it can lead to a really crappy experience for the user.
Erlangシステム内のメッセージキューが大きくなるのを管理する最も単純な方法は入力を制限することです。
基本的にユーザーとのやり取りを遅くさせ(\emph{バックプレッシャー}をかけています)ていることを意味しているため、追加の最適化の必要もなくすぐに問題を修正するという理由から最も単純な手法なのです。
一方で、ユーザーにとっては本当にひどい体験となります。
%The most common way to restrict data input is to make calls to a process whose queue would grow in uncontrollable ways synchronously. By requiring a response before moving on to the next request, you will generally ensure that the direct source of the problem will be slowed down.
データの入力を制限する最もよく知られた方法は、無制限に成長する可能性があるキューを持ったプロセスを同期的に呼び出すことです。
次のリクエストに移る前にレスポンスを求めるようにすれば、一般的に問題の直接の原因は確実に軽減されるでしょう。
%The difficult part for this approach is that the bottleneck causing the queue to grow is usually not at the edge of the system, but deep inside it, which you find after optimizing nearly everything that came before. Such bottlenecks will often be database operations, disk operations, or some service over the network.
この手法の難しい部分はキューの成長の原因となるボトルネックは通常システムの周辺部ではなく、システムの深層部にあって、この問題が顕在化する前に行うほぼすべての最適化のあとに見つけることになります。
このようなボトルネックはだいたいがデータベースの操作、ディスクの操作、あるいはネットワーク越しのサービスでしょう。
%This means that when you introduce synchronous behaviour deep in the system, you'll possibly need to handle back-pressure, level by level, until you end up at the system's edges and can tell the user, "please slow down."
これはつまり同期的な動作をシステムの深層部に導入すると、おそらく各階層ごとにバックプレッシャーに対応し、システムの周辺部までたどり着いてユーザーに「もう少しゆっくりしてください」と言えるようになるまで対応する必要が出て来るということです。
%Developers that see this pattern will often try to put API limits per user\footnote{There's a tradeoff between slowing down all requests equally or using rate-limiting, both of which are valid. Rate-limiting per user would mean you'd still need to increase capacity or lower the limits of all users when more new users hammer your system, whereas a synchronous system that indiscriminately blocks should adapt to any load with more ease, but possibly unfairly.} on the system entry points. This is a valid approach, especially since it can guarantee a basic quality of service (QoS) to the system and allows one to allocate resources as fairly (or unfairly) as desired.
このパターンを理解した開発者はしばしばユーザーごとにシステムのエントリーポイントにAPI制限を設けようとします。\footnote{すべてのリクエストを等しく遅くするか、あるいは速度制限を設けるかはトレードオフがあって、ともに有効です。より多くの新規ユーザーがシステムにアクセスしてきたときに、ユーザーごとに速度制限をするということは依然としてキャパシティを大きくする必要がある、あるいはすべてのユーザーの限度を下げる必要があります。一方で、無差別にブロックする同期的なシステムはもっと簡単にあらゆる負荷に適用できますが、おそらく不公平でしょう。}
特に基本的なシステムに対するサービスのクオリティ(QoS)を保証でき、リソースを公平に(あるいは不公平に)望んだとおりにリソースを割り当てられるので、これは正当なやり方です。
%\subsection{How Long Should a Time Out Be}
\subsection{タイムアウトはどれくらいの長さであるべきか}
%What's particularly tricky about applying back-pressure to handle overload via synchronous calls is having to determine what the typical operation should be taking in terms of time, or rather, at what point the system should time out.
過負荷に対処するために同期呼び出しによるバックプレッシャーを適用することにおいて特に扱いづらいのは、処理は通常どれくらいの時間がかかるのかを決定しなければならないこと、より正確に言えばシステムがどのタイミングでタイムアウトするべきかを決める必要があることです。
%The best way to express the problem is that the first timer to be started will be at the edge of the system, but the critical operations will be happening deep within it. This means that the timer at the edge of the system will need to have a longer wait time that those within, unless you plan on having operations reported as timing out at the edge even though they succeeded internally.
この問題を最もうまく表現しようと思うと、最初のタイマーがシステムの周辺部で開始されるけれども、致命的な処理はシステムの深層部で起きているというような具合です。
つまり、システムの周辺部にあるタイマーはシステムの深層部にあるタイマーよりも長い時間待つ必要があるでしょう。ただし、深層部で処理が成功していたとしても周辺部では操作はタイムアウトしたということにしたいのであれば別です。
%An easy way out of this is to go for infinite timeouts. Pat Helland\footnote{\href{http://queue.acm.org/detail.cfm?id=2187821}{Idempotence is Not a Medical Condition}, April 14, 2012} has an interesting answer to this:
この状況を脱する簡単な方法はタイムアウトを無期限にすることです。Pat Helland\footnote{\href{http://queue.acm.org/detail.cfm?id=2187821}{Idempotence is Not a Medical Condition} 2012年4月14日発行}がこれに対して興味深い回答をしています。
\begin{quote}
%Some application developers may push for no timeout and argue it is OK to wait indefinitely. I typically propose they set the timeout to 30 years. That, in turn, generates a response that I need to be reasonable and not silly. \emph{Why is 30 years silly but infinity is reasonable?} I have yet to see a messaging application that really wants to wait for an unbounded period of time…
アプリケーション開発者によってはタイムアウトを設定せず、無制限に待機しても良いと主張するかもしれません。
私がよく、彼らはタイムアウトを30年に設定した、ということにしています。そして次に私は愚かではなく合理的である必要があるんだという返事が来ます。
\emph{なぜ30年は愚かで、無制限は合理的なんでしょう?}私は無制限に返事を待つようなメッセージアプリを見たことがありません...
\end{quote}
%This is, ultimately, a case-by-case issue. In many cases, it may be more practical to use a different mechanism for that flow control.\footnote{In Erlang, using the value \term{infinity} will avoid creating a timer, avoiding some resources. If you do use this, remember to at least have a well-defined timeout somewhere in the sequence of calls.}
つまり、究極的にはケースバイケースな問題です。多くの場合、フロー制御には異なる機構を使うほうがより実用的でしょう。\footnote{Erlangでは、\term{infinity}という値を使うことでタイマーを作ることを回避でき、それによってリソースを多少節約出来ます。これを使う場合は一連の呼び出しの中のどこかで最低一つはきちんとタイムアウトを設定することを肝に命じておきましょう。}
%\subsection{Asking For Permission}
\subsection{許可を求める}
%A somewhat simpler approach to back-pressure is to identify the resources we want to block on, those that cannot be made faster and are critical to your business and users. Lock these resources behind a module or procedure where a caller must ask for the right to make a request and use them. There's plenty of variables that can be used: memory, CPU, overall load, a bounded number of calls, concurrency, response times, a combination of them, and so on.
バックプレッシャーのもう少し単純な例はブロックしたいリソース、それも速く出来ずビジネスやユーザーにとって致命的なリソースの確認です。
呼び出し元がリソース要求の権利を求めたりそのリソースを使うようなモジュールやプロシージャの裏で、これらのリソースのロックしましょう。
使われる変数は様々あって、メモリ、CPU、全体の負荷、呼び出し回数の上限、並行処理、応答時間、これらの組み合わせ、などです。
%The \emph{SafetyValve}\footnote{\href{https://github.com/jlouis/safetyvalve}{https://github.com/jlouis/safetyvalve}} application is a system-wide framework that can be used when you know back-pressure is what you'll need.
\emph{SafetyValve}\footnote{\href{https://github.com/jlouis/safetyvalve}{https://github.com/jlouis/safetyvalve}}アプリケーションはシステム全体に及ぶフレームワークで、バックプレッシャーが必要だとわかっているときに使えます。
%For more specific use cases having to do with service or system failures, there are plenty of circuit breaker applications available. Examples include \otpapp{breaky}\footnote{\href{https://github.com/mmzeeman/breaky}{https://github.com/mmzeeman/breaky}}, \otpapp{fuse}\footnote{\href{https://github.com/jlouis/fuse}{https://github.com/jlouis/fuse}}, or Klarna's \otpapp{circuit\_breaker}\footnote{\href{https://github.com/klarna/circuit\_breaker}{https://github.com/klarna/circuit\_breaker}}.
サービスやシステムの障害により関係しそうなユースーケースには、多くのサーキットブレーカーアプリケーションがあります。
たとえば\otpapp{breaky}\footnote{\href{https://github.com/mmzeeman/breaky}{https://github.com/mmzeeman/breaky}}、\otpapp{fuse}\footnote{\href{https://github.com/jlouis/fuse}{https://github.com/jlouis/fuse}}、Klarnaの \otpapp{circuit\_breaker}\footnote{\href{https://github.com/klarna/circuit\_breaker}{https://github.com/klarna/circuit\_breaker}}といった具合です。
%Otherwise, ad-hoc solutions can be written using processes, ETS, or any other tool available. The important part is that the edge of the system (or subsystem) may block and ask for the right to process data, but the critical bottleneck in code is the one to determine whether that right can be granted or not.
または、プロセス、ETS、あるいはその他のツールなどを使ってアドホックな解決策を作ることも出来ます。
重要なのは、システムの周辺部(あるいはサブシステム)がデータをブロックして、データを処理する権利を求めるけれども、コード内の致命的なボトルネックは権利が許可されるかどうかを決めるものです。
%The advantage of proceeding that way is that you may just avoid all the tricky stuff about timers and making every single layer of abstraction synchronous. You'll instead put guards at the bottleneck and at a given edge or control point, and everything in between can be expressed in the most readable way possible.
このように進める利点は、タイマーや抽象化のあらゆる層を同期的にするというような厄介なものをすべて避けられることです。
かわりに、ボトルネックや周辺部の特定の場所あるいは制御点に見張りを置いて、その間にあるものはすべて最も読みやすい形で表現できます。
%\subsection{What Users See}
\subsection{ユーザーが見るもの}
%The tricky part about back-pressure is reporting it. When back-pressure is done implicitly through synchronous calls, the only way to know it is at work due to overload is that the system becomes slower and less usable. Sadly, this is also going to be a potential symptom of bad hardware, bad network, unrelated overload, and possibly a slow client.
バックプレッシャーで厄介なのはそれを報告することです。バックプレッシャーが暗黙的に同期呼び出し経由で行われたとき、それが過負荷によって起きていると知る唯一の方法はシステムが遅くなってあまり使えなくなる場合だけです。
悲しいことに、これはハードウェアやネットワークの不調や、関係ない過負荷、そして遅いクライアントに起こりうる兆候でもあります。
%Trying to figure out that a system is applying back-pressure by measuring its responsiveness is equivalent to trying to diagnose which illness someone has by observing that person has a fever. It tells you something is wrong, but not what.
システムの応答性によってシステムがバックプレッシャーを適用しているかを知ろうとすることは、その人に熱があるという診察結果から病気を診断しようとするようなものです。
%Asking for permission, as a mechanism, will generally allow you to define your interface in such a way that you can explicitly report what is going on: the system as a whole is overloaded, or you're hitting a limit into the rate at which you can perform an operation and adjust accordingly.
機構として、許可を求めることは、一般的に何が起きているかを明示的に報告できるようなインターフェースを定義できるようにしています。
たとえばシステム全体が過負荷になっていたり、あるいは処理を実行したり適宜調整できる限界の速度になったりした場合の報告などです。
%There is a choice to be made when designing the system. Are your users going to have per-account limits, or are the limits going to be global to the system?
システムを設計するときには選択をしなければなりません。
ユーザーにアカウントごとの制限を設けるか、あるいはシステム全体で一つの制限を持つかです。
%System-global or node-global limits are usually easy to implement, but will have the downside that they may be unfair. A user doing 90\% of all your requests may end up making the platform unusable for the vast majority of the other users.
システム全体あるいはノード全体での制限というのは通常実装が簡単ですが、不公平に成りうるという欠点があります。
あるユーザーがリクエストの99\%を行っていると、そのせいで他の大多数のユーザーがプラットフォームを使えない状態になってしまうでしょう。
%Per-account limits, on the other hand, tend to be very fair, and allow fancy schemes such as having premium users who can go above the usual limits. This is extremely nice, but has the downside that the more users use your system, the higher the effective global system limit tends to move. Starting with 100 users that can do 100 requests a minute gives you a global 10000 requests per minute. Add 20 new users with that same rate allowed, and suddenly you may crash a lot more often.
一方で、アカウントごとの制限は非常に公平で、通常の制限よりもゆるい制限になるプレミアムユーザーを設定するというような気の利いたこともできるようになります。
これは極めて素晴らしいことですが、欠点もあって、システムを使うユーザーの数が増えるほど、快適に動作するためのシステム全体の制限は引き上げられます。
1分間に100リクエストを投げられるユーザー100人がいると全体では1分間に10000リクエストとなります。
同じ制限速度で20人新規ユーザーを追加すると、とたんにクラッシュが大量に発生するようになります。
%The safe margin of error you established when designing the system slowly erodes as more people use it. It's important to consider the tradeoffs your business can tolerate from that point of view, because users will tend not to appreciate seeing their allowed usage go down all the time, possibly even more so than seeing the system go down entirely from time to time.
システムを設計したときに作ったエラーに対するセーフマージンはユーザーの数が増えるにつれ徐々に失われていきます。
その観点からビジネスが耐えられるかのトレードオフを考慮することが重要です。なぜなら、ユーザーはシステム全体がときどき落ちてしまうことよりもずっと、自分に割り当てられた使用量がいつも使えないことのほうが嫌だと思うからです。
%\section{Discarding Data}
\section{データの破棄}
%When nothing can slow down outside of your Erlang system and things can't be scaled up, you must either drop data or crash (which drops data that was in flight, for most cases, but with more violence).
Erlangシステム以外から流入速度を落とせず、かつスケールアップもできない場合、データをドロップさせるか、クラッシュ(結局それでも大抵の場合は、処理中のデータをより乱暴にドロップ)させる必要があります。
%It's a sad reality that nobody really wants to deal with. Programmers, software engineers, and computer scientists are trained to purge the useless data, and keep everything that's useful. Success comes through optimization, not giving up.
これは全ての人が目を背けたくなる悲しい現実です。プログラマーや、ソフトウェアエンジニア、そしてコンピュータサイエンティストは、不要なデータは消去し、有用なデータはすべて保持するよう学んできています。地道な最適化をすることにより、Erlangシステムは入力データを問題なく処理できる状態になります。
%However, there's a point that can be reached where the data that comes in does so at a rate faster than it goes out, even if the Erlang system on its own is able to do everything fast enough. In some cases, It's the component \emph{after} it that blocks.
しかし、Erlangシステム自身が全てのデータを十分な速度で処理できたとしても、Erlangシステムの \emph{後ろ} に控えるコンポーネントがブロックし、出力よりもデータ入力の方が速くなることもあります。
%If you don't have the option of limiting how much data you receive, you then have to drop messages to avoid crashing.
受信するデータ量を制限する選択肢がない場合は、クラッシュを避けるためにメッセージをドロップする必要があります。
%\subsection{Random Drop}
\subsection{ランダムドロップ}
%Randomly dropping messages is the easiest way to do such a thing, and might also be the most robust implementation, due to its simplicity.
ランダムにメッセージをドロップすることは、これらの対処を行う最も簡単な方法であり、シンプルがゆえに最も堅牢な実装でもあります。
%The trick is to define some threshold value between 0.0 and 1.0 and to fetch a random number in that range:
その中身は、0.0から1.0の間のある閾値を定義し、その範囲の乱数を取り出すという仕組みになっています。
\includecode[erlang]{drop.erl}
%If you aim to keep 95\% of the messages you send, the authorization could be written by a call to \expression{case drop:random(0.95) of true -> send(); false -> drop() end}, or a shorter \expression{drop:random(0.95) andalso send()} if you don't need to do anything specific when dropping a message.
送信するメッセージの95%を保持することを目指す場合、 \expression{case drop:random(0.95) of true -> send(); false -> drop() end} の呼び出しによって承認できます。メッセージをドロップする際に特別な処理を行う必要がない場合、より短く \expression{drop:random(0.95) andalso send()} で書けます。\footnote{訳注: random moduleはOTP18で代替のrand moduleが追加されOTP19以降では非推奨になっているため、OTP19以降はrandを使った方が良いでしょう。}
%The \function{maybe\_seed()} function will check that a valid seed is present in the process dictionary and use it rather than a crappy one, but only if it has not been defined before, in order to avoid calling \function{now()} (a monotonic function that requires a global lock) too often.
\function{maybe\_seed()}関数は、\function{now()} (グローバルロックを要求する単調関数) を高頻度で呼び出すことを避けるため、過去に呼び出されていない場合、有効なシードがプロセスディクショナリに存在していることをチェックし、それを有用なものとして使用します。
%There is one `gotcha' to this method, though: the random drop must ideally be done at the producer level rather than at the queue (the receiver) level. The best way to avoid overloading a queue is to not send data its way in the first place. Because there are no bounded mailboxes in Erlang, dropping in the receiving process only guarantees that this process will be spinning wildly, trying to get rid of messages, and fighting the schedulers to do actual work.
しかし、この方法には1つの「落とし穴」があります。ランダムドロップは、キュー(レシーバ)側ではなくプロデューサ側で行うのが理想的なのです。キューの過負荷を避ける最善の方法は、そもそもデータを送信しないことです。Erlangには制限付きのメールボックスがないため、受信プロセスのメッセージドロップだけが唯一、プロセスの高速動作、メッセージ除去への試み、処理を行うためにスケジューラに追いつくこと、などを保証します。
%On the other hand, dropping at the producer level is guaranteed to distribute the work equally across all processes.
その一方で、プロデューサ側でのメッセージドロップは、すべてのプロセス間で均等に分散されることを保証します。
%This can give place to interesting optimizations where the working process or a given monitor process\footnote{Any process tasked with checking the load of specific processes using heuristics such as \expression{process\_info(Pid, message\_queue\_len)} could be a monitor} uses values in an ETS table or \function{application:set\_env/3} to dynamically increase and decrease the threshold to be used with the random number. This allows control over how many messages are dropped based on overload, and the configuration data can be fetched by any process rather efficiently by using \function{application:get\_env/2}.
このメッセージドロップの方法で、処理するプロセスまたは特定のモニタプロセス\footnote{\expression{process\_info(Pid, message\_queue\_len)}のような、ヒューリスティックスを使用して特定のプロセスの負荷をチェックするプロセスは、どんなものでもモニタにすることができます。}が、ETSテーブルまたは \function{application:set\_env/3} の値を使用して、乱数とともに使用されるしきい値を動的に増減させる、という興味深い最適化をすることができます。これにより、負荷に基づいてドロップされるメッセージの数を制御したり、\function{application:get\_env/2}を使用すれば、どんなプロセスでも効率的に構成データを取得できます。
%Similar techniques could also be used to implement different drop ratios for different message priorities, rather than trying to sort it all out at the consumer level.
同様の技術を使用すれば、処理する側ですべてを選別するのではなく、メッセージに別々の優先順位をつけて異なるドロップ比率を実装することもできます。
%\subsection{Queue Buffers}
\subsection{キューバッファ}
%Queue buffers are a good alternative when you want more control over the messages you get rid of than with random drops, particularly when you expect overload to be coming in bursts rather than a constant stream in need of thinning.
キューバッファはメッセージを捨てる時に、ランダムではなく、より制御された方法で行いたい時の代替手段となります。ある程度の量を捨てる必要がある(量の変動が少ない)ストリームではなく、負荷がバーストすることが予想されているときには、特にそうです。
%Even though the regular mailbox for a process has the form of a queue, you'll generally want to pull \emph{all} the messages out of it as soon as possible. A queue buffer will need two processes to be safe:
プロセスの通常のメールボックスには一定量のキューがありますが、一般的には出来るだけ早く \emph{全ての}メッセージを取り出す必要があるでしょう。安全のためには、キューバッファには2種類のプロセスが必要です。
\begin{itemize*}
%\item The regular process you'd work with (likely a \module{gen\_server});
\item 通常のプロセス(例えば \module{gen\_server})
%\item A new process that will do nothing but buffer the messages. Messages from the outside should go to this process.
\item メッセージをバッファする以外は何もしない新しいプロセス。外部からのメッセージはこのプロセスに送ります。
\end{itemize*}
%To make things work, the buffer process only has to remove all the messages it can from its mail box and put them in a queue data structure\footnote{The \module{queue} module in Erlang provides a purely functional queue data structure that can work fine for such a buffer.} it manages on its own. Whenever the server is ready to do more work, it can ask the buffer process to send it a given number of messages that it can work on. The buffer process picks them from its queue, forwards them to the server, and goes back to accumulating data.
バッファするプロセスが、自身のメールボックスから取り出せる全てのメッセージを取り除き、自身で管理できるキュー用のデータ構造\footnote{Erlangの\module{queue}モジュールは、このような用途のバッファに適した、完全に関数型のキューデータ構造です}の中に入れることで、これを実現できます。サーバがさらに処理を行うことができるときには、バッファプロセスに対していつでも、処理可能な量だけのメッセージを送るよう要求することができます。バッファプロセスはキューからメッセージを取り出してサーバに送り、またメッセージを集める処理に戻ります。
%Whenever the queue grows beyond a certain size\footnote{To calculate the length of a queue, it is preferable to use a counter that gets incremented and decremented on each message sent or received, rather than iterating over the queue every time. It takes slightly more memory, but will tend to distribute the load of counting more evenly, helping predictability and avoiding more sudden build-ups in the buffer's mailbox} and you receive a new message, you can then pop the oldest one and push the new one in there, dropping the oldest elements as you go.\footnote{You can alternatively make a queue that pops the newest message and queues up the oldest ones if you feel previous data is more important to keep.}
キューが一定のサイズ\footnote{キューのサイズを計算する場合、キューを毎回イテレートするよりも、メッセージの送受信の度に加算・減算されるカウンターを使うことが望ましいです。多少のメモリが必要になりますが、カウントの負荷を分散させられますし、バッファのメールボックスが突然あふれるのを防いだりあるいは予測するのに役立ちます}よりも大きくなり新しいメッセージを受け取ったときにはいつでも、最も古いメッセージを取り出し、新しいメッセージをキューに入れ、古いメッセージを消していくことができます\footnote{この他にも、古いメッセージが重要だと思えるときには、最も新しいメッセージを取り出して、最も古いメッセージをキューに残すようなキューを作成することができます}。
%This should keep the entire number of messages received to a rather stable size and provide a good amount of resistance to overload, somewhat similar to the functional version of a ring buffer.
これにより、受信したメッセージの総数を適切なサイズに保ち、ある程度高負荷にも耐えるリングバッファに似たような機能を提供できます。
%The \emph{PO Box}\footnote{Available at: \href{https://github.com/ferd/pobox}{https://github.com/ferd/pobox}, the library has been used in production for a long time in large scale products at Heroku and is considered mature} library implements such a queue buffer.
\emph{PO Box}\footnote{\href{https://github.com/ferd/pobox}{https://github.com/ferd/pobox} にあります。このライブラリはHerokuの大規模な本番環境で長い間使われてきており、成熟していると考えられています}ライブラリはこのようなキューバッファを提供しています。
%\subsection{Stack Buffers}
\subsection{スタックバッファ}
%Stack buffers are ideal when you want the amount of control offered by queue buffers, but you have an important requirement for low latency.
キューバッファのような制御も欲しいが、低レイテンシのための重要な要件があるときには、スタックバッファが理想的です。
%To use a stack as a buffer, you'll need two processes, just like you would with queue buffers, but a list\footnote{Erlang lists \emph{are} stacks, for all we care. They provide push and pop operations that take O(1) complexity and are very fast} will be used instead of a queue data structure.
スタックをバッファとして利用するには、キューバッファの時のように二つのプロセスが必要となりますが、キュー構造の代わりにリスト\footnote{どうでも良いことですが、Erlangではリスト\emph{が}スタックです。ErlangリストはO(1)の計算量でpushとpop操作を提供していて、非常に速いです。}が使われます。
%The reason the stack buffer is particularly good for low latency is related to issues similar to bufferbloat\footnote{\href{http://queue.acm.org/detail.cfm?id=2071893}{http://queue.acm.org/detail.cfm?id=2071893 }}. If you get behind on a few messages being buffered in a queue, all the messages in the queue get to be slowed down and acquire milliseconds of wait time. Eventually, they all get to be too old and the entire buffer needs to be discarded.
低レイテンシのためにスタックバッファが特に優れている理由は、バッファブロート\footnote{\href{http://queue.acm.org/detail.cfm?id=2071893}{http://queue.acm.org/detail.cfm?id=2071893 }}に似た問題と関係しています。キューバッファにあるメッセージの処理が遅れると、それがほんの少量のメッセージだったとしても、キューの中にあるすべてのメッセージにミリ秒単位の待ち時間が発生します。しだいにメッセージは古くなりすぎて、すべてのバッファを削除する必要が出てくるでしょう。
%% Image of queue taking long from RTB article. Maybe side image or side explanation? %% ここは最初からコメントアウトされています
%%On the other hand, a stack will make it so only a restricted number of elements are kept waiting while the newer ones keep making it to the server to be processed in a timely manner.
その一方で、スタックはある一定数の要素だけが待たされて、新しい方からタイムリーにサーバで処理されます。% TODO: 要はLIFOってことを言いたいだけだと思うんですが、stackでもqueueでも一定数の制限を設けることはできるよなぁ…と思い、前半の文意を少し捉えかねています
%% image of queue taking a short time from RTB article. %%ここも最初からコメントアウトされています
%Whenever you see the stack grow beyond a certain size or notice that an element in it is too old for your QoS requirements you can just drop the rest of the stack and keep going from there. \emph{PO Box} also offers such a buffer implementation.
スタックが一定サイズを超えて成長した場合や、その中の要素がQoS要求を満たすには遅すぎると気づいた時は、ただスタックの残りを破棄して、続きを始めれば良いです。
\emph{PO Box}もこのようなバッファ実装を行っていますね。
%A major downside of stack buffers is that messages are not necessarily going to be processed in the order they were submitted — they're nicer for independent tasks, but will ruin your day if you expect a sequence of events to be respected.
スタックバッファの主な欠点はメッセージが必ずしも投稿された順番どおりに行われないことです --- 独立したタスクには向きますが、イベントが順番通りだと期待するなら、一日を無駄にすることでしょう。
%\subsection{Time-Sensitive Buffers}
\subsection{時間に敏感なバッファ}
%If you need to react to old events \emph{before} they are too old, then things become more complex, as you can't know about it without looking deep in the stack each time, and dropping from the bottom of the stack in a constant manner gets to be inefficient. An interesting approach could be done with buckets, where multiple stacks are used, with each of them containing a given time slice. When requests get too old for the QoS constraints, drop an entire bucket, but not the entire buffer.
もし、古いイベントに対して、古くなりすぎる\emph{前に}反応する必要があるのなら、物事はより複雑になります。
イベントが古いかどうかは、スタックの奥深くまで毎回確認しなければわかりませんし、スタックを底からものをイベントを取り出すのは普通、非効率です。
この場合、バケツ --- それぞれが担当時間帯を持っている、複数のスタック --- を使った面白いアプローチが使えます。
リクエストがQoS制約として古くなりすぎたら、その時間帯のバケツ一つをまるごと削除します。バッファすべては削除しません。
%It may sound counter-intuitive to make some requests a lot worse to benefit the majority — you'll have great medians but poor 99 percentiles — but this happens in a state where you would drop messages anyway, and is preferable in cases where you do need low latency.
多数の利益のために一部リクエストを非常に悪くする--- 中央値は良いが、下位1\%がひどいことになっている --- というのは直感に反するかもしれませんが、
これはとにかくメッセージを捨てたいという状態で起こるもので、低レイテンシがどうしても求められる場合には、望ましい方法です。
%\subsection{Dealing With Constant Overload}
\subsection{定量負荷の取り扱い}
%Being under constant overload may require a new solution. Whereas both queues and buffers will be great for cases where overload happens from time to time (even if it's a rather prolonged period of time), they both work more reliably when you expect the input rate to eventually drop, letting you catch up.
定量負荷に対しては、新しい方法が必要でしょう。
キューもバッファも、時々(たとえそれが、かなりの長い時間続くものだったとしても)発生する負荷に対しては良い方法ですが、
どちらも、入力速度が徐々に落ち着き処理が追いつくことが期待できる場合に信頼できる方法です。
%You'll mostly get problems when trying to send so many messages they can't make it all to one process without overloading it. Two approaches are generally good for this case:
一つのプロセスに大量のメッセージを送ろうとすると、過負荷になってしまうことがよく問題になります。
一般的に、この場合には二つのアプローチがあります。
\begin{itemize*}
%\item Have many processes that act as buffers and load-balance through them (scale horizontally)
\item バッファとして機能するプロセスをたくさん持ち、それらの間で負荷分散を行う(スケールアウトさせる方法)
%\item use ETS tables as locks and counters (reduce the input)
\item ロックとカウンタの仕組みとして、ETSテーブルを使う(入力を減らす方法)
\end{itemize*}
%ETS tables are generally able to handle a ton more requests per second than a process, but the operations they support are a lot more basic. A single read, or adding or removing from a counter atomically is as fancy as you should expect things to get for the general case.
一般的にETSテーブルは、1秒間にプロセスよりかなりの多くのリクエストを処理できますが、サポートする操作はより基本的なものだけです。
あなたが期待できる操作はたいてい、単純な読み込み、カウンタへのアトミックな追加と削除くらいでしょう。
%ETS tables will be required for both approaches.
どちらのアプローチを取るにしても、ETSテーブルは必要です。
%Generally speaking, the first approach could work well with the regular process registry: you take \var{N} processes to divide up the load, give them all a known name, and pick one of them to send the message to. Given you're pretty much going to assume you'll be overloaded, randomly picking a process with an even distribution tends to be reliable: no state communication is required, work will be shared in a roughly equal manner, and it's rather insensitive to failure.
ひとつめのアプローチは、一般論で言えば、プロセスレジストリを使うだけでも動作します。
負荷を分散するのに\var{N}個のプロセスを用意し、プロセスそれぞれにknown nameをつけ、メッセージを送る際はその中の一つを選択すれば良いのです。
高負荷がかかることを十分に想定するなら、一様ランダムにプロセスを選択するようにすれば頼もしいですね。
プロセス同士で状態を教え合う必要はありませんし、負荷はおおよそ等しく分配され、失敗に対しても鈍感です。
%In practice, though, we want to avoid atoms generated dynamically, so I tend to prefer to register workers in an ETS table with \expression{read\_concurrency} set to \expression{true}. It's a bit more work, but it gives more flexibility when it comes to updating the number of workers later on.
実際にはしかし、プロセス名のためにatomを動的に生成するのは避けたいので、\expression{read\_concurrency}を\expression{true}に設定したETSテーブルにワーカーを保存することをおすすめします。
ひと手間かかりますが、後々ワーカー数の更新が柔軟に行えるでしょう。
%An approach similar to this one is used in the \module{lhttpc}\footnote{The \href{https://github.com/ferd/lhttpc/blob/master/src/lhttpc\_lb.erl}{lhttpc\_lb} module in this library implements it.} library mentioned earlier, to split load balancers on a per-domain basis.
これに似たアプローチは、本書で以前に出てきた \module{lhttpc}\footnote{このライブラリの中で実装されている\href{https://github.com/ferd/lhttpc/blob/master/src/lhttpc\_lb.erl}{lhttpc\_lb}モジュール} ライブラリでも、ドメインごとの負荷を分けるために使われています。
%For the second approach, using counters and locks, the same basic structure still remains (pick one of many options, send it a message), but before actually sending a message, you must atomically update an ETS counter\footnote{By using \function{ets:update\_counter/3}.}. There is a known limit shared across all clients (either through their supervisor, or any other config or ETS value) and each request that can be made to a process needs to clear this limit first.
ふたつめのアプローチ、つまりはカウンタとロックを使う方法でも、たくさんの選択肢から一つを選んでメッセージを送るという基本構造は変わりません。
しかし、実際にメッセージを送る前に、ETSカウンタをアトミックに更新\footnote{\function{ets:update\_counter/3}を使いましょう}しなければなりません。
全クライアントで共有できる量は限界があり(スーパバイザを介しても、configやETSの値を使うにしても、です)、プロセスに対するリクエストはまずはじめにクリアする必要があります。
%This approach has been used in \module{dispcount}\footnote{\href{https://github.com/ferd/dispcount}{https://github.com/ferd/dispcount}} to avoid message queues, and to guarantee low-latency responses to any message that won't be handled so that you do not need to wait to know your request was denied. It is then up to the user of the library whether to give up as soon as possible, or to keep retrying with different workers.
このアプローチは\module{dispcount}\footnote{\href{https://github.com/ferd/dispcount}{https://github.com/ferd/dispcount}}モジュールで使われていて、
メッセージキューを避け、任意のメッセージに対して(処理されないかもしれませんが)低レイテンシを保証することで、リクエストが拒否されたことを知るまでに待たずに済むようになっています。
他のワーカをつかって処理を続けるか、可能な限りすぐに諦めるかは、利用する側にまかせています。
%\subsection{How Do You Drop}
\subsection{どのようにドロップするか}
%Most of the solutions outlined here work based on message quantity, but it's also possible to try and do it based on message size, or expected complexity, if you can predict it. When using a queue or stack buffer, instead of counting entries, all you may need to do is count their size or assign them a given load as a limit.
ここで提示された解決策のほとんどはメッセージの数によって動作していましたが、予測できるのであればメッセージのサイズや複雑さに応じた解決策も出来ます。
エントリーを数えるかわりにキューやスタックバッファを使う場合は、そのサイズを測るか、最初から制約として決められた容量を与えれば良いでしょう。
%I've found that in practice, dropping without regard to the specifics of the message works rather well, but each application has its share of unique compromises that can be acceptable or not\footnote{Old papers such as \href{http://research.microsoft.com/en-us/um/people/blampson/33-hints/webpage.html}{Hints for Computer System Designs} by Butler W. Lampson recommend dropping messages: "Shed load to control demand, rather than allowing the system to become overloaded." The paper also mentions that "A system cannot be expected to function well if the demand for any resource exceeds two-thirds of the capacity, unless the load can be characterized extremely well." adding that "The only systems in which cleverness has worked are those with very well-known loads."}.
私が実務経験の中で発見したことは、どのような種類のメッセージであろうともドロップはかなりうまくいきますが、アプリケーションごとに受け入れられるドロップするメッセージの量の妥協点は異なります。\footnote{Butler W. Lampsonによる\href{http://research.microsoft.com/en-us/um/people/blampson/33-hints/webpage.html}{Hints for Computer System Designs}のような古い論文ではメッセージのドロップを推奨しています。たとえば「システムが過負荷になってしまう状況を許可するのではなく、管理できる程度に負荷を落としましょう。」というコメントがあります。それ以外にも「システムはあらゆるリソースへの要求がキャパシティの三分の二を超えると、負荷を極めてうまく対応しないと、期待した機能を発揮できないでしょう。」というコメントもあります。それに付け加えて「うまく対応できるようなシステムは、よく知られた負荷がかかっているシステムだけです。」というコメントもあります。}
%There are also cases where the data is sent to you in a "fire and forget" manner — the entire system is part of an asynchronous pipeline — and it proves difficult to provide feedback to the end-user about why some requests were dropped or are missing. If you can reserve a special type of message that accumulates dropped responses and tells the user "\var{N} messages were dropped for reason \var{X}", that can, on its own, make the compromise far more acceptable to the user. This is the choice that was made with Heroku's \href{https://devcenter.heroku.com/articles/logplex}{logplex} log routing system, which can spit out \href{https://devcenter.heroku.com/articles/error-codes\#l10-drain-buffer-overflow}{L10 errors}, alerting the user that a part of the system can't deal with all the volume right now.
また、データが---システム全体が非同期パイプラインの一部になっていて---「ファイア・アンド・フォーゲット」の形で送信される場合もあります。そして、この場合、エンドユーザーに対してあるリクエストがドロップされたり取り逃がされている理由をフィードバックを送ることが難しくなります。
ドロップしたレスポンスを集めてユーザーに「\var{N}個のメッセージが\var{X}という理由でドロップされました」というような特別なメッセージを送れるようにしておくと、それだけで、ユーザーがずっと納得しやすい妥協点を提供できるでしょう。
これはHerokuの\href{https://devcenter.heroku.com/articles/logplex}{logplex}というログルーティングシステムで取った選択で、このシステムでは、\href{https://devcenter.heroku.com/articles/error-codes\#l10-drain-buffer-overflow}{L10エラー}を出して、ユーザーにシステムの一部が対応出来てない旨を警告します。
%In the end, what is acceptable or not to deal with overload tends to depend on the humans that use the system. It is often easier to bend the requirements a bit than develop new technology, but sometimes it is just not avoidable.
結局、負荷に対応するために何が受け入れられるかは、そのシステムを使う人間に依存しがちです。
しばしば新しい技術を開発するよりも要求を曲げるほうが簡単ですが、新しい技術を開発することが避けられないこともあります。
%\section{Exercises}
\section{演習}
%\subsection*{Review Questions}
\subsection*{復習問題}
\begin{enumerate}
%\item Name the common sources of overload in Erlang systems
\item Erlangシステム内での過負荷のよくある原因を挙げてください
%\item What are the two main classes of strategies to handle overload?
\item 過負荷の対応策の主な戦略を2つ答えてください
%\item How can long-running operations be made safer?
\item 長時間稼働する処理はどのようにして安全にできますか
%\item When going synchronous, how should timeouts be chosen?
\item 同期処理を行うとき、タイムアウトはどのように設定しますか
%\item What is an alternative to having timeouts?
\item タイムアウト以外の代替案はなんでしょうか
%\item When would you pick a queue buffer before a stack buffer?
\item どんなときにスタックバッファの前にキューバッファを選びますか
\end{enumerate}
%\subsection*{Open-ended Questions}
\subsection*{自由回答問題}
\begin{enumerate}
%\item What is a \emph{true bottleneck}? How can you find it?
\item \emph{真のボトルネック}とはなんでしょうか。それはどうやったら見つけられるでしょうか。
%\item In an application that calls a third party API, response times vary by a lot depending on how healthy the other servers are. How could one design the system to prevent occasionally slow requests from blocking other concurrent calls to the same service?
\item サードパーティのAPIを呼び出すアプリケーションでは、応答時間は他のサーバーの正常性によって大きく変化します。同じサービスへの他の並行呼び出しをブロックすることにより時折発生する遅いリクエストを予防するようにするにはシステムをどう設計すればよいでしょうか。
%\item What's likely to happen to new requests to an overloaded latency-sensitive service where data has backed up in a stack buffer? What about old requests?
\item データがスタックバッファ内にバックアップされた過負荷のレイテンシに敏感なサービスに対して新しいリクエストを送ったら、そのリクエストには何が起きるでしょうか。
%\item Explain how you could turn a load-shedding overload mechanism into one that can also provide back-pressure.
\item 部分的に送信停止する過負荷機構をバックプレッシャーも提供できるような機構に変えるにはどうしたらいいでしょうか
%\item Explain how you could turn a back-pressure mechanism into a load-shedding mechanism.
\item バックプレッシャー機構を部分的に送信停止する機構に変えるにはどうしたらいいでしょうか。
%\item What are the risks, for a user, when dropping or blocking a request? How can we prevent duplicate messages or missed ones?
\item ユーザーにとって、リクエストをドロップしたりブロックするときにどういうリスクがあるでしょうか。メッセージの重複やメッセージの喪失をどうやって防げばよいでしょうか。
%\item What can you expect to happen to your API design if you forget to deal with overload, and suddenly need to add back-pressure or load-shedding to it?
\item API設計をする際に過負荷対策を忘れてしまっていたあとに、急にバックプレッシャーや部分的な送信停止を追加する必要が出てきたら、設計にどういう影響があるでしょうか。
\end{enumerate}