<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Please Sleep</title>
    <link>https://please-sleep.cou929.nu/</link>
    <description>From notes on my laptop</description>
    <managingEditor>cou929@gmail.com (Kosei Moriyama)</managingEditor>
    <pubDate>Thu, 24 Apr 2025 02:23:10 +0000</pubDate>
    <item>
      <title>読書メモ: Prompt Engineering for LLMs</title>
      <link>https://please-sleep.cou929.nu/prompt-engineering-for-llms-book.html</link>
      <description>&lt;div class=&#34;amazlet-box&#34; style=&#34;margin-bottom:0px;&#34;&gt;&lt;div class=&#34;amazlet-image&#34; style=&#34;float:left;margin:0px 12px 1px 0px;&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/B0DM3VLNSK/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;&lt;img src=&#34;https://m.media-amazon.com/images/I/8182Q91T3nL._SY385_.jpg&#34; alt=&#34;Prompt Engineering for LLMs: The Art and Science of Building Large Language Model–Based Applications (English Edition)&#34; style=&#34;border: none; width: 113px;&#34; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-info&#34; style=&#34;line-height:120%; margin-bottom: 10px&#34;&gt;&lt;div class=&#34;amazlet-name&#34; style=&#34;margin-bottom:10px;line-height:120%&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/B0DM3VLNSK/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Prompt Engineering for LLMs: The Art and Science of Building Large Language Model–Based Applications (English Edition)&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-detail&#34;&gt;英語版  John Berryman (著), Albert Ziegler (著)  形式: Kindle版&lt;br/&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-sub-info&#34; style=&#34;float: left;&#34;&gt;&lt;div class=&#34;amazlet-link&#34; style=&#34;margin-top: 5px&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/B0DM3VLNSK/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Amazon.co.jpで詳細を見る&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-footer&#34; style=&#34;clear: left&#34;&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;GitHub Copilot 開発者らによる、プロンプトエンジニアリングの本。プロンプトエンジニアリングと言うと、ChatGPT に投げる文言を工夫するテクニックのようなイメージだったが、そうではなく LLM をベースとしたアプリケーション開発全般を指している。LLM がどのような仕組みで動作しているのかをアプリケーション開発者の視点から概説したあとは、アプリケーションの実装方法について背景となる理論を踏まえて解説してくれる。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Cline や Cursor を始めコーディングエージェントが台頭し、実際の普段の業務で利用されるようになってきた。ただその内部構造がブラックボックスで、課題感を感じていた。LLM アプリケーションに対するメンタルモデルが出来上がっていないので、Cline のソースコードや各 LLM プラットフォームの API ドキュメントを見ても、いまいち掴みどころを持てていいなかった。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;新しいツールや概念がどんどん出てくる中で、それらの情報に踊らされる、ただ表面を追うだけで時間を浪費させられてしまうことに危機感を持った。特に AI 関連は第一印象でどうしても「驚いて」しまうので、今までの新技術より「踊らされる」リスクが高い。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;こうした背景の上で、いわゆる機械学習等のバックグラウンドがないソフトウェアエンジニアの立場から、こうした LLM アプリケーションの内部のメンタルモデルを構築するのに、この本はうってつけだった。現状ある製品をあくまでツールとして、あくまでブラックボックスとして使うだけなら不要だが、内部構造をある程度理解したい場合は非常に良い本だと思う。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;なおもうすぐ和訳も出るようなので、そちらを待っても良いかもしれない。&lt;/p&gt;&#xA;&#xA;&lt;div class=&#34;amazlet-box&#34; style=&#34;margin-bottom:0px;&#34;&gt;&lt;div class=&#34;amazlet-image&#34; style=&#34;float:left;margin:0px 12px 1px 0px;&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4814401132/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;&lt;img src=&#34;https://m.media-amazon.com/images/I/81vJdX2fweL._SY385_.jpg&#34; alt=&#34;LLMのプロンプトエンジニアリング —GitHub Copilotを生んだ開発者が教える生成AIアプリケーション開発&#34; style=&#34;border: none; width: 113px;&#34; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-info&#34; style=&#34;line-height:120%; margin-bottom: 10px&#34;&gt;&lt;div class=&#34;amazlet-name&#34; style=&#34;margin-bottom:10px;line-height:120%&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4814401132/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;LLMのプロンプトエンジニアリング —GitHub Copilotを生んだ開発者が教える生成AIアプリケーション開発&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-detail&#34;&gt;John Berryman (著), Albert Ziegler (著)&lt;br/&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-sub-info&#34; style=&#34;float: left;&#34;&gt;&lt;div class=&#34;amazlet-link&#34; style=&#34;margin-top: 5px&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4814401132/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Amazon.co.jpで詳細を見る&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-footer&#34; style=&#34;clear: left&#34;&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;以下読書メモ:&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;I. Foundations&lt;/h2&gt;&#xA;&#xA;&lt;h3&gt;1. Introduction to Prompt Engineering&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;プロンプトエンジニアリングというと、チャット型の LLM に投げる文言を工夫するテクニックという先入観があった。一方でこの本では、LLM をベースにしたアプリケーション全般を対象にしている。アプリケーションはプロンプトの構築や解釈をプログラマティックに行い、今まででは難しかった高度なタスクを遂行できるようになる。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&amp;gt; at the core, LLMs are simply models that predict the next word in a block of  text—that’s it and nothing more!&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;本質的には、LLM は単にテキスト内の次の単語を予測するモデルで、それ以上でも以下でもない&lt;/li&gt;&#xA;&lt;li&gt;抽象化すればスマホ IME の予測変換と変わらない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;LLM に至る道のり&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;自然言語処理のマルコフモデル (1984) まで遡ることができる&lt;/li&gt;&#xA;&lt;li&gt;2014 年: Google より &lt;a href=&#34;https://arxiv.org/abs/1409.3215&#34;&gt;seq2seq アーキテクチャ&lt;/a&gt; の言語モデル&lt;/li&gt;&#xA;&lt;li&gt;2015 年: &lt;a href=&#34;https://arxiv.org/abs/1409.0473&#34;&gt;Soft search&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;2017 年: &lt;a href=&#34;https://arxiv.org/abs/1706.03762&#34;&gt;Transformer アーキテクチャ&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;2018 年: &lt;a href=&#34;https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf&#34;&gt;GPT&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;2019 年: GPT-2&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;GPT と比較し、&lt;a href=&#34;https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf&#34;&gt;トレーニングセットのサイズを桁違いに増やすだけで性能が大幅に上昇&lt;/a&gt;。特定のタスク専用に fine-tuning したものよりも、トレーニングセットを大きくした汎用モデルのほうが性能で勝った&lt;/li&gt;&#xA;&lt;li&gt;この時点で既に悪意のある利用が懸念されているのも興味深い。&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&amp;gt; Due to our concerns about malicious applications of  the technology, we are not releasing the trained model.&lt;/li&gt;&#xA;&lt;li&gt;ref. &lt;a href=&#34;https://openai.com/index/better-language-models/&#34;&gt;https://openai.com/index/better-language-models/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;2020 年: &lt;a href=&#34;https://arxiv.org/abs/2005.14165&#34;&gt;GPT-3&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;プロンプトによって出力を調整できることが発見され、prompt engineering という概念が生まれた&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Prompt Engineering&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;前述のように LLM はあるテキストから残りを補完するモデルである。達成したい課題に対処するために入力テキストを工夫するのが、最もシンプルな形態のプロンプトエンジニアリング&lt;/li&gt;&#xA;&lt;li&gt;一方で単一のプロンプトにとどまらず、プロンプトの構築と解釈をプログラムから行う、LLM ベースのアプリケーションの構築もプロンプトエンジニアリングの範疇&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;最もシンプルなものは、入力に微細な変更を加えただけでほぼそのままモデルに渡すもの。一般的な Chat UI は ChatML のような形式への変換は行うがそれは薄いもので、Copilot の簡単な補完もほぼそのまま入力を受け渡している&lt;/li&gt;&#xA;&lt;li&gt;次のレベルでは、ユーザーの入力を変換・補強したうえでモデルに渡すもの。例えば Copilot で関連するコードを添えて LLM に渡し、補完の精度を上げるようなもの&lt;/li&gt;&#xA;&lt;li&gt;更に進むと、モデルとのやりとりがステートフルに、つまりこれまでのやりとりを保持しながら行うものになる。モデルが処理できるコンテキストにはサイズの限界があるので、コンテキストの管理が必要になる&lt;/li&gt;&#xA;&lt;li&gt;最終的には LLM アプリケーションがエージェントとして、外界とやりとりしながらタスクを実行するものを見ていく&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;2. Understanding LLMs&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;LLM の基本的な仕組み。LLM は学習セットにあるテキストを模倣する。一度に一つのトークンを処理している。以前のトークンを編集するような事はできない。&#xA;LLM の仕組みを概観し、LLM の利用時にどのような特性があるかを知る。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;LLM とは何か。上段から見ていく&lt;/li&gt;&#xA;&lt;li&gt;最もハイレベルでは、LLM はテキストをプロンプトとして受け取り、出力として入力を確率的に補完しテキストを完成させる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;img src=&#34;images/prompt-engineering-fig-2-1.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/li&gt;&#xA;&lt;li&gt;事前に大量のドキュメント (換言するとただのテキスト) で学習している。本、フォーラムの書き込み、論文、ソースコード、etc。そこから模倣した、新しいテキストを返すことができる&lt;/li&gt;&#xA;&lt;li&gt;トレーニングセットにあるテキストから確率的に補完をしているだけなので、事実ではない補完 (ハルシネーション) が起こる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;LLM はテキストをトークンに分割して扱う&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;トークンは 3, 4 文字程度のチャンク&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例: &lt;a href=&#34;https://platform.openai.com/tokenizer&#34;&gt;Tokenizer - OpenAI API&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;人間は単語単位で認識するが、LLM はトークン単位。この間にはいくつか違いがある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Tokenizer は決定的&lt;/li&gt;&#xA;&lt;li&gt;トークンより細かい単位での操作が難しい。例えば &lt;code&gt;one example&lt;/code&gt; を &lt;code&gt;eno elpmaxe&lt;/code&gt; に変換してといってもうまくできない事が多い。あるいは &lt;code&gt;Sw から始まるヨーロッパの国は?&lt;/code&gt; と聞いても、&lt;code&gt;Sweden&lt;/code&gt; のトークンが &lt;code&gt;Sw&lt;/code&gt; で切られていなければうまく答えられない。トークンを最小単位とした操作なら可能&lt;/li&gt;&#xA;&lt;li&gt;文字の表記を認識できない。例えばあるテキストを全て大文字にするタスクに失敗する。LLM にとって小文字のトークンと大文字のトークンは大きな違いで、人間のように &lt;code&gt;a&lt;/code&gt; は &lt;code&gt;A&lt;/code&gt; の別形式であると認識しているわけではない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;トークン数はモデルの利用にかかる金額や、扱えるトークン数上限などに関わるため、プロンプトエンジニアリングとしてはよく意識する必要がある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;多くのモデルは英語に最適化されているので、英語のほうがトークン長が効率的。反対にランダムな文字列などは効率が悪い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;LLM は複数のトークンから、次の 1 トークンを補完している&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;最も可能性が高い 1 トークンを補完し、それを入力として次のトークンを補完する (自己回帰)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;img src=&#34;images/prompt-engineering-fig-2-9.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;一度補完したトークンに遡って訂正したりはできない。一定の補完をしたあとに間違いに気づいた場合、それをリカバーするのはアプリケーション側の役割になる&lt;/li&gt;&#xA;&lt;li&gt;補完は複数の候補から確からしいものを選択している&lt;/li&gt;&#xA;&lt;li&gt;多くのモデルは logprobs と temperature というパラメータを持っている。logprobs は次のトークンの確率分布を返す。temperature は確率分布の平坦さを調整するパラメータで、0 は最も確からしいトークンを選択する。1 はトレーニングセットの確率分布と同じ選択をする。1 以上の場合、よりランダムな選択になる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;正確さを重視する場合は小さく、出力のバリエーションを増やしたい場合は大きくするなど、それぞれのユースケースがある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;LLM はトランスフォーマーアーキテクチャをベースにしている&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;複数の &lt;code&gt;小さな脳&lt;/code&gt; が集まって全体を構成している&lt;/li&gt;&#xA;&lt;li&gt;各 &lt;code&gt;小さな脳&lt;/code&gt; はそれぞれ、テキストの中のひとつのトークンを処理する。またテキスト中の自分の位置も知っている。それぞれの &lt;code&gt;小さな脳&lt;/code&gt; はレイヤーと呼ばれる複数のステップで計算をしている&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;小さな脳&lt;/code&gt; は左 (ここまでのテキスト) の情報から予測し、それ (中間状態) を右 (これからのテキスト) に伝える&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;計算は常に左から右へ行われており、これ以外の経路はない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;そのため、プロンプトエンジニアリングの観点では、プロンプトに記載する情報の順序が非常に重要になる。&lt;/li&gt;&#xA;&lt;li&gt;&lt;img src=&#34;images/prompt-engineering-fig-2-14.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;3. Moving to Chat&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;ここまでは最もベースとなる、テキストの補完を行う LLM を見てきたが、ここではそれをチャット形式で利用する方法を見ていく。RLHF という方式でベースのモデルに fine tuning していき、ChatGPT などで見慣れたチャットができる能力を施していく。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;&lt;a href=&#34;http://arxiv.org/pdf/2203.02155&#34;&gt;RLHF&lt;/a&gt; (Reinforcement Learning from Human Feedback, 人間のフィードバックによる強化学習)&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ベースモデル (テキスト補完だけしかできない) に RLHF を適用し、チャット形式でのやりとりを可能にする&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;あわせて、&lt;a href=&#34;https://arxiv.org/abs/2203.02155&#34;&gt;役に立ち・正直で・無害 (Helpful, Honest, Harmless - HHH)&lt;/a&gt; という 3 つの基準を満たすようにする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;順序&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;まずはベースモデルに fine-tuning を行い、SFT (supervised Fine-Tuning) model と呼ばれる中間モデルを作成する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ユーザーとアシスタント間のチャット形式の (HHH な) やりとりをトレーニングセットとして与える&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;GPT-3 の場合 13,000 件のやり取りを与えた&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;SFT を高い temperature で呼び出し、生成された複数パターンのテキストを人間が評価しランク付けする&lt;/li&gt;&#xA;&lt;li&gt;SFT の生成とその評価結果を評価関数として、強化学習を行う&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;報酬モデルを作成しそれで強化学習を繰り返すことで、人手で評価したものではカバーしきれない量の学習を行う&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Instruction model&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;RLHF が施されたこの時点のモデルは instruction modelと呼ばれる。ベースモデルが入力からの補完だけを行うのに対して、人間とアシスタントとのやりとりを学習した instruction modelは質問に対して回答を行うことができるようになった&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば &lt;code&gt;What is a good indoor activity for a family of four?&lt;/code&gt; というプロンプトに対して、ベースモデルは続きの文章を生成する。instruction modelは質問とみなして回答を生成する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ここで、ユーザーがこのプロンプトに対して補完を求めているのか、解答を求めているのか確実に判断できないという問題が残る&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;ChatML&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ここでは OpenAI が開発したフォーマットを扱う。後述例のように簡単なタグで明示できるようになっている&lt;/li&gt;&#xA;&lt;li&gt;トランスクリプトとしてプロンプトを定義できるようになる。またシステムプロンプトでロールを定義できる&lt;/li&gt;&#xA;&lt;li&gt;ChatML フォーマットを RLHF fine tuningしたものが chart model となる&lt;/li&gt;&#xA;&lt;li&gt;実際にプロンプトエンジニアが直接 ChatML を記述することはない。例えば OpenAI API では、ロールでシステムやユーザーを明示して呼び出す。裏側で内部的に ChatML が利用されている&#xA;&lt;code&gt;&#xA;&amp;lt;|im_start|&amp;gt;system  You are a sarcastic software assistant. You provide humorous answers to  software questions. You use lots of emojis.&amp;lt;|im_end|&amp;gt;&#xA;&amp;lt;|im_start|&amp;gt;user  I was told that my computer would show me a funny joke if I typed :(){ :|:&amp;amp;};:  in the terminal. Why is everything so slow now?&amp;lt;|im_end|&amp;gt;&#xA;&amp;lt;|im_start|&amp;gt;assistant  I personally find the joke amusing. I tell you what, restart your computer  and then come back in 20 minutes and ask me about fork bombs. |im_end|&amp;gt;&#xA;&amp;lt;|im_start|&amp;gt;user  Oh man.&amp;lt;|im_end|&amp;gt;&#xA;&amp;lt;|im_start|&amp;gt;assistant  Jokes on you, eh?  &amp;lt;|im_end|&amp;gt;&#xA;&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Chat model であることで、ベースの補完モデルに劣後する部分もある&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;HHH なレスポンスにチューニングされているので、その分解答の質が下がる可能性がある&lt;/li&gt;&#xA;&lt;li&gt;丁寧なレスポンスのため前置きなどが入り、解答そのものを取り出す手間が増える&lt;/li&gt;&#xA;&lt;li&gt;オリジナルのトレーニングセットには、丁寧なものであれ無礼なものであれ、あらゆる文章が含まれている。HHH に従うようチューニングされたモデルでは、後者が出づらくなる&lt;/li&gt;&#xA;&lt;li&gt;ChatML であれ、あくまでも (このフォーマットに fine tuning された) モデルが、テキストを補完しているだけだ、という視点は改めて忘れないようにすると良い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;4. Designing LLM Applications&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;LLM アプリケーションの全体像。LLM アプリケーションはユーザーのタスクを変換し、モデルからの解答を変換して返すループを繰り返す。特に前者には情報の収集や優先度付けも必要になる。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;LLM Application はユーザーの問題領域と LLM のドメインとの変換レイヤー&lt;/li&gt;&#xA;&lt;li&gt;ループ構造&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;img src=&#34;images/prompt-engineering-fig-4-1.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/li&gt;&#xA;&lt;li&gt;アプリケーションはユーザーの課題を変換し LLM に入力する。LLM からの出力を変換しユーザーに返す。このフローを問題解決まで繰り返す&lt;/li&gt;&#xA;&lt;li&gt;ループはまずユーザーの問題から始まり、次にアプリケーションはそれをモデルのドメインに変換する。このときアプリケーションは次の要件を満たすプロンプトを作成する必要がある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;トレーニングセットのコンテンツによく似ている (赤ずきんの原理)&lt;/li&gt;&#xA;&lt;li&gt;問題解決に必要なすべての情報が含まれている (情報の収集、選別)&lt;/li&gt;&#xA;&lt;li&gt;問題を解決する補完をするようモデルを導く&lt;/li&gt;&#xA;&lt;li&gt;補完が自然なところで終了する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;次は作成したプロンプトを LLM に渡し結果を受け取る。ここではモデルの選択 (品質、コスト、レイテンシ) や fine tuningの有無が検討事項になる&lt;/li&gt;&#xA;&lt;li&gt;最後に受け取った結果を変換しユーザーに返す&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ユーザーの課題をモデルのドメインに変換するステップ feed forward pass を詳しく見る&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;img src=&#34;images/prompt-engineering-fig-4-2.jpeg&#34; alt=&#34;&#34; /&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;必要な情報 (コンテキスト) を収集する&lt;/li&gt;&#xA;&lt;li&gt;収集したコンテキストをスニペットに分割する&lt;/li&gt;&#xA;&lt;li&gt;スニペットにスコアを付け、選別する&lt;/li&gt;&#xA;&lt;li&gt;最終的なプロンプトを成形する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;次のような難しさ、複雑さが発生する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;状態管理 (コンテキスト管理) 。”記憶” を引き継ぐ&lt;/li&gt;&#xA;&lt;li&gt;外部の情報の取得&lt;/li&gt;&#xA;&lt;li&gt;深い推論をさせる (chain of thoughtsを促すなど)&lt;/li&gt;&#xA;&lt;li&gt;ツールの利用を通した外界とのインタラクション&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;性能の評価&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;オフライン評価&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;事前に性能を評価する。アプリケーションによっては簡単ではない。例えば Copilot のようなコード補完エージェントの場合は、生成されたコードをテストして評価できる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;オンライン評価&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ユーザーからの評価 (自動収集されるテレメトリも含む)&lt;/li&gt;&#xA;&lt;li&gt;ChatGPT のサムズアップ、ダウンボタンがわかりやすい例。Copilot の場合候補が選択されたかどうかなど、より暗黙的な指標の方が良い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;II. Core Techniques&lt;/h2&gt;&#xA;&#xA;&lt;h3&gt;5. Prompt Content&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;Feed forward pass の第一段階である、プロンプトへの情報追加について。プロンプトのボイラープレートのような静的なコンテンツと、外部情報を取得するような動的なコンテンツに大別できる。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;静的コンテンツ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;プロンプトの定型テンプレートに、モデルが従うべきルールなどを記載する。プロンプトの明確化のためのもの&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば &lt;a href=&#34;https://arstechnica.com/information-technology/2023/02/ai-powered-bing-chat-spills-its-secrets-via-prompt-injection-attack/&#34;&gt;Bing chat はこのようなものを使っていると解析された&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;指示のコツは、否定形ではなく肯定形で、理由を付与する、絶対的な言い方を避ける&lt;/li&gt;&#xA;&lt;li&gt;ただモデルが常にこれに従ってくれるとは限らない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://arxiv.org/abs/2005.14165&#34;&gt;Few-shot prompting&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;いくつかの例を与えることで、モデルに期待する出力の形式を示す&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;img src=&#34;images/prompt-engineering-fig-5-3.png&#34; alt=&#34;&#34; /&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;以下の欠点がある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;多くのコンテキストを含めるには、例の羅列という方法では長くなりすぎる。回答フォーマットの例示としての用途が良い&lt;/li&gt;&#xA;&lt;li&gt;例の内容によって補完内容にバイアスがかかる。&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば本のレビューをしたい場合、few shot として「本A: 1, 本B: 2, 本C: 1」と与えると、モデルは「本D: 1」(実際は 5 だったとしても) といったように、与えた例に従った補完をする&lt;/li&gt;&#xA;&lt;li&gt;このようなケースでは、できるだけ実際の確率分布に従った例を与える必要がある (簡単ではないが)&lt;/li&gt;&#xA;&lt;li&gt;一方でエッジケースの明示には効果的&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;意図しないパターンに LLM が従ってしまう。&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば「本A: 1, 本B: 2, 本C: 3」と与えた場合、モデルは単に昇順にしたスコアで「本D: 4」と補完してしまう可能性がある。また例の最初に典型例、後に例外や特殊例を与えると、モデルは直近の例を強く評価し、悲観的な補完をする傾向がある&lt;/li&gt;&#xA;&lt;li&gt;バイアスを避けるために、例として何をどんな順で乗せるかは簡単ではない。&lt;a href=&#34;https://github.com/stanfordnlp/dspy&#34;&gt;DSPy&lt;/a&gt; という手法も考案されている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;動的コンテンツ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;関連のある情報を収集する。静的コンテツとは違い、レイテンシ、準備のしやすさ (preparability, その情報の取得が簡単か・困難か)、比較可能性 (情報は多く収集しあとからトリアージするが、その際に他の情報と比較できなければいけない)&lt;/li&gt;&#xA;&lt;li&gt;どのような情報を集めるべきかは、開発者による発想が求められる部分。考えの助けになるフレームワークは、マインドマップ、あるいは「ユーザーとの近さ」x「変化の多さ」の 2 次元にマッピングするなど&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://arxiv.org/abs/2005.11401&#34;&gt;RAG (Retrieval-Augmented Generation)&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;トレーニングセットには存在しない情報を付与するためのパターン&lt;/li&gt;&#xA;&lt;li&gt;無関係な情報を含めないように注意しないといけない (チェーホフの銃の誤謬)。コンテキストサイズが大きくても、無関係な情報を含めてしまうと、モデルはそれを考慮して補完を行い、精度が下がる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;img src=&#34;images/prompt-engineering-fig-5-10.png&#34; alt=&#34;&#34; /&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Lexical retrieval&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;チェーホフの銃の誤謬を避ける軽量な方法。検索クエリと取得結果に登場する単語の出現率に基づく類似度でスコアを付ける。スコアの低いものを除く&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Stopword (一般的な単語) を除外し、Stemming (語幹を抽出) し、ジャッカード係数でスコアを付ける。TF-IDF や BM25 などを利用する発展系もある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;NLP で昔から使われている手法なので、一般的な NLP のライブラリには実装されている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Neural retrieval&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;単に単語の出現頻度による類似度ではなく、より意味を考慮した類似度の計算を行う。embedding model を用いてテキストをベクトルに変換し、その距離で類似度を計算する。距離の計算はユークリッド距離やコサイン類似度などでよい&lt;/li&gt;&#xA;&lt;li&gt;検索対象は事前に embedding model でベクトル化しておく必要がある (簡単には OpenAI のツールを用いても可能)。計算結果は一般的に Vector Storage と呼ばれる DB に格納される。検索クエリも同様にベクトル化し、Vector Storage に問い合わせる&lt;/li&gt;&#xA;&lt;li&gt;&lt;img src=&#34;images/prompt-engineering-fig-5-12.png&#34; alt=&#34;&#34; /&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Neural retrieval が必ずしも常にベストチョイスなわけではない。Lexcical retrieval は枯れた技術で、Elasticsearch などの成熟したツールがすでにあり、問題時のデバッグや手動でのチューニングはしやすい。一方で意味的なマッチをしたい場合 (別言語間の類似度や、画像の類似度など) は Neural retrieval でないと実現できない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;要約&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;長いコンテキストは要約する必要がある。テキストの要約は LLM が得意なタスク&lt;/li&gt;&#xA;&lt;li&gt;例えば本全体を要約する必要がある場合、段落 =&amp;gt; 章 =&amp;gt; 全体のように、段階的に要約していくと良い。ソースコードの場合は個別のファイル =&amp;gt; ディレクトリ =&amp;gt; プロジェクト全体、など&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;6. Assembling the Prompt&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;前章で集めたコンテンツをどのように構成し、どのようにトリアージし、最終的なプロンプトに仕上げるのかを見ていく。プロンプトの最終形は導入・本文・リフォーカス・移行からなる。ドキュメントには会話形式、分析レポート、構造化ドキュメントの 3 種類がある。本文にどう情報を組み込むかは、各構成要素の依存関係や重要性などを基にした最適化問題になる。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;Anatomy of the Ideal Prompt (理想的なプロンプトの構成)&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;img src=&#34;images/prompt-engineering-fig-6-1.png&#34; alt=&#34;&#34; /&gt;&lt;/li&gt;&#xA;&lt;li&gt;導入 (Introduction)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;プロンプトの目的、文脈を明確化する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;本文 (プロンプト要素の列)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;紹介文にプロンプトの各要素が続く。ここで LLM には以下の傾向があることに注意する&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://arxiv.org/abs/2302.11042&#34;&gt;In-context learning&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;プロンプトの最後に近いほど、モデルへの影響が高くなる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://arxiv.org/abs/2307.03172&#34;&gt;The lost middle phenomenon&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;モデルはプロンプトの最初と最後を簡単に思い出せるが、真ん中の部分はそうではない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;これらの特徴から、プロンプトの特に前半中間にある内容が効果的に使われないという、The Valley of Meh と呼ばれる現象が起こる。これを防ぐ方法はない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;リフォーカス (Refocusing)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;LLM に本題を思い出させるためのもの。プロンプト全体が長いときほど効果的&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;移行 (Transition)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;これまでのコンテキストの説明を終え、問題解決を行うよう促すもの。プロンプトの最後に加える&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;ドキュメントの形式&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;赤ずきんの原理に基づき、プロンプトはトレーニングセットにある文章に似ている方がよい。それを踏まえ、ここでは典型的なドキュメントの形式を見ていく&lt;/li&gt;&#xA;&lt;li&gt;会話形式&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;2 人の会話形式。1 人 (ユーザー) は何らかの助けを求め、もう 1 人 (モデル) はそれ助ける。&lt;/li&gt;&#xA;&lt;li&gt;自然で、複数回のやり取りも扱うことができ、実世界との統合もしやすい&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;分析レポート&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;大学や企業で報告されるようなレポート形式。&lt;/li&gt;&#xA;&lt;li&gt;通常は Markdown 形式が推奨される。トレーニングセットに最も多く含まれており、必要な機能がシンプルに備わっている&lt;/li&gt;&#xA;&lt;li&gt;目次をつけるのも効果的。モデルの注意を適切に引き、Chain of Thoughts を促す&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;構造化ドキュメント (The Structured Document)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Python スクリプト、小さな React アプリ、マーメイド記法で作図された図式、SVG など、構造を持つもの&lt;/li&gt;&#xA;&lt;li&gt;特定の形式を強制し、モデルの出力を扱いやすくする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;スニペットのフォーマッティング&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;動的に得られた情報をプロンプトに組み込む際、上記の形式に沿った自然なものにする&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;# 会話形式の場合の例&#xA;User: What&#39;s the weather like?&#xA;Assistant: It&#39;s going to be {{ weather[&amp;quot;description&amp;quot;] }} with a temperature of {{ weather[&amp;quot;temperature&amp;quot;] }} degrees.&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Elastic Snippets&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ひとつの情報をスニペットにするとき、長さが違う複数のバージョンを用意しておく。コンテキストウィンドウのサイズにあわせて、その中のどれを使うかを決定する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;プロンプト要素の関係&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;各プロンプト要素をどう配置するか。位置、重要性、依存関係をもとに決定する&lt;/li&gt;&#xA;&lt;li&gt;位置&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;時系列や因果関係などが壊れないように配置する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;重要性&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;重要な要素に多くのトークンを割り当てるのか、そうでないものを多く割り当てるのかのトレードオフ&lt;/li&gt;&#xA;&lt;li&gt;前提として、すべての要素が同じ尺度でスコアリングされている必要がある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;依存関係&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Requirement (必要条件。ある要素が他の要素に依存している) と Incompatibility (非互換性。ある要素が他の要素と同時に存在できない) の 2 つの関係がある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;img src=&#34;images/prompt-engineering-fig-6-6.png&#34; alt=&#34;&#34; /&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;ここまで来ると、最終的なプロンプトの作成は、最適化問題になる&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;依存関係とプロンプトのの長さの制約の中で、価値を最大化するようなプロンプトを作成する&lt;/li&gt;&#xA;&lt;li&gt;線形プログラミングや 0-1ナップサック問題などに似ているが、独自のソリューションが必要になることが多い&lt;/li&gt;&#xA;&lt;li&gt;最初はナイーブな方法から始めて発展させていく&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;7. Taming the Model&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;ここまででプロンプトの作成ができるようになったので、次はモデル自体に関わるトピックを扱う。logprobs を用いてアプリケーションを工夫することができる。モデル選択の判断軸。Fine tuning をするかどうか、する場合でも、より軽量な手法もある。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;理想的な補完の構造&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;img src=&#34;images/prompt-engineering-fig-7-1.png&#34; alt=&#34;&#34; /&gt;&lt;/li&gt;&#xA;&lt;li&gt;前文 (preamble)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;RLHF が丁寧なやりとりをする場合の前置きや免責事項など。結果をアプリケーションが解釈する際に邪魔にもなりやすい (システムプロンプトである程度は抑制できる)&lt;/li&gt;&#xA;&lt;li&gt;一方で Chain of thoughts で推論を行う場合、前文が長い方がより良い出力になることが多い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;認識できる開始と終了&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;モデルのアウトプットをプログラムで解釈するためには、モデルの補完結果部分の開始と終了が認識できないといけない。例えばマークダウンの場合はセクション記法など、ドキュメントの形式によって異なる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;追記 (postscript)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;前文同様にこちらも不要なのでなくしたいことが多い。プロンプトで抑制するのも良いが、モデルによっては stop sequence に対応しているものもある。streaming で接続している場合は、必要なアウトプットの終了を認識できた時点で生成をストップさせると良い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;logprobs を活用する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;出力の確信度に基づいたアクションを行う。例えば、一定の確度以上の場合にだけユーザーに提示する、一定の確度以下の場合リトライしたり、別のモデルに切り替えるなど&lt;/li&gt;&#xA;&lt;li&gt;モデルに何らかの分類、例えば「この文面はプロフェッショナルに書かれていますか」、をさせる場合、その度合いを logprobs を用いて定量的に判定する&lt;/li&gt;&#xA;&lt;li&gt;プロンプト (入力側) のエラー検知。モデルによっては &lt;code&gt;echo&lt;/code&gt; という、プロンプトを含めてレスポンスしてくれるものもある。この場合プロンプトの logprobs も確認できる。例えばタイプミスがあった場合、そのトークンだけ logprobs が非常に低くなったりする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;モデルの選択&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;以下の軸で検討すると良い&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;性能 (intelligence)&lt;/li&gt;&#xA;&lt;li&gt;レイテンシ&lt;/li&gt;&#xA;&lt;li&gt;コスト&lt;/li&gt;&#xA;&lt;li&gt;使いやすさ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;GPU 配置の可変性、モデルのデプロイ、クラッシュ時のリスタート、キャッシュ機能などの有無&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;機能&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ツール機能があるか、logprobs を返すかなど&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;その他&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;OSS でなければならない。特定のデータ所在地でなければならないといった、特殊な要件&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;小さく始めるには商用の LLM-as-a-service から始めるのが速い。発展に応じて OSS モデルに切り替えることもあり得る&lt;/li&gt;&#xA;&lt;li&gt;十分な性能のモデルよりも、良いモデルで開発を進めると良い。新しいモデルが出ると、過去のものは値下げされる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Fine tuning&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;fine tuning を行うべきかどうかは、まずはそのトレーニングセットがどれだけ入手しやすいかを考えるべき&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;img src=&#34;images/prompt-engineering-fig-7-8.png&#34; alt=&#34;&#34; /&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;また fine timing の手法にも段階がある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Full fine tuning&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;モデルのすべてのパラメータを継続的にチューニングする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Parameter efficient  fine-tuning (PeFT)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;パラメータの一部をチューニングする。LoRA のような手法が例。Full よりも計算が短い。&lt;/li&gt;&#xA;&lt;li&gt;Full はモデルに新しい知識を教えることができる。LoRA はそうではなく、モデルの前提を調整するイメージ。例えば日本を起点にした旅行アプリの場合、日本をベースにした旅行先を提案するように調整するといった使い方になる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Soft prompt&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Fine tuning をするのではなく soft-prompt, つまりプロンプト側を学習し embedding (ベクトル) を入力として渡す&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Fine tuning を施した場合でも、赤ずきんの原理に注意する。プロンプトが Fine tuning のデータセットに近いものにしないと、チューニング前の汎用な方のパスに入ることもある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;III. An Expert of the Craft&lt;/h2&gt;&#xA;&#xA;&lt;h3&gt;8. Conversational Agency&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;エージェント型という、現実世界とインタラクションするものについて。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;Tool usage&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;チャットモデルはナレッジカットオフ以降の情報は持っておらず、重要な情報を見逃したり、不得意なタスク (特に数学) がある。また実際に現実世界に作用  (メールを送る、など) をすることもできない。こうした限界を克服するために、ツールを利用する&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;2023 年 6 月に OpenAI はツール呼び出し用に fine tuning された新しいモデルを導入。その後、他の競合もそれに追随した&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;OpenAI を例にした実装例&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;function の定義をする&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;# get_room_temp, set_room_temp の実装は既にあるとする&#xA;tools = [&#xA;    {&#xA;        &amp;quot;type&amp;quot;: &amp;quot;function&amp;quot;,&#xA;        &amp;quot;function&amp;quot;: {&#xA;            &amp;quot;name&amp;quot;: &amp;quot;get_room_temp&amp;quot;,&#xA;            &amp;quot;description&amp;quot;: &amp;quot;Get the ambient room temperature in Fahrenheit&amp;quot;,&#xA;        },&#xA;    },&#xA;    {&#xA;        &amp;quot;type&amp;quot;: &amp;quot;function&amp;quot;,&#xA;        &amp;quot;function&amp;quot;: {&#xA;            &amp;quot;name&amp;quot;: &amp;quot;set_room_temp&amp;quot;,&#xA;            &amp;quot;description&amp;quot;: &amp;quot;Set the ambient room temperature in Fahrenheit&amp;quot;,&#xA;            &amp;quot;parameters&amp;quot;: {&#xA;                &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,&#xA;                &amp;quot;properties&amp;quot;: {&#xA;                &amp;quot;temp&amp;quot;: {&#xA;                    &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;,&#xA;                    &amp;quot;description&amp;quot;: &amp;quot;The desired room temperature in ºF&amp;quot;,&#xA;                    },&#xA;                },&#xA;                &amp;quot;required&amp;quot;: [&amp;quot;temp&amp;quot;],&#xA;            },&#xA;        },&#xA;    }&#xA;]&#xA;&#xA;&#xA;available_functions = {&#xA;    &amp;quot;get_room_temp&amp;quot;: get_room_temp,&#xA;    &amp;quot;set_room_temp&amp;quot;: set_room_temp,&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;これを利用し処理する process_message 関数の例&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;def process_messages(client, messages):&#xA;# Step 1: send the messages to the model along with the tool definitions&#xA;response = client.chat.completions.create(&#xA;    model=&amp;quot;gpt-4o&amp;quot;,&#xA;    messages=messages,&#xA;    tools=tools,&#xA;)&#xA;response_message = response.choices[0].message&#xA;# Step 2: append the model&#39;s response to the conversation&#xA;# (it may be a function call or a normal message)&#xA;messages.append(response_message)&#xA;# Step 3: check if the model wanted to use a tool&#xA;if response_message.tool_calls:&#xA;    # Step 4: extract tool invocation and make evaluation&#xA;    for tool_call in response_message.tool_calls:&#xA;        function_name = tool_call.function.name&#xA;        function_to_call = available_functions[function_name]&#xA;        function_args = json.loads(tool_call.function.arguments)&#xA;        function_response = function_to_call(&#xA;            # note: in python the ** operator unpacks a&#xA;            # dictionary into keyword arguments&#xA;            **function_args&#xA;        )&#xA;        # Step 5: extend conversation with function response&#xA;        # so that the model can see it in future turns&#xA;        messages.append(&#xA;            {&#xA;                &amp;quot;tool_call_id&amp;quot;: tool_call.id,&#xA;                &amp;quot;role&amp;quot;: &amp;quot;tool&amp;quot;,&#xA;                &amp;quot;name&amp;quot;: function_name,&#xA;                &amp;quot;content&amp;quot;: function_response,&#xA;            }&#xA;        )&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;呼び出しとその結果の例&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;# 呼び出し&#xA;messages = [&#xA;    {&#xA;        &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&#xA;        &amp;quot;content&amp;quot;: &amp;quot;You are HomeBoy, a happy, helpful home assistant.&amp;quot;,&#xA;    },&#xA;    {&#xA;        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&#xA;        &amp;quot;content&amp;quot;: &amp;quot;Can you make it a couple of degrees warmer in here?&amp;quot;,&#xA;    }&#xA;]&#xA;client = OpenAI()&#xA;process_messages(client, messages)&#xA;&#xA;# 結果1。モデルはツールを呼び出し、現在の温度を取得しようとしている。&#xA;[&#xA;    {&#xA;        &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&#xA;        &amp;quot;content&amp;quot;: None,&#xA;        &amp;quot;tool_calls&amp;quot;: [{&#xA;            &amp;quot;id&amp;quot;: &amp;quot;call_t7vNPjRlFJ3nKAhdGAz256cZ&amp;quot;,&#xA;            &amp;quot;function&amp;quot;: {&#xA;                &amp;quot;arguments&amp;quot;: &amp;quot;{}&amp;quot;,&#xA;                &amp;quot;name&amp;quot;: &amp;quot;get_room_temp&amp;quot;&#xA;            },&#xA;            &amp;quot;type&amp;quot;: &amp;quot;function&amp;quot;,&#xA;        }],&#xA;    },&#xA;    {&#xA;        &amp;quot;tool_call_id&amp;quot;: &amp;quot;call_t7vNPjRlFJ3nKAhdGAz256cZ&amp;quot;,&#xA;        &amp;quot;role&amp;quot;: &amp;quot;tool&amp;quot;,&#xA;        &amp;quot;name&amp;quot;: &amp;quot;get_room_temp&amp;quot;,&#xA;        &amp;quot;content&amp;quot;: &amp;quot;74&amp;quot;,&#xA;    }&#xA;]&#xA;&#xA;# process_message を再度呼んで 1 ステップ進める。&#xA;process_messages(client, messages)&#xA;&#xA;# 現在温度に応じて温度を変更しようとしている。&#xA;[&#xA;    {&#xA;        &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&#xA;        &amp;quot;tool_calls&amp;quot;: [{&#xA;            &amp;quot;function&amp;quot;: {&#xA;                &amp;quot;name&amp;quot;: &amp;quot;set_room_temp&amp;quot;&#xA;                &amp;quot;arguments&amp;quot;: &amp;quot;{\&amp;quot;temp\&amp;quot;:76}&amp;quot;,&#xA;            },&#xA;            &amp;quot;type&amp;quot;: &amp;quot;function&amp;quot;&#xA;            &amp;quot;id&amp;quot;: &amp;quot;call_X2prAODMHGOmgt523Ob9BIij&amp;quot;,&#xA;        }],&#xA;    },&#xA;    {&#xA;        &amp;quot;role&amp;quot;: &amp;quot;tool&amp;quot;,&#xA;        &amp;quot;name&amp;quot;: &amp;quot;set_room_temp&amp;quot;,&#xA;        &amp;quot;content&amp;quot;: &amp;quot;DONE&amp;quot;&#xA;        &amp;quot;tool_call_id&amp;quot;: &amp;quot;call_X2prAODMHGOmgt523Ob9BIij&amp;quot;,&#xA;    }&#xA;]&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;モデルの内部ではツールは次のように、システムプロンプトでマークダウンと Typescript で定義されている。いずれもトレーニングセットで頻出の形式。また静的型付けもモデルへのよいヒントになる。(ただし OpenAI は内部表現を公開しておらず、著者らの独自調査による内容)。&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;&amp;lt;|im_start|&amp;gt;system&#xA;You are HomeBoy, a happy, helpful home assistant.&#xA;# Tools&#xA;## functions&#xA;namespace functions {&#xA;// Set the ambient room temperature in Fahrenheit&#xA;type set_room_temp = (_: {&#xA;// The desired room temperature in ºF&#xA;temp: number,&#xA;}) =&amp;gt; any;&#xA;} // namespace functions&#xA;&amp;lt;|im_end|&amp;gt;&#xA;// 利用例&#xA;&amp;lt;|im_start|&amp;gt;user&#xA;I&#39;m a bit cold. Can you make it a couple of degrees warmer in here?&amp;lt;|im_end|&amp;gt;&#xA;&amp;lt;|im_start|&amp;gt;assistant to=functions.get_room_temp&#xA;{}&amp;lt;|im_end|&amp;gt;&#xA;&amp;lt;|im_start|&amp;gt;tool&#xA;74&amp;lt;|im_end|&amp;gt;&#xA;&amp;lt;|im_start|&amp;gt;assistant to=functions.set_room_temp&#xA;{&amp;quot;temp&amp;quot;: 76}&amp;lt;|im_end|&amp;gt;&#xA;&amp;lt;|im_start|&amp;gt;tool&#xA;DONE&amp;lt;|im_end|&amp;gt;&#xA;&amp;lt;|im_start|&amp;gt;assistant&#xA;The room temperature was 74ºF and has been increased to 76°F.&amp;lt;|im_end|&amp;gt;&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;ツール定義のガイドライン&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;適切なツールを選択できるよう、必要なものだけにする。複数のツールがあるとモデルは混乱する。&lt;/li&gt;&#xA;&lt;li&gt;関数名・変数名などは、名前自体がドキュメントにもなっているような、適切な命名をする&lt;/li&gt;&#xA;&lt;li&gt;ツールの説明文は必要十分に&lt;/li&gt;&#xA;&lt;li&gt;引数は極力シンプルにする。不要なものは省く。JSON Schema は利用できるが、高度な定義 (min&#xA;Items, uniqueItems, minimum, maximum, pattern, format 等) に頼らないほうが良い&lt;/li&gt;&#xA;&lt;li&gt;ツールの返り値には不要なものは含まない。あったら便利かもしれないものを含めるとモデルが混乱する&lt;/li&gt;&#xA;&lt;li&gt;エラーはそのまま帰すのではなく、モデルの視点で活用できるように注意する。バリデーションエラーであれば、その理由と修正してリトライする旨を伝える。&lt;/li&gt;&#xA;&lt;li&gt;危険な操作には人間の承認を必要とする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Reasoning&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;モデルの推論能力は、一度に結果を出すのではなく、内部のモノローグを促すことによって強化することができる&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://arxiv.org/abs/2201.11903&#34;&gt;Chain of thought&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;2022 年の論文 &lt;a href=&#34;https://arxiv.org/abs/2201.11903&#34;&gt;Chain-of-Thought Prompting Elicits Reasoning in Large Language Models&lt;/a&gt; では、モデルに先に理由を述べてから回答を出力するように促すと精度が上がることを実証した。一般的な質問では 69.4% から 75.6%、数学ドメインでは 20 から 60% の改善がみられた&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;次のような few-shot プロンプトでそれを実現した&#xA;&lt;code&gt;&#xA;Q: Do hamsters provide food for any animals?&#xA;A: Hamsters are prey animals. Prey are food for predators. Thus, hamsters&#xA;provide food for some animals. So the answer is yes.&#xA;Q: Yes or no: would a pear sink in water?&#xA;A: The density of a pear is about 0.6g/cm3, which is less than water. Objects&#xA;less dense than water float. Thus, a pear would float. So the answer is no.&#xA;&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;その後 &lt;a href=&#34;https://arxiv.org/abs/2205.11916&#34;&gt;Large Language Models are Zero-Shot Reasoners&lt;/a&gt; では単に &lt;code&gt;Let’s think step-by-step,&lt;/code&gt; と付け加えるだけで同様の効果が得られることが示された&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://arxiv.org/abs/2310.02226&#34;&gt;Think before you speak: Training Language Models With Pause Tokens&lt;/a&gt; では &lt;code&gt;pause&lt;/code&gt; トークンを認識できるようモデルを fine tuning し、&lt;code&gt;pause&lt;/code&gt; トークンを適当に挟むことで、モデルが立ち止まって考えられるようになり、精度が上がることを示した&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;いずれにしても、一度に答えを出すのではなく、内部で考えるように促すことで、推論の精度を上げることができる&lt;/p&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://arxiv.org/abs/2210.03629&#34;&gt;ReAct: Iterative Reasoning and Action&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;モデルに Think-Act-Observe のサイクルを繰り返させることで、より複雑なタスクをこなせるようにする&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;例えば「雑誌 A と 雑誌 B のどちらが古いか」というタスクを解かせる。ここで以下の Search, Lookup, Finish の 3 つのアクションを定義する&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;Solve a question-answering task with interleaving Thought, Action, and Observation steps.&#xA;Thought can reason about the current situation, and Action can be three types:&#xA;(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search&#xA;(2) Lookup[keyword], which returns the next sentence containing a keyword in the current passage&#xA;(3) Finish[answer], which returns the answer and finishes the task&#xA;Here are some examples.&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;そのうえで 3,000 程度のサンプルでモデルを fine tuningu すると精度が向上した&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Search, Lookup ツールで検索するだけでも精度は良かったが、Reasoning を加えることでさらに向上した&lt;/li&gt;&#xA;&lt;li&gt;上記のプロンプトの下に few-shot を追加するだけではうまくいかず、fine tuning が必要だった&lt;/li&gt;&#xA;&lt;li&gt;&lt;img src=&#34;images/prompt-engineering-fig-8-1.png&#34; alt=&#34;&#34; /&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://arxiv.org/abs/2305.04091&#34;&gt;Plan-and-Solve Prompting: Improving Zero-Shot Chain-of-Thought Reasoning by Large Language Models&lt;/a&gt; では次のようなプロンプトを追加し、最初に計画をたててから実行するよう促すだけで、精度が向上することを示した。この手法はツール利用を伴わないシチュエーションでも有効&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;Let’s first understand the problem and devise a plan to solve the problem. Then, let’s carry out the plan and solve the problem step-by-step.&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://arxiv.org/abs/2303.11366&#34;&gt;Reflexion: Language Agents with Verbal Reinforcement Learning&lt;/a&gt; は反対に実行後に結果を振り返るというアプローチをとっている&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://arxiv.org/abs/2310.15123&#34;&gt;Branch-Solve-Merge Improves Large Language Model Evaluation and Generation&lt;/a&gt; は複数のソルバーに問題を解かせてからマージする&lt;/p&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;コンテキスト管理&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;前までの章の議論に加えて、エージェント型のアプリケーションでのコンテキスト管理について、さらに必要な要件がある&lt;/li&gt;&#xA;&lt;li&gt;コンテキストの収集元が追加される。これまでの会話の履歴、開いているファイルなどの (ユーザーがコピー&amp;amp;ペーストするのではなくエージェントがツールを通じて取得する)、その他ツールを利用して取得した外部の情報など&lt;/li&gt;&#xA;&lt;li&gt;コンテキストのトリアージには、万能な方法はない。&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;不要なツールは候補から外す&lt;/li&gt;&#xA;&lt;li&gt;必要なアーティファクトだけを含める&lt;/li&gt;&#xA;&lt;li&gt;アーティファクトのフォーマット (XML, JSON, Markdown など)&lt;/li&gt;&#xA;&lt;li&gt;アーティファクトをどのように提供するか。単に全てを含めても収まるのか、Elastic snippet のような要約が必要か、RAG のような検索システムが必要か&lt;/li&gt;&#xA;&lt;li&gt;以前の会話をどこまで含めるか&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;9. LLM Workflows&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;現在の LLM の性能では、汎用性を維持しながら特定の複雑なタスクを確実にこなすことはできない。この章では LLM Workflows という、特定のドメインに特化させて複雑なタスクを遂行できるアプリケーションを見る。タスクをサブタスクに分解し、それをスーパーバイズする。スーパーバイザーは LLM でもそうでないパターンもある。なおここでは Lang‐ Chain, Semantic Kernel, AutoGen, DSPy といった特定のフレームワークの詳細には立ち入らず、概念的な説明を行う。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;img src=&#34;images/prompt-engineering-fig-9-1.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;このような問題を考える。現在の LLM の性能ではこれを直接遂行するのは難しい&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;1.人気のあるShopifyストアフロントのリストを生成し、ウェブサイトのHTMLを取得。2.各ストアフロントについて、製品の提供、ブランディング、スタイル、価値などの詳細を抽出。3.各ストアフロントを確認し、そのビジネスに役立つプラグインを考案。4.各ストアフロントオーナーにプラグインコンセプトを宣伝するマーケティングメールを生成。5.メールを送る&lt;/li&gt;&#xA;&lt;li&gt;必要な web 検索やメール送信といったツールを用意しても、例えば網羅的に &lt;code&gt;1&lt;/code&gt; のリストアップタスクができる可能性は低い&lt;/li&gt;&#xA;&lt;li&gt;ツール側に要件を満たせるような特殊な実装を多く入れても、コンテキストが大きくなりすぎる、単一の LLM とのやり取りではうまくいかない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;基本的なワークフロー&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;全体をサブタスクに分割する。各タスクの入出力を特定する&lt;/li&gt;&#xA;&lt;li&gt;タスクは LLM を使っても使わなくても良い。不必要なのであれば、従来の技術を使って実装した方が安定する。また人間による作業を伴うタスクがあっても良い&lt;/li&gt;&#xA;&lt;li&gt;LLM を使う場合のアプローチ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;プロンプトのテンプレート。タスクの入力値で埋めてプロンプトが完成する。Lang-Chain はこのアプローチを採用している&lt;/li&gt;&#xA;&lt;li&gt;ツールベースのアプローチ。例えば取得した html から店舗情報や商品情報を抽出するようなタスクは LLM が得意とする。特に入力として html をとるようなツールとして定義すると簡単&lt;/li&gt;&#xA;&lt;li&gt;複雑な推論では chain of thoughts を促してやる必要がある。OpenAI API であれば、ツール利用をオフにして試行させた上で、ツール利用をオンにして実施させる&lt;/li&gt;&#xA;&lt;li&gt;出力が正しくないと判定できる場合 (コードが出力ならテストを通すとか、LLM に出力を判断させるとか) 、それを入力にし再度修正を試みる実装もできる。ただし複雑さは大きくなる。&lt;/li&gt;&#xA;&lt;li&gt;ワークフロー全体を構築する前に、個々のタスクの動作が適切か確認評価しておくと良い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ここまででタスクの集合が定義できた。各タスクはDAG として表現できる。そしてDAG を管理するのは、従来からの Airflow や Luigi といったツールを利用することができる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;LLM ベースのタスクの場合、失敗時の再試行が発生することがある。可能ならばこのリトライをタスク内で処理できると、DAG がシンプルになって良い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;発展系では、タスクのスーパーバイザーを LLM に任せる構成&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;柔軟性が増える一方、開発の難しさが増える&lt;/li&gt;&#xA;&lt;li&gt;なかでも最もシンプルなものは、各タスクをツールとして、スーパーバイザーのモデルに依頼する形式&lt;/li&gt;&#xA;&lt;li&gt;これを発展させると、各タスクもそれぞれ会話型のエージェントとし、エージェント間のやり取りとしてワークフロー全体が構成される形式&lt;/li&gt;&#xA;&lt;li&gt;さらに進めると、スーパーバイザーのモデルが必要に応じてタスクを生成するという構成もありえる&lt;/li&gt;&#xA;&lt;li&gt;いずれにしてもこの分野はまだまだ安定しておらず、フロンティア&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;10. Evaluating LLM Applications&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;アプリケーションをどう評価するか。Copilot の開発では最初に評価システムを構築したことでうまくいった。評価方法にはオフライン評価とオンライン評価がある。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;テスト対象&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;4章の LLM アプリケーションのループ全体のテスト、またはループの各要素の個別のテスト&lt;/li&gt;&#xA;&lt;li&gt;例えばモデルを切り替えたい場合などは前者で、モデルのパラメータをチューニングしたい場合は後者で評価できると便利&lt;/li&gt;&#xA;&lt;li&gt;最初に始める際にどちらかだけを選ぶのであれば前者のほうが良い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;オフライン評価&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Example Suites&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;実際に使われそうなプロンプトを5-20個ほど準備、それらを自動でアプリケーションに読み込ませ結果を保存、以前のバージョンとの diff をとる&lt;/li&gt;&#xA;&lt;li&gt;いわゆるソフトウエアテストと異なり自動で結果が評価されるのではなく、人間がチェックしやすいだけの仕組み。ただ、全くアプリケーションがない状態でも始められる・結果を確認しながらアプリケーションを作り込めるという大きなメリットがある&lt;/li&gt;&#xA;&lt;li&gt;複雑なアプリケーションの場合、個別の LLM とのやり取り部分だけに適用しても良い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;サンプルの収集方法&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;そのタスクがすでに AI に頼らず実施されている (これからそれを AI アプリケーション化する) 場合、それを利用できる可能性がある。ただし現実のサンプルの分布とは離れている可能性はある&lt;/li&gt;&#xA;&lt;li&gt;アプリケーションのリリース後の場合は実際の利用ログが使える場合がある。ただしリリース前には不可能、アプデ後には古いサンプルが無意味化、収集するためのプライバシーなどの整理が必要、生成結果が理想的とは限らないといった注意点がある&lt;/li&gt;&#xA;&lt;li&gt;自分で、あるいは LLM にサンプルの生成を依頼することもできる。LLM を使う場合は、まずサンプルのカテゴリを洗い出させ、それぞれのカテゴリごとに N 個のサンプルを生成するよう段階を踏むと良い。依然実際のサンプルの分布とはずれる可能性はあ？ことは留意する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;生成結果の評価&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Gold standard&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Yes/No を回答するものや、複数の選択肢から選ぶようなものなど、理想の回答が確定的に定義できるケース&lt;/li&gt;&#xA;&lt;li&gt;自由に文章を生成するケースでは、重要な部分文字列を定義してその一致度を測るアプローチをとる&lt;/li&gt;&#xA;&lt;li&gt;あるいはツール利用のエージェントの場合、適切な fiction call ができているかを測る方法もある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Functional testing&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;生成結果が機能するかをテストする。コンパイルが通るか、単体テストをパスするかなど&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;LLM assessment&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;上記のいずれもが難しい場合、LLM に判断を任せる方法もある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;SOMA assessment&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;次の例のように、評価軸とそれぞれの度合いを定義し、LLM に評価させる方法&#xA;&lt;code&gt;&#xA;I need your help with evaluating a smart home assistant. I&#39;m going to give you some interactions of that assistant, which you are to grade on a scale of 1 to 5. Grade each interaction for effectiveness: whether the assistant&#39;s attempted action would have remedied the user&#39;s problem.&#xA;Please rate effectiveness on a scale of 1 to 5, where the values mean the&#xA;following:&#xA;1. This action would do nothing to address the user&#39;s problem or might even make it worse.&#xA;2. This action might address a small part of the problem but leave the main part unaddressed.&#xA;3. This action has a good chance of addressing a substantial part of the problem.&#xA;4. This action is not guaranteed to work completely, but it should solve most of the problem.&#xA;5. This action will definitely solve the problem completely.&#xA;The conversation was as follows:&#xA;User: I&#39;m a bit chilly.&#xA;Assistant: to functions.set_room_temp {“temp”: 77}&#xA;Please provide a thorough analysis and then conclude your answer with &amp;quot;Effectiveness: X,&amp;quot; where X is your chosen effectiveness rating from 1 to 5.&#xA;&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;オンライン評価&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;A/B テスト&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;スタンダードな方法。評価指標をどのように選ぶかが肝になる&lt;/li&gt;&#xA;&lt;li&gt;いずれにせよオンライン評価はオフライン評価に比べコストも時間がかかるので、オフライン評価も駆使しながらなにを オフライン評価にするかよく検討したほうが良い&lt;/li&gt;&#xA;&lt;li&gt;スタンダード方法なので、既存のソリューションが多くある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Metrics&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;以下に大別できる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;直接フィードバック：ユーザーからの直接フィードバック&lt;/li&gt;&#xA;&lt;li&gt;機能的正確性：アウトプットは機能するか。コンパイルやテストが通るかなど&lt;/li&gt;&#xA;&lt;li&gt;ユーザーの受け入れ：ユーザーは提案に従ったか&lt;/li&gt;&#xA;&lt;li&gt;達成された影響：ユーザーはどのくらい恩恵を受けるか&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;例えば ChatGPT のサムズアップ・ダウンボタンは直接フィードバックにあたる。ただしこれはサムズアップされるのは特に優れた提案がされたときが多く、サムズダウンは不満があるときに多く押されるなど、バイアスはある&lt;/li&gt;&#xA;&lt;li&gt;ユーザーの受け入れは、例えば旅行アプリの場合、提案した旅行プランを実際に予約したかどうかなど。可能ならこのような指標を開発できると良い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;11. Looking Ahead&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;今後の発展。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Multimodality&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;テキストだけでなく、画像や音声などのマルチモーダルな情報を扱うことができるようになる&lt;/li&gt;&#xA;&lt;li&gt;方法の一つは、畳み込みネットワークを使い、画像等を embedding vector に変換。移行はテキストと同様に扱うことができる&lt;/li&gt;&#xA;&lt;li&gt;UIUX の幅が広がる他、LLM のトレーニングセットを大幅に増やせるというメリットもある。テキストだけではトレーニングセットが枯渇すると予想されている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;User Experience and User Interface&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;現在は会話形式が一般的&lt;/li&gt;&#xA;&lt;li&gt;その発展形としては、例えばペアプログラミングの対象のコードのように、会話の対象物をステートフルなものとして提示しながらやり取りする形式がある&lt;/li&gt;&#xA;&lt;li&gt;Anthropic の Artifact などはこの例になる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Intelligence&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;現在のベンチマークでは主要なモデルはほぼ上位スコアに飽和しており、LLM コミュニティはベンチマークのアップデートも積極的に行っている&lt;/li&gt;&#xA;&lt;li&gt;知識蒸留という、大きなモデルから迅速に小さなモデルの学習を進める手法も考えられている&lt;/li&gt;&#xA;&lt;li&gt;トレーニング方法の改善だけではなく、例えば量子化など、アーキテクチャレベルの改善も発展として存在する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;div class=&#34;amazlet-box&#34; style=&#34;margin-bottom:0px;&#34;&gt;&lt;div class=&#34;amazlet-image&#34; style=&#34;float:left;margin:0px 12px 1px 0px;&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/B0DM3VLNSK/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;&lt;img src=&#34;https://m.media-amazon.com/images/I/8182Q91T3nL._SY385_.jpg&#34; alt=&#34;Prompt Engineering for LLMs: The Art and Science of Building Large Language Model–Based Applications (English Edition)&#34; style=&#34;border: none; width: 113px;&#34; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-info&#34; style=&#34;line-height:120%; margin-bottom: 10px&#34;&gt;&lt;div class=&#34;amazlet-name&#34; style=&#34;margin-bottom:10px;line-height:120%&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/B0DM3VLNSK/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Prompt Engineering for LLMs: The Art and Science of Building Large Language Model–Based Applications (English Edition)&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-detail&#34;&gt;英語版  John Berryman (著), Albert Ziegler (著)  形式: Kindle版&lt;br/&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-sub-info&#34; style=&#34;float: left;&#34;&gt;&lt;div class=&#34;amazlet-link&#34; style=&#34;margin-top: 5px&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/B0DM3VLNSK/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Amazon.co.jpで詳細を見る&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-footer&#34; style=&#34;clear: left&#34;&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&#xA;&lt;div class=&#34;amazlet-box&#34; style=&#34;margin-bottom:0px;&#34;&gt;&lt;div class=&#34;amazlet-image&#34; style=&#34;float:left;margin:0px 12px 1px 0px;&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4814401132/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;&lt;img src=&#34;https://m.media-amazon.com/images/I/81vJdX2fweL._SY385_.jpg&#34; alt=&#34;LLMのプロンプトエンジニアリング —GitHub Copilotを生んだ開発者が教える生成AIアプリケーション開発&#34; style=&#34;border: none; width: 113px;&#34; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-info&#34; style=&#34;line-height:120%; margin-bottom: 10px&#34;&gt;&lt;div class=&#34;amazlet-name&#34; style=&#34;margin-bottom:10px;line-height:120%&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4814401132/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;LLMのプロンプトエンジニアリング —GitHub Copilotを生んだ開発者が教える生成AIアプリケーション開発&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-detail&#34;&gt;John Berryman (著), Albert Ziegler (著)&lt;br/&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-sub-info&#34; style=&#34;float: left;&#34;&gt;&lt;div class=&#34;amazlet-link&#34; style=&#34;margin-top: 5px&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4814401132/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Amazon.co.jpで詳細を見る&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-footer&#34; style=&#34;clear: left&#34;&gt;&lt;/div&gt;&lt;/div&gt;&#xA;</description>
      <author>Kosei Moriyama</author>
      <pubDate>Mon, 21 Apr 2025 19:30:00 +0900</pubDate>
    </item>
    <item>
      <title>証明書のピンニングをしている自アプリのデバッグ</title>
      <link>https://please-sleep.cou929.nu/bypass-pinning-by-using-custom-leaf-certification.html</link>
      <description>&lt;p&gt;モバイルアプリの HTTP 通信を見ながらデバッグしたい場合、&lt;a href=&#34;https://mitmproxy.org/&#34;&gt;mitmproxy&lt;/a&gt; や &lt;a href=&#34;https://www.wireshark.org/&#34;&gt;Wireshark&lt;/a&gt; といったプロキシを挟む (意図的に MITM を行い通信を見る) という定番の方法がある。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://please-sleep.cou929.nu/decrypting-tls-traffic-packet-capture.html&#34;&gt;TLS 通信のパケットキャプチャ - Please Sleep&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;ただし証明書のピンニング (ピン留め, Certificate Pinning, SSL Pinning) をしている場合、中間で通信を覗こうと思っても単純にはできない。プロキシデフォルトの証明書をクライアントは信用しないため、通信に失敗する。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;対象が自プロジェクトや自社のアプリで、管理している証明書にアクセスできる場合、これを回避できる。プロキシに正しい証明書を渡すことでクライアントはそれを信用し通信を行う。あとは通常通りプロキシから通信内容を参照できる。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;ここでは Leaf certificate をピンニングしているケースを想定している。この状況で mitmproxy を使って iOS のネイティブアプリをデバッグする。ピンニングの方式やプロキシソフトによって手順の詳細は変わるので注意。&lt;/p&gt;&#xA;&#xA;&lt;h1&gt;証明書の準備&lt;/h1&gt;&#xA;&#xA;&lt;p&gt;PEM 形式の証明書を用意する。実際にピンニングしているものを使うので、取り扱いには十分注意する。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;PEM 形式は概ね次のようなフォーマット。&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;-----BEGIN PRIVATE KEY-----&#xA;&amp;lt;private key&amp;gt;&#xA;-----END PRIVATE KEY-----&#xA;-----BEGIN CERTIFICATE-----&#xA;&amp;lt;cert&amp;gt;&#xA;-----END CERTIFICATE-----&#xA;-----BEGIN CERTIFICATE-----&#xA;&amp;lt;intermediary cert (optional)&amp;gt;&#xA;-----END CERTIFICATE-----&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;秘密鍵と証明書のそれぞれのファイルがある場合は次のようにして結合する。&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cat cert.key cert.crt &amp;gt; cert.pem&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;h1&gt;証明書を受け渡して Proxy を起動する&lt;/h1&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://docs.mitmproxy.org/stable/concepts-options/#certs&#34;&gt;&lt;code&gt;--certs&lt;/code&gt; オプション&lt;/a&gt; で任意の (Leaf) 証明書を使うよう mitmproxy に指定できる。&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# &amp;lt;domain&amp;gt;=&amp;lt;cert file path&amp;gt; という記法。&#xA;mitmproxy --certs &#39;please-sleep-cou929.nu=/path/to/cert.pem&#39;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://docs.mitmproxy.org/stable/concepts-options/#certs&#34;&gt;https://docs.mitmproxy.org/stable/concepts-options/#certs&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;SSL certificates of the form &amp;ldquo;[domain=]path&amp;rdquo;. The domain may include a wildcard, and is equal to &amp;ldquo;*&amp;rdquo; if not specified. The file at path is a certificate in PEM format. If a private key is included in the PEM, it is used, else the default key in the conf dir is used. The PEM file should contain the full certificate chain, with the leaf certificate as the first entry.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&#xA;&lt;h1&gt;動作確認&lt;/h1&gt;&#xA;&#xA;&lt;p&gt;モバイルデバイスから試す前に、CLI での確認をしておくと効率的。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;mitmproxy 認証局の証明書を使ってアクセスする状況を curl などで確認できる。デフォルトでは mitmproxy は &lt;code&gt;~/.mitmproxy&lt;/code&gt; に認証局のキーを作成する。こちらを引数として渡せば良い。&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# mitmproxy がローカルホストの 8080 番ポートで待ち受けていると仮定。&#xA;curl --proxy 127.0.0.1:8080 --cacert ~/.mitmproxy/mitmproxy-ca-cert.pem https://please-sleep.cou929.nu&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;以上で通信が成功すれば OK。ここまででピンニング以外の部分は確認できる。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;返される証明書の内容も確認しておくとよい。&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# host, port に Proxy の接続先を指定する。&#xA;openssl s_client -servername please-sleep.cou929.nu -host 127.0.0.1 -port 8080 -showcerts &amp;lt;/dev/null 2&amp;gt;/dev/null&#xA;&#xA;# 例えばクライアントでは Leaf 証明書の base64 エンコードした文字列を比較していると行ったケースでは、次のようにしても確認しやすいかもしれない。&#xA;## 上記で取得した証明書部分を cert.crt に書き出しておく。&#xA;cat cert.crt | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der 2&amp;gt;/dev/null |  openssl dgst -sha256 -binary | openssl enc -base64&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;これらの内容がピンニングしているクライアントが想定しているものと一致すれば OK。ここまででピンニングも含めた通信ができていることが確認できる。&lt;/p&gt;&#xA;&#xA;&lt;h1&gt;端末からのアクセス&lt;/h1&gt;&#xA;&#xA;&lt;p&gt;以降は通常のプロキシ利用と同じ手順で良い。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;前述の手順で mitmproxy のプロセスが起動されている&lt;/li&gt;&#xA;&lt;li&gt;iPhone 側でプロキシと証明書の設定&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;Settings &amp;gt; Wifi &amp;gt; 詳細ページ &amp;gt; Configure Proxy&lt;/code&gt; にて、&lt;code&gt;Manual&lt;/code&gt; をタップ&lt;/li&gt;&#xA;&lt;li&gt;mitmproxy を動かしているホストの ip と port 8080 (デフォルト) を入力し Save&lt;/li&gt;&#xA;&lt;li&gt;Safari で &lt;code&gt;mitm.it&lt;/code&gt; にアクセスし証明書をダウンロード&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;Settings &amp;gt; General &amp;gt; Profiles mitmproxy&lt;/code&gt; を選択しインストール&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;Settings &amp;gt; General &amp;gt; About &amp;gt; Certificate Trust Settings&lt;/code&gt; より mitmproxy のトグルを有効化&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;該当のアプリで適当に通信を行う。mitmproxy でその内容が確認できれば成功&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;詳細は &lt;a href=&#34;https://please-sleep.cou929.nu/decrypting-tls-traffic-packet-capture.html&#34;&gt;TLS 通信のパケットキャプチャ - Please Sleep&lt;/a&gt; に詳しい。&lt;/p&gt;&#xA;&#xA;&lt;h1&gt;参考&lt;/h1&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://docs.mitmproxy.org/stable/concepts-certificates/&#34;&gt;Certificates - mitmproxy docs&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://cheatsheetseries.owasp.org/cheatsheets/Pinning_Cheat_Sheet.html&#34;&gt;Pinning - OWASP Cheat Sheet Series&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/news/?id=g9ejcf8y&#34;&gt;Identity Pinning: How to configure server certificates for your app - Discover - Apple Developer&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h1&gt;PR&lt;/h1&gt;&#xA;&#xA;&lt;div class=&#34;amazlet-box&#34; style=&#34;margin-bottom:0px;&#34;&gt;&lt;div class=&#34;amazlet-image&#34; style=&#34;float:left;margin:0px 12px 1px 0px;&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/490868619X/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;&lt;img src=&#34;https://m.media-amazon.com/images/I/51DGE8zEr6L._SY425_.jpg&#34; alt=&#34;プロフェッショナルTLS＆PKI 改題第2版&#34; style=&#34;border: none; width: 113px;&#34; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-info&#34; style=&#34;line-height:120%; margin-bottom: 10px&#34;&gt;&lt;div class=&#34;amazlet-name&#34; style=&#34;margin-bottom:10px;line-height:120%&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/490868619X/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;プロフェッショナルTLS＆PKI 改題第2版&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-detail&#34;&gt;Ivan Ristić (著), 齋藤孝道 (監修)&lt;br/&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-sub-info&#34; style=&#34;float: left;&#34;&gt;&lt;div class=&#34;amazlet-link&#34; style=&#34;margin-top: 5px&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/490868619X/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Amazon.co.jpで詳細を見る&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-footer&#34; style=&#34;clear: left&#34;&gt;&lt;/div&gt;&lt;/div&gt;&#xA;</description>
      <author>Kosei Moriyama</author>
      <pubDate>Thu, 16 Jan 2025 20:00:00 +0900</pubDate>
    </item>
    <item>
      <title>読書メモ: Prometheus 実践ガイド</title>
      <link>https://please-sleep.cou929.nu/practical-prometheus-book.html</link>
      <description>&lt;div class=&#34;amazlet-box&#34; style=&#34;margin-bottom:0px;&#34;&gt;&lt;div class=&#34;amazlet-image&#34; style=&#34;float:left;margin:0px 12px 1px 0px;&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4910313001/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;&lt;img src=&#34;https://m.media-amazon.com/images/I/9165fv-dB5L._SY466_.jpg&#34; alt=&#34;Prometheus実践ガイド: クラウドネイティブな監視システムの構築&#34; style=&#34;border: none; width: 113px;&#34; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-info&#34; style=&#34;line-height:120%; margin-bottom: 10px&#34;&gt;&lt;div class=&#34;amazlet-name&#34; style=&#34;margin-bottom:10px;line-height:120%&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4910313001/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Prometheus実践ガイド: クラウドネイティブな監視システムの構築&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-detail&#34;&gt;仲亀 拓馬 (著)&lt;br/&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-sub-info&#34; style=&#34;float: left;&#34;&gt;&lt;div class=&#34;amazlet-link&#34; style=&#34;margin-top: 5px&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4910313001/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Amazon.co.jpで詳細を見る&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-footer&#34; style=&#34;clear: left&#34;&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;会社でも利用している Prometheus だが、なんとなくでしか使えていなかったのでキャッチアップのために読んだ。その目的なのでハンズオンの部分は割愛し、全体は 400 ページほどあるがさっと読むことができた。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Prometheus のアーキテクチャや PromQL の基礎が把握できて満足。前者は、Grafana の存在で可視化部分が疎になっているのはわかっていたが、Alert 部分も分かれていることや、リモートストレージを介して別 Prometheus や外部サービスと連動させやすいのは新鮮だった。後者について、もともと時系列データへのクエリは苦手だったが、基礎的なプリミティブ (Guage/Counter/Histogram/Summary の区分、label の概念、Instant/Range Vector) を押さえることで理解しやすくなったと思う。また PromQL に限らず抽象化すれば Cloud Monitoring など他製品とも共通の考え方なので、その点も有用だった。さわりだけだが TSDB のファイル構成にも触れられていて、シンプルながらパワフルな印象を持ち、面白そうだった。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;以下読書メモ。&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;Part1：Prometheusと監視の基本&lt;/h2&gt;&#xA;&#xA;&lt;h3&gt;Chapter 1：Prometheusで監視を始めるには&lt;/h3&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;システム監視の基本&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;目的: サービスをユーザーに無事に届けるため = ダウンタイムの最小化&lt;/li&gt;&#xA;&lt;li&gt;方式&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;メトリクス&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;システムの状態を数値化し時系列データとして扱う&lt;/li&gt;&#xA;&lt;li&gt;Prometheus, Zabbix, CloudWatch など&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ログ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;システムの状態を文字列として記録する&lt;/li&gt;&#xA;&lt;li&gt;集計・比較しやすいメトリクスに対して、ログは詳細な情報が得られるがストレージ消費は多い&lt;/li&gt;&#xA;&lt;li&gt;Elasticsearch, Cloud Logging など&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;トレーシング (分散トレーシング)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ユーザーのリクエストにスコープを置き、各処理経路の所要時間を記録する&lt;/li&gt;&#xA;&lt;li&gt;複数のコンポーネントをまたがるため、トレーシング ID という共通の ID で追跡する&lt;/li&gt;&#xA;&lt;li&gt;OpenTelemetry はその標準&lt;/li&gt;&#xA;&lt;li&gt;Jaeger, Zipkin, Datadog APM など&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;監視の導入フロー&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;サービスの正常・異常を定義&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;i.e. コーポレートサイトがブラウザからアクセスできる、商品をカートにいれることができる、アップロードしたファイルが正常に転送される&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ユーザー側から導入する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;コンポーネントの CPU やメモリ使用率からではなく、「そのサービスが使えているのか」から始める&lt;/li&gt;&#xA;&lt;li&gt;ブラックボックス監視からホワイトボックス監視の順&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;アラートを設定する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;発報時にどんなアクションをするかを決め、通知レベルを分ける&lt;/li&gt;&#xA;&lt;li&gt;継続的に調整、不要なアラートの削除をする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;監視システム自体も監視する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;オブザーバビリティ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;クラウドネイティブな環境では、多数のコンポーネントが協調するため、問題の発生パターンを事前に予測するのが従来よりも難しい&lt;/li&gt;&#xA;&lt;li&gt;そのため各コンポーネントの状況が統一的な方法で記録され、それを事後に自由に取得できることが求められる&lt;/li&gt;&#xA;&lt;li&gt;こうした概念は Obserbability と名付けられている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;Chapter 2：Prometheusの概要と基本的な使い方&lt;/h3&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Prometheus とは&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;CNCF Graduated Project&lt;/li&gt;&#xA;&lt;li&gt;すべてのデータを key-value のメトリクスとして扱う&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;key はメトリクス名 + ラベル (&lt;code&gt;prometheus_build_info {instance=&amp;quot;localhost:9090&amp;quot;&#xA;}&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;PromQL&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;メトリクスのクエリ言語&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;エクスポーター&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;監視システム側に従属する統一的なクライアント (エージェント) ではなく、ミドルウェアや OS といった各監視対象ごとに従属するクライアント (エクスポーター) という考え方&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Pull 型モデルとサービスディスカバリ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;監視サーバが各エクスポーターにメトリクスを問い合わせる Pull 型モデル&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;監視対象を中央で一元管理できる。例えば監視対象がメトリクスを送れなくなったという検知や、スケールインでなくなったノードのデータ破棄などが容易になる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;伸縮するクラウドネイティブなインフラでは監視対象が動的に変わるため、サービスディスカバリが必要&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;アーキテクチャ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;figure&gt;&lt;img src=images/31FDD39B-053A-48C6-AA4E-E86F09CBB213.jpeg /&gt;&lt;figcaption&gt;図2.5 Prometheus のアーキテクチャ より引用&lt;/figcaption&gt;&lt;/figure&gt;&lt;/li&gt;&#xA;&lt;li&gt;Prometheus Server + TSDB&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;メトリクスの収集、保存、クエリを担当&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Exporter&lt;/li&gt;&#xA;&lt;li&gt;AlertManager&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Prometheus Server がアラートルールに則った PromQL を定期実行し、マッチするものがあれば AlertManager に送信する&lt;/li&gt;&#xA;&lt;li&gt;AlertManager はアラートのグルーピング、ラベル情報をもとにルーティングしたうえで通知を行う&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Grafana&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ビジュアライズ&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Pushgateway&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Push 型のメトリクス収集をカバーする。バッチ処理やイベントのような一時的な処理のメトリクス収集がユースケースになる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;不得意なこと&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;データの信頼性&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Pull したデータが正常かどうかを検知する仕組みは無い。例えばネットワークの問題でデータが欠損しても再取得する機能はない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;エクスポーターの管理コスト&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;種類が多く、コミュニティ、極端には個人がメンテナンスするものもある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;構成ファイル&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;prometheus.yml&lt;/code&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;global&lt;/code&gt; でグローバル設定&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;scrape_interval&lt;/code&gt; 監視間隔&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;scrape_timeout&lt;/code&gt; 監視タイムアウト&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;evaluation_interval&lt;/code&gt; アラートルールの評価間隔&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;評価時にはクエリを実行するため大量のルールがある場合は負荷がかかる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;external_labels&lt;/code&gt; 全体へのラベルを追加&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;env: production&lt;/code&gt; を全メトリクスにつけるといったユースケース&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;その他リモートストレージの設定など&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;scrape_configs&lt;/code&gt; で監視対象を定義&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;job_name&lt;/code&gt; 監視対象の名前。監視対象ごとに Job という単位で管理&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;metrics_path&lt;/code&gt; エクスポーターのエンドポイント。デフォルトは &lt;code&gt;/metrics&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;scheme&lt;/code&gt; http か https&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;params&lt;/code&gt; クエリパラメータ&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;basic_auth&lt;/code&gt; ベーシック認証&lt;/li&gt;&#xA;&lt;li&gt;interval, timeout はグローバルの設定を上書きできる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;alerting&lt;/code&gt; でアラートルールを定義&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;alertmanagers&lt;/code&gt; AlertManager のエンドポイントを指定&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;静的・動的に設定できる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;alert_relabel_configs&lt;/code&gt; アラートルールのラベルを変更&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;rule_files&lt;/code&gt; で外部ファイルからアラートルールを読み込む&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Topics: Prometheus のメトリクスフォーマットは &lt;a href=&#34;https: //prometheus.io/docs/instrumenting/exposition_formats/&#34;&gt;Exposion Formats&lt;/a&gt; としてまとめられている&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;これも参考にしつつオープンな標準として &lt;a href=&#34;https: //openmetrics.io/&#34;&gt;OpenMetrics&lt;/a&gt; がある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;Chapter 3：監視ターゲットの検出とラベル操作&lt;/h3&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;サービスディスカバリーはクラウドインフラの API や k8s などに既にある外部のサービスディスカバリー機能を呼び出すのが基本&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;それがない場合はカスタマイズしたディスカバリーシステムが必要になる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Prometheus は監視対象の情報を label という形で保持している&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;監視対象のホストやパスなどもラベルとして表現されている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;外部のサービスディスカバリーサービスから取得した対象の一覧に対して、さまざまな加工を行う必要がある。この操作を relabel (再ラベル) という&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば gce のインスタンス情報を取得した場合、デフォルトでは ip としてプライベートアドレスが入っている。サブネット外の Prometheus から監視したい場合は別のフィールドにメタデータとして入っているパブリック ip を ip ラベルに置き換える&lt;/li&gt;&#xA;&lt;li&gt;そのほか、GCP など対象のサービス内のラベル表現を Prometheus 側の規則にそったラベルに置き換えたり、監視対象の絞り込みなども relabel の操作で実現する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;Chapter 4：エクスポーターによるデータの収集&lt;/h3&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;HTTP API のエンドポイントを公開し、Prometheus server からの Pull に応じて監視対象のメトリクスを取得、所定のフォーマットに変換して返す役割&lt;/li&gt;&#xA;&lt;li&gt;二つの動作モデル&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;デーモン&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;監視対象とは別プロセスのデーモンとして動作する。Node exporter などはこのモデル&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;プロキシ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;監視対象が HTTP でメトリクスを公開している場合、Exporter が Prometheus との間にプロキシとして立つ。Prometheus server からは監視対象の URL を添えて Exporter にリクエスト、Exporter はそこに問い合わせ、メトリクスを返す&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;監視対象は Exporter 側には保持していない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;メトリクスのフォーマット&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;コメントとメトリクスデータから構成される&lt;/li&gt;&#xA;&lt;li&gt;メトリクスデータはメトリクスの key と value&lt;/li&gt;&#xA;&lt;li&gt;コメントには人間向けの純粋なコメントの他に、計算機向きのメトリクスの型情報も含まれている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# HELP go_goroutine Number of goroutines that currently exist.&#xA;# TYPE go_goroutine gauge&#xA;go_goroutine 38&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;h3&gt;Chapter 5：PromQLとメトリクス&lt;/h3&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;メトリクスの種別&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;gauge: ある時点の値&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;counter: 累計値&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;histogram: 分布。各バケットの区切りが絶対値&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;&lt;code&gt;le&lt;/code&gt; (less or equal) でバケットの区切りが表現される&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;prometheus_http_request_duration_seconds_bucket{handler=&amp;quot;/&amp;quot;,le=&amp;quot;0.1&amp;quot;} 2&#xA;prometheus_http_request_duration_seconds_bucket{handler=&amp;quot;/&amp;quot;,le=&amp;quot;0.2&amp;quot;} 3&#xA;prometheus_http_request_duration_seconds_bucket{handler=&amp;quot;/&amp;quot;,le=&amp;quot;0.4&amp;quot;} 4&#xA;prometheus_http_request_duration_seconds_bucket{handler=&amp;quot;/&amp;quot;,le=&amp;quot;1.0&amp;quot;} 5&#xA;prometheus_http_request_duration_seconds_bucket{handler=&amp;quot;/&amp;quot;,le=&amp;quot;+Inf&amp;quot;} 5&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;summary: 分布。各バケットの区切りがパーセンタイル&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;prometheus_engine_query_duration_seconds{slice=&amp;quot;inner_eval&amp;quot;,quantile=&amp;quot;0.5&amp;quot;} 0.001530686&#xA;prometheus_engine_query_duration_seconds{slice=&amp;quot;inner_eval&amp;quot;,quantile=&amp;quot;0.9&amp;quot;} 0.001536674&#xA;prometheus_engine_query_duration_seconds{slice=&amp;quot;inner_eval&amp;quot;,quantile=&amp;quot;0.99&amp;quot;} 0.001536674&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;被演算子のデータ型&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;Instant vector:ある一時点のデータ&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;up{instance=&amp;quot;localhost:9090&amp;quot;,job=&amp;quot;prometheus} 1&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;時間の指定がなければ最新のメトリクスが選ばれる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Range vector: ある期間のすべてのデータ&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;データに加えてタイムスタンプを持つ&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;node_memory_MemFree_bytes{instance=&amp;quot;localhost:9100&amp;quot;,job=&amp;quot;node_exporter&amp;quot;}[5m]&#xA;28323840 @1596386196.235&#xA;28323848 @1596386201.235&#xA;28323932 @1596386206.235&#xA;28384833 @1596386211.235&#xA;28329991 @1596386216.235&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Scala&lt;/li&gt;&#xA;&lt;li&gt;単純な数値&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;セレクタ&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;&lt;code&gt;{job=&amp;quot;prometheus&amp;quot;}&lt;/code&gt; といった、メトリクスから条件に一致するものをフィルタするための指定&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;セレクタがない場合は全件取得になる&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# 全サーバのリクエスト数が返される&#xA;prometheus_http_requests_total&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;matcher&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;=&lt;/code&gt;, &lt;code&gt;!=&lt;/code&gt;, &lt;code&gt;=~&lt;/code&gt;, &lt;code&gt;!~&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;offset&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;offset 5m&lt;/code&gt; といった指定で、過去の時点データを取得する。範囲ではなく Instant vector を返す&lt;/li&gt;&#xA;&lt;li&gt;range vector selector&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;[5m]&lt;/code&gt; といった指定で、過去の範囲データ Range vencor を取得する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;演算子&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;算術演算子、比較二項演算子、論理演算子&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;集計演算&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;sum, min, max, avg, stddev, stdvar, count, count_values (count_if), bottomk, topk, quantile&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;grouping&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;by: 指定したラベルでグルーピングして集計する&lt;/li&gt;&#xA;&lt;li&gt;without: 除外するラベルを指定し、残りのラベルすべてでグルーピングして集計する&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# job ラベルごとの合計リクエスト数の集計例&#xA;sum(prometheus_http_requests_total) by (job)&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;関数&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;rate, delta, sort など多くの種類がある&#xA;&lt;code&gt;&#xA;rate(node_cpu_seconds_total[&#xA;5m&#xA;])&#xA;&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;Chapter 6：ルールとアラート&lt;/h3&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;rule&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Prometheus server 側でクエリを定期実行する仕組み&lt;/li&gt;&#xA;&lt;li&gt;定期実行のみを行う recording_rule&lt;/li&gt;&#xA;&lt;li&gt;アラートに対応する alert_rule&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;promql と条件式、インターバル、付与するラベルなどを定義する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;groups:&#xA;  - name: example-alerts&#xA;    rules:&#xA;    - alert: HighMemoryUsage&#xA;      expr: node_memory_Active_bytes / node_memory_MemTotal_bytes * 100 &amp;gt; 80&#xA;      for: 5m&#xA;      labels:&#xA;        severity: critical&#xA;      annotations:&#xA;        summary: &amp;quot;High memory usage detected on instance {{ $labels.instance }}&amp;quot;&#xA;        description: &amp;quot;Memory usage is above 80% for more than 5 minutes.\n  VALUE = {{ $value }}\n  LABELS = {{ $labels }}&amp;quot;&#xA;&#xA;    - alert: HighCPUUsage&#xA;      expr: sum(rate(node_cpu_seconds_total{mode=&amp;quot;system&amp;quot;}[1m])) by (instance) / count(node_cpu_seconds_total{mode=&amp;quot;system&amp;quot;}[1m]) by (instance) * 100 &amp;gt; 90&#xA;      for: 10m&#xA;      labels:&#xA;        severity: warning&#xA;      annotations:&#xA;        summary: &amp;quot;High CPU usage detected on instance {{ $labels.instance }}&amp;quot;&#xA;        description: &amp;quot;CPU usage is above 90% for more than 10 minutes.\n  VALUE = {{ $value }}\n  LABELS = {{ $labels }}&amp;quot;&#xA;&#xA;    - alert: InstanceDown&#xA;      expr: up == 0&#xA;      for: 1m&#xA;      labels:&#xA;        severity: critical&#xA;      annotations:&#xA;        summary: &amp;quot;Instance down alert&amp;quot;&#xA;        description: &amp;quot;Instance {{ $labels.instance }} is down for more than 1 minute.&amp;quot;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;AlertManager&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;通知先を表現する receiver&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;チーム別、レベル別などに分けるユースケース&lt;/li&gt;&#xA;&lt;li&gt;指定したラベルにマッチすると送信する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;アラートをまとめる router&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;待ち時間やインターバルと共にグループ化するラベルを指定する&lt;/li&gt;&#xA;&lt;li&gt;期間内のアラートを一つの通知にまとめられる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;Chapter 7：メトリクスを可視化する&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;Grafana の使い方の解説。一般的なグラフツールやクラウドインフラのモニタリングツールと概念はそう変わらないので割愛。&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;Part2：Prometheusの実践&lt;/h2&gt;&#xA;&#xA;&lt;h3&gt;Chapter 8：Kubernetesの監視&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;Kubernetes クラスタの監視や Prometheus operator について。必要な時に最新のチュートリアルを参照した方が良いので割愛。&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;Chapter 9：Prometheusのストレージ&lt;/h3&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;ローカルストレージとリモートストレージ&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Prometheus server 足下のストレージ TSDB の形式でファイルを保存するローカル形式と、リモートの外部ストレージに連携する形式&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;TSDB&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;データ形式&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;時系列データを保持&lt;/li&gt;&#xA;&lt;li&gt;一つのデータはサンプルデータと呼ばれる&lt;/li&gt;&#xA;&lt;li&gt;2時間分ごとにブロックが分かれている&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;チャンク内にはデータ本体と、そのブロックの期間等のメタデータ、インデックス&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;クエリの際は、メタデータからブロックを、インデックスから対象のファイルを探してスキャンする&lt;/li&gt;&#xA;&lt;li&gt;障害時の復旧のための WAL&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ヘッドブロックのデータはメモリ上に保持しているので、障害時は WAL から復旧する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;一定以上古い期間のファイルはひとまとめにしていく&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;./data&#xA;    /01F01F5A3…  # 2時間分ごとのブロック&#xA;        /chunk&#xA;            00001&#xA;            …&#xA;        index&#xA;        meta.json&#xA;        tombstones&#xA;    /01F5AAB8V…&#xA;    /01F5AAB8X…&#xA;    /chunks_head  # 現在書き込み中のブロック（ヘッドブロック）&#xA;    /lock&#xA;    /queries.active&#xA;    /wal&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;データ量の見積もり&lt;/li&gt;&#xA;&lt;li&gt;1 メトリクスあたりのデータサイズ x 秒間の取得メトリクス数 x 保持期間 = TSDB のデータサイズ という要領で見積もる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;1 メトリクスあたりのデータサイズは 1,&#xA;2 byte という経験則で良い。残り二つの項目は設定から算出できる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;リモートストレージ&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;さまざまな外部サービスと接続できる&lt;/li&gt;&#xA;&lt;li&gt;read/write または write only の接続方式がある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Prometheus はメトリクス収集に特化し、ストレージには書き込みのみ、可視化やアラートは外部システム側に任せる（あるいは別の Prometheus Server instance）という構成も可能になる&lt;/li&gt;&#xA;&lt;li&gt;read もする場合、通常は外部ストレージから一定期間のデータを全権取得し、Prometheus 側で PromQL で集計するという構成になる。Prometheus 側のメモリ使用量には注意が必要&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;この意味でも可視化などを外部サービス側に任せる構成があり得てくる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;write にはバッファの大きさや並列数といったチューニングポイントがある。バッファが消費するメモリサイズや書き込みのレイテンシを鑑みながら調整する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;Chapter 10：独自のメトリクスを公開する&lt;/h3&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;方式&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Direct instrument&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;監視対象に Prometheus のインタフェースに従ったメトリクス取得のエンドポイントを直接実装する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Exporter&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Exporter を実装する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;実装時にはラベルのカーディナリティの低いメトリクスを作らないように注意する（本当に必要か検討する）&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えばメールアドレスごとのラベルなど&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;Chapter 11：実践的なPrometheusの活用&lt;/h3&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;冗長化&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;複数の Prometheus server を起動するのがシンプル&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;それぞれでストレージを別で持つ&lt;/li&gt;&#xA;&lt;li&gt;スクレイプ数やデータ量がサーバ数分増えてしまうが、シンプルなのがメリット&lt;/li&gt;&#xA;&lt;li&gt;お互いを監視させると良い&lt;/li&gt;&#xA;&lt;li&gt;Prometheus には冗長化のためのクラスタ化機能などはない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;AlertManager はクラスタ機能がある。複数起動し協調動作する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;複数の Prometheus の連携&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;小型のサーバを複数展開するほうが運用しやすいことが多い&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;サービスごとや環境ごとに別のサーバをたてる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;並列の連携&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;複数の Prometheus が同一のリモートストレージに書き込む&lt;/li&gt;&#xA;&lt;li&gt;リモートストレージを参照しに Garafana などで可視化する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ツリー型の連携&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えばデータセンター全体の監視で、フロアごとサーバのメトリクスを中央のサーバに集約する構成&lt;/li&gt;&#xA;&lt;li&gt;これをサポートする機能は Prometheus が提供している&lt;/li&gt;&#xA;&lt;li&gt;中央（ツリーの上位）のサーバから Pull する federation と、下位のサーバから Push する Remote write receiver がある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;別システムへの連携&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Managed Prometheus や Cloud Monitoring などへ転送する&lt;/li&gt;&#xA;&lt;li&gt;一台であってもあり得る構成&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;配置場所&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;本番環境と同じ場所に構築する場合&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;コスト削減、経路上のネットワークの影響が少ない、k8s operator を使えるといったメリットがある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;分けるのは障害時の影響を分離でき、一般的には良い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;メトリクス命名のコツ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;上位のコンポーネントから順に並べる (nginx_system_memory_… など)&lt;/li&gt;&#xA;&lt;li&gt;メトリクス名は抽象化し、具体性の高い情報はラベルにする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;Chapter 12：Prometheusの周辺ツール&lt;/h3&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;PushGateway&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;バッチなど短命なジョブのメトリクス収集&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;VictoriaMetrics&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Prometheus 専用に作られたリモートストレージ&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Prometheus Loki&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Prometheus と親和性の高いログツール&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;div class=&#34;amazlet-box&#34; style=&#34;margin-bottom:0px;&#34;&gt;&lt;div class=&#34;amazlet-image&#34; style=&#34;float:left;margin:0px 12px 1px 0px;&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4910313001/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;&lt;img src=&#34;https://m.media-amazon.com/images/I/9165fv-dB5L._SY466_.jpg&#34; alt=&#34;Prometheus実践ガイド: クラウドネイティブな監視システムの構築&#34; style=&#34;border: none; width: 113px;&#34; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-info&#34; style=&#34;line-height:120%; margin-bottom: 10px&#34;&gt;&lt;div class=&#34;amazlet-name&#34; style=&#34;margin-bottom:10px;line-height:120%&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4910313001/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Prometheus実践ガイド: クラウドネイティブな監視システムの構築&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-detail&#34;&gt;仲亀 拓馬 (著)&lt;br/&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-sub-info&#34; style=&#34;float: left;&#34;&gt;&lt;div class=&#34;amazlet-link&#34; style=&#34;margin-top: 5px&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4910313001/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Amazon.co.jpで詳細を見る&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-footer&#34; style=&#34;clear: left&#34;&gt;&lt;/div&gt;&lt;/div&gt;&#xA;</description>
      <author>Kosei Moriyama</author>
      <pubDate>Mon, 17 Jun 2024 09:00:00 +0900</pubDate>
    </item>
    <item>
      <title>読書メモ: Stream Processing with Apache Flink</title>
      <link>https://please-sleep.cou929.nu/stream-processing-with-apache-flink-book.html</link>
      <description>&lt;div class=&#34;amazlet-box&#34; style=&#34;margin-bottom:0px;&#34;&gt;&lt;div class=&#34;amazlet-image&#34; style=&#34;float:left;margin:0px 12px 1px 0px;&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/149197429X/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;&lt;img src=&#34;https://m.media-amazon.com/images/I/912jjckPLoL._SY466_.jpg&#34; alt=&#34;Stream Processing with Apache Flink: Fundamentals, Implementation, and Operation of Streaming Applications&#34; style=&#34;border: none; width: 113px;&#34; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-info&#34; style=&#34;line-height:120%; margin-bottom: 10px&#34;&gt;&lt;div class=&#34;amazlet-name&#34; style=&#34;margin-bottom:10px;line-height:120%&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/149197429X/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Stream Processing with Apache Flink: Fundamentals, Implementation, and Operation of Streaming Applications&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-detail&#34;&gt;英語版  Fabian Hueske (著), Vasiliki Kalavri (著)&lt;br/&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-sub-info&#34; style=&#34;float: left;&#34;&gt;&lt;div class=&#34;amazlet-link&#34; style=&#34;margin-top: 5px&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/149197429X/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Amazon.co.jpで詳細を見る&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-footer&#34; style=&#34;clear: left&#34;&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;会社で運用し始めたので、キャッチアップのために読んだ。そしてその目的にはぴったりの本だった。Apache Flink の専門書ではあるが、ストリーミング処理やその周辺のログ基盤技術についてあまり馴染みのない・片手間で対応してきたが体型的には把握できていないエンジニアが素早くキャッチアップするのに、コンパクトにまとまっていることもあり最適だった。一方で全体的に深入りはしていないので、すでにこの分野や Flink そのものに習熟しているエンジニアには物足りないかもしれない。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;1 から 3 章は Flink に限らないストリーミング処理一般の概念や設計思想の説明で、特に興味深かった。解決したい要件に対して、既存のアプローチ（バッチ中心の構成やラムダアーキテクチャなど）との比較から始まり、ストリーミング処理独自のチャレンジ、トレードオフの紹介などがまとまっている。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;また例えば watermark の仕組みが興味深い。分散システムの一般的な特性だが、ネットワーク越しの外部からのデータが確実にタイムリーに到着するとは仮定できないため、できるだけデータの到着を長く待った方がより正確な計算ができる。一方で無限に待つことはできず、あまりに長く待つとレイテンシが悪化するという、正確性とレイテンシのトレードオフの関係がある。これを調停するのが watermark という仕組みで、その時点でデータは確定しているとみなす閾値、= どの程度待つか、を定めている。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;他には、例えば障害時に計算途中から処理を再開するための checkpoint という中間データを永続化する仕組みがあるが、全体のレイテンシと障害時の復旧速度はトレードオフの関係にあり、（他にもチューニングポイントはいくつかあるが） checkpoint 取得頻度というパラメータでそれを調整できるようになっている。また連鎖するオペレーターで上流から checkpoint を取得するためのマーク (checkpoint barrier) を流すことで全体の整合性が取られるというアプローチも綺麗だ。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;4 章以降は Flink の具体的なセットアップやアプリケーションの実装の説明となり、基本的には流し読み、必要になってから参照しに戻れるよう頭にインデックスを作るイメージで読んだ。次は実際に手を動かしたり、運用している仕組みの実態を把握していったりしたいと思う。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;以降は読書メモ。&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;1. Introduction to Stateful Stream Processing&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;A Distributed stream processor&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Stateful Stream Processing Application を実装できる&lt;/li&gt;&#xA;&lt;li&gt;2014 に Apache の incubating project 入り&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;従来型のデータ基盤&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;従来のデータ基盤は Transactional processing と Analytical processing に分かれている考え方だった&lt;/li&gt;&#xA;&lt;li&gt;Transactional&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;いわゆる OLTP の DBMS に各アプリケーションがアクセスする形式&lt;/li&gt;&#xA;&lt;li&gt;データ量が増加した場合の最近のアプローチはマイクロサービスで、分割することでデータストア1つあたりの規模を抑える&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Analytical&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Transactional DBMS 上のデータは通常 (マイクロサービス構成では特に) 分散しているので、それを一箇所のデータウェアハウスに集約する&lt;/li&gt;&#xA;&lt;li&gt;この集約処理は ETL と呼ばれる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;データの抽出、変換、バリデーション、正規化、エンコーディング、重複排除などを行う&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;一般的に ETL はバッチ処理で、ビジネス要件に応じて実行頻度を高めるには相応のコストや技術が必要とされる&lt;/li&gt;&#xA;&lt;li&gt;データウェアハウスにインポートされたデータの利用方法は、定期実行クエリとアドホッククエリに分類できる&lt;/li&gt;&#xA;&lt;li&gt;今日ではデータストアとしては HDFS、S3、HBase などが、その上で動く処理エンジンとして Hive, Drill, Impalla といった SQL-on-Hadoop エンジンが使われることが多い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Stateful Stream Processing&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;すべてのデータは「継続するイベントのストリーム」と抽象化することができる。Stateful Stream Processing はこのイベントストリームを処理し多様なユースケースに対応できるデザインパターン&lt;/li&gt;&#xA;&lt;li&gt;大まかな仕組み&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;イベントストリームは単にその時点のレコードだけを処理するのではなく、なんらかの状態 (中間データ) を参照する必要がある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;各所からアクセスできるデータストアに中間データを保持し、アプリケーションがイベントを受け取った際にそのデータを読み書きする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Flink は状態をローカルのメモリに保持する。また Flink は分散システムなので、状態を定期的にチェックポイントとして外部ストレージに冗長化し、障害に備えている&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;figure&gt;&lt;img src=images/stream-processing-flink-fig-1-4.png /&gt;&lt;figcaption&gt;Figure 1-4. A stateful streaming application より引用&lt;/figcaption&gt;&lt;/figure&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;イベントの受け取りにはよく Kafka 等の Event log system が使われる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;イベントストリームを追記のみで immutable な Event Log として扱う&lt;/li&gt;&#xA;&lt;li&gt;この特性は過去の特定の時点からの「リプレイ」を可能にするので、耐障害性が高い。またバグ修正、マイグレーション、AB テストも容易になる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;代表的なユースケース&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Event-Driven Applications&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;イベントのストリームをトリガーに動作するアプリケーション。一つのアプリケーションは別のアプリケーションが発したイベントを Consume する (= Event-Driven アプリケーションが数珠繫ぎになっている) 構成もある&lt;/li&gt;&#xA;&lt;li&gt;Real-time recommendation, Pattern detection (Fraund detection), Anomaly detection などが代表的なユースケース&lt;/li&gt;&#xA;&lt;li&gt;Transaction なシステム、またはマイクロサービス、と比較すると、通信手段が REST API から Event-log に、データストアが RDB からローカルのステートに変わっている&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;状態をより近くに持っているのでパフォーマンスに有利で、またスケールや耐障害性の担保を Stream Processor 側に任せることができるのがメリット&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Data Pipelines&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一般的にシステム全体の中で、パフォーマンス要件のためデータはいろいろなデータストアに分散する&lt;/li&gt;&#xA;&lt;li&gt;これらのデータストア間の同期は、ナイーブには定期的な ETL で行われるが、レイテンシが要件に合わなくなることも多い&lt;/li&gt;&#xA;&lt;li&gt;その場合 Stream Processor がその同期を担うことでレイテンシを大幅に下げられる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;データソースの変更をイベントとして受け取り、Consumer のデータストアに (必要に応じて正規化などを行った上で) 挿入する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Stream Processor には多様なデータ Source, Sink への対応が求められる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Streaming Analytics&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;従来のデータ分析は定期的な ETL、数時間から数日のレイテンシ、に頼っていたが、Stream Processor に置き換えることでほぼリアルタイムの分析が可能になる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えばモバイルネットワーク回線品質のモニタリング、モバイルアプリのユーザー行動分析、消費者のリアルタイムデータのアドホック分析などが可能になる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;隠れたメリットとしては、従来構成では複数のコンポーネント (ETL プロセス、データストア、データプロセッサー、スケジューラーなど) が必要だったが、Stream Processor によって一つのコンポーネントで済むようになる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Stream Processor の歴史&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;第1世代 (Lambda Architecture) (2011 年頃)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Stream Processor はレイテンシの改善 (milliseconds レベル) を主目的として、反面正確性と一貫性は保障していなかった。またイベントは more-than-once で処理されていた&lt;/li&gt;&#xA;&lt;li&gt;Lambda Architecture は従来の Batch Layer (正確だが高レイテンシ) と Speed Layer (不正確だが低レイテンシ) を並列させるというもの&lt;/li&gt;&#xA;&lt;li&gt;主なデメリット&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;意味的に同じ処理を別の API 向けに 2 回実装する必要がある&lt;/li&gt;&#xA;&lt;li&gt;Speed Layer の結果が概算値のみ&lt;/li&gt;&#xA;&lt;li&gt;セットアップとメンテナンスが複雑&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;第2世代 (2013 年頃)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;対障害性と exactly-once を保障し、よりシンプルな API を提供するが、レイテンシは秒レベルまで落ちてしまった&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;第3世代 (2015 年頃)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;イベントが届いたタイミングと順序に依存する問題を解決し、またスループットとレイテンシを両立させるようになった&lt;/li&gt;&#xA;&lt;li&gt;この時点で Lambda Architecture は不要になり Stream Processor に 1 本化できるようになった&lt;/li&gt;&#xA;&lt;li&gt;Flink は第 3 世代のプロダクト&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;2. Stream Processing Fundamentals&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Introduction of Dataflow Programming&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;有向グラフで表現される処理の流れ&lt;/li&gt;&#xA;&lt;li&gt;ノードがオペレータでエッジがデータの依存関係を表し、一つ以上の source と sink がある&lt;/li&gt;&#xA;&lt;li&gt;ハッシュタグをカウントする例。これはハイレベルな処理の抽象化なので「ロジカル」と呼ばれる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;figure&gt;&lt;img src=images/stream-processing-flink-fig-2-1.png /&gt;&lt;figcaption&gt;Figure 2-1. A logical dataflow graph to continuously count hashtags より引用&lt;/figcaption&gt;&lt;/figure&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;一方で実際に各ノードで処理されるグラフは「フィジカル」と呼ばれる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;figure&gt;&lt;img src=images/stream-processing-flink-fig-2-2.png /&gt;&lt;figcaption&gt;Figure 2-2. A physical dataflow plan for counting hashtags より引用&lt;/figcaption&gt;&lt;/figure&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Data Parallelism と Task Parallelism&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;前者はデータを分割し、同じ処理を異なるデータのサブセットに対して繰り返すこと&lt;/li&gt;&#xA;&lt;li&gt;後者は異なる処理を並列に動かすこと&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Data exchange strategy&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Forward&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;単純に次のタスクに全データを渡す。同じ何度ならネットワークコストがかからない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Broadcast&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;次の並列タスク全てに全データを渡す。コストが高い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Key based&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;キーに合致するものは必ず同じタスクに処理されるよう保障&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Random&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ランダムに均等に分散させる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ストリームの並列処理&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;Data stream&lt;/code&gt;: 区切りのないイベントのシーケンス&lt;/li&gt;&#xA;&lt;li&gt;Latency と Thoughtput&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Latency&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;イベントが登録されてから最終成果が出来上がるまで&lt;/li&gt;&#xA;&lt;li&gt;低 Latency が Stream Processor の最大の特徴であり従来のバッチ処理との大きな違い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Throughout&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;単位時間あたりの処理量&lt;/li&gt;&#xA;&lt;li&gt;処理できる最大スループットを超えた入力がある状況を backpressure と呼ぶ&lt;/li&gt;&#xA;&lt;li&gt;レイテンシが下がるとスループットは上がる関係にある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Operations&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ステートレスなオペレーションはそれぞれ独立なので並列化や障害時のリトライがしやすいが、ステートフルな場合は簡単ではない。以下でケースごとに詳細を見ていく&lt;/li&gt;&#xA;&lt;li&gt;データの挿入 source と送出 sink (ステートレス)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;多様なフォーマットへの対応する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Transformation operations (ステートレス)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;入力を変換し出力する単純な操作&lt;/li&gt;&#xA;&lt;li&gt;複数の入出力先に対応する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Rolling aggregations (ステートフル)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;sum, min, max といった集約を行う操作で、ステートフル&lt;/li&gt;&#xA;&lt;li&gt;集約関数は結合的かつ可換的である必要がある。そうでなければすべてのデータ履歴を保持する必要があるので&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Window operations (ステートフル)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;transformation や aggrigation は単一のイベントに対する処理だが、複数のデータに対する処理も必要&lt;/li&gt;&#xA;&lt;li&gt;Window は無限のストリームから有限のデータセットを生成する&lt;/li&gt;&#xA;&lt;li&gt;1 つの bucket にどのようにデータを入れるかを定める window policy と、どのタイミングで bucket のデータを評価 (計算) するかの trigger policy がある&lt;/li&gt;&#xA;&lt;li&gt;Tumbling window&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一定のイベント (カウント、時間ごと等) をオーバーラップしないで bucket に入れる&lt;/li&gt;&#xA;&lt;li&gt;bucket がいっぱいになった時点 (指定したカウントに達したか、指定した時間に達した時点) で計算を行う&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Sliding window&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一定のイベントをオーバーラップさせて bucket に入れる&lt;/li&gt;&#xA;&lt;li&gt;Length (1 bucket あたりのイベント数) と Slide (次の bucket を作成するタイミング) を指定する&lt;/li&gt;&#xA;&lt;li&gt;&lt;figure&gt;&lt;img src=images/stream-processing-flink-fig-2-8.png /&gt;&lt;figcaption&gt;Figure 2-8. Sliding count-based window with a length of four events and a slide of three events より引用&lt;/figcaption&gt;&lt;/figure&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Session window&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;イベントの間隔が一定以上空いた場合に bucket を閉じる。この間隔を session gap と呼ぶ&lt;/li&gt;&#xA;&lt;li&gt;ユーザー行動分析等現実のユースケースに対して有用&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Parallel windows&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えばデバイス ID でイベントを分けてから、それぞれに対して並列に独立して Window 操作を行う&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Time semantics&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;正しい順序ではなく届いたイベントの処理や、過去データのリプレイの処理に必要な概念&lt;/li&gt;&#xA;&lt;li&gt;例えば地下鉄乗車中のゲームプレイ。ネットワークが不通の区間に入るとイベントがローカルにバッファリングされ、復旧した際に一括送信される。このとき、イベントを受け取った時刻 (Processing Time) で処理するよりも、イベントの実際の時刻 (Event Time) で処理できたほうがよい&lt;/li&gt;&#xA;&lt;li&gt;Event Time をもとにした処理で、ネットワーク品質に依存せず、決定論的にストリームを処理できる。この性質は過去データのリプレイにも有効&lt;/li&gt;&#xA;&lt;li&gt;遅延して届くイベントをどれだけ待つかは、確率論的で簡単に決めることはできない。その調整弁として Watermarks がある。Watermarks は仮にすべてのイベントを処理しきった時刻を表す。短くするとレイテンシは下がるが誤差が増えるというトレードオフがある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;アプリケーションによっては Watermarks を超えて遅延したイベントを適切に処理する必要がある (ログに残す、再計算するなど)&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;正確な結果が不要な場合などは Processing Time を使うこともある。Event Time に比べてレイテンシが低い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;State and Consistency Models&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;状態管理の難しさ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;State management&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;複数のオペレーターからの並行したアクセスの制御も必要&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;State partitioning&lt;/li&gt;&#xA;&lt;li&gt;State recovery&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;最も大きなチャレンジ&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Task failures&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;イベントを受診してローカルバッファに入れる、イベントを読み取り内部状態を更新する、計算結果を送信する。どの時点での障害からも、一貫した内部状態への復旧を保証したい&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;従来のバッチ構成だと毎実行がステートレスなのでこの問題は無い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ここでは内部状態の一貫性の保証について扱う。出力の一貫性は扱わない。sink 先がトランザクションに対応しているかどうか次第なので。&lt;/li&gt;&#xA;&lt;li&gt;保証の種類&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;At-most-once&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;no-guaranteeと同じ。レイテンシが重要で正確さは不要な場合には取りうるオプション&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;At-least-once&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;イベントのロストはなく、重複はある&lt;/li&gt;&#xA;&lt;li&gt;冗長化されたイベントログのリプレイや、イベント受信時の acknowledge (ack されて初めて送信側はそのイベントを discard できる）といった実現方法がある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Exactly-once&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;最も厳密で実現が難しい保証&lt;/li&gt;&#xA;&lt;li&gt;At-least-once 同様のリプレイと、その際にどこまでが内部状態に反映されたかの考慮が行われる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;transactional な状態更新がひとつのアプローチだが、パフォーマンスのオーバーヘッドが大きい&lt;/li&gt;&#xA;&lt;li&gt;他にはより軽量な sharpshooting の仕組みを使うアプローチがあり、Flink はこちらを採用している&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;End-to-end exactly-once&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;オペレーターごとでなく、パイプライン全体での保証&lt;/li&gt;&#xA;&lt;li&gt;一般的には個々のオペレーターの保証レベルより、全体の保証レベルは弱いものになる。ただし各オペレーターが冪等な場合、at-least-once 保証から exactly-once が実現できたりする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;3. The Architecture of Apache Flink&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;System architecture&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一般的な分散システムの難しさ - コンピューティング資源のアロケーションと管理、プロセス間の調停、冗長で高可用性のあるストレージ、障害復旧 - のうち一部は、k8s, HDFS や S3, ZooKeeper といった既存のソリューションに任せている&lt;/li&gt;&#xA;&lt;li&gt;コンポーネント&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;figure&gt;&lt;img src=images/stream-processing-flink-fig-3-1.png /&gt;&lt;figcaption&gt;Figure 3-1. Application submission and component interactions より引用&lt;/figcaption&gt;&lt;/figure&gt;&lt;/li&gt;&#xA;&lt;li&gt;JobManager&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;アプリケーションごとのマスタープロセス&lt;/li&gt;&#xA;&lt;li&gt;アプリケーションを JobGraph (logical dataflow graph) として受け取り、ExecutionGraph (physical) を組み立てる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;必要なクラスやライブラリなどがバンドルされた JAR も必要&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ResourceManager に必要な資源 (TaskManager slots) を要求し、満たされたらデプロイ&lt;/li&gt;&#xA;&lt;li&gt;アプリケーションの実行中は調停が必要なアクション（障害復旧のためのチェックポイント関連など）に責任を持つ&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ResourceManager&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;TaskManager slots という単位でリソースを管理&lt;/li&gt;&#xA;&lt;li&gt;バックエンドは k8s, YARN, Mesos, standalone cluster など&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;TaskManager&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;いわゆるワーカープロセス。ひとつの TaskManager に複数の slot がある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Dispatcher&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;アプリケーションを登録する REST API や Dashboard の提供&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Application deployment&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;複数のアプリケーションを受け付けることができる Framework style と、特定のアプリケーションごとバンドルされている Library style がある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Task execution&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一つの JobGraph を複数の TaskManager slot に並列に割り当てられる&lt;/li&gt;&#xA;&lt;li&gt;ひとつの slot には複数のオペレーターが入る。slot 間の通信が少ない方がパフォーマンスに有利&lt;/li&gt;&#xA;&lt;li&gt;各 slot はスレッドで実装されている。パフォーマンスとスレッド感の独立性（極端には TaskManager ごとに 1 slot にすると独立性は高まる）はトレードオフの関係にある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Highly available setup&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;特に JobManager の冗長化について&lt;/li&gt;&#xA;&lt;li&gt;JobManager は JobGraph や JAR などの必要情報を外部ストレージに、そのポインタやメタデータを Zookeeper に保存する&lt;/li&gt;&#xA;&lt;li&gt;再起動した際や待機系に切り替わった際は Zookeeper の情報を元にクラスタを復旧、再開する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Data transfer in Flink&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;TaskManager にはデータを受け取る Receiver と送る Sender があり、それぞれにはバッファがある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;それぞれ送受信信先の Receiver, Sender ごとにバッファがわかれている&lt;/li&gt;&#xA;&lt;li&gt;同じノードの TaskManager 同士はメモリを介してネットワークを経ずにデータをやり取りする&lt;/li&gt;&#xA;&lt;li&gt;ネットワークを介す場合、ノード間で永続的な TCP 接続が張られ、その接続内では各 TaskManager の通信が多重化されている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Credit-Based Flow Control&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;送信バッファと受信バッファの余裕に応じて適切な送信量を調整する仕組み&lt;/li&gt;&#xA;&lt;li&gt;receiver は sender に受入可能な量を credit として伝える&lt;/li&gt;&#xA;&lt;li&gt;sender は credit の範囲で必要なだけデータを送る。その際に自身の backlog (バッファ内にあり送信準備完了しているデータの量) も receiver に伝える&lt;/li&gt;&#xA;&lt;li&gt;receiver は sender の backlog も考慮して credit を調整する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Task Chaining&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;同じ並列性と local forward で繋がれているオペレータを一つのオペレータにまとめて、タスク授受のオーバーヘッドを減らす&lt;/li&gt;&#xA;&lt;li&gt;ただし 1 つのオペレーターの処理が重いなど、あえて chain しないほうが有利なケースもある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Event-time processing&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Event-time で動作させる場合、各データは必ず timestamp を持つ必要がある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;イベントが発生した時刻が一般的だが、アプリケーションによって定義は変わって良い。ストリームプロセッサーからすると、とにかくイベントはこの timestamp 順に並んでいるとみなして処理する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Watermarks は単調増加する event-time の時計として機能する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Watermark 以前の timestamp を持つイベントは Late records と呼ばれ、そのハンドリングは後述&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;オペレータは内部に Timer サービスを持っており、そこにコールバックを (例えば Window 処理で集計を行う処理のコールバックを) 登録する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Timer は Watermark を受け取り、それが登録されているコールバックの Event time より進んでいれば、そのコールバックを実行する。その後 Watermark を下流に投げる&lt;/li&gt;&#xA;&lt;li&gt;Watermak は上流のパーティション事に保持している。Timer 内部の Event-time はそれらの最小値を参照し、下流にもそれを Watermark として伝える&lt;/li&gt;&#xA;&lt;li&gt;&lt;figure&gt;&lt;img src=images/stream-processing-flink-fig-3-9.png /&gt;&lt;figcaption&gt;Figure 3-9. Updating the event time of task with watermarks より引用&lt;/figcaption&gt;&lt;/figure&gt;&lt;/li&gt;&#xA;&lt;li&gt;この設計では、一部の Watermark が著しく遅れたり idle 状態になるとそれ以降のオペレーターに悪影響が出る&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一定以上 idle なパーティションは Watermark のハンドリング時に除外するといった対策がされている。詳しくは後述&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Timestamp と Watermark は基本的にはアプリケーションの Srouce で生成され、下流のオペレーターへ流れていく&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;AssignerWithPeriodicWatermarks, AssignerWithPunctuatedWatermarks といったユーザーがカスタムできるインタフェースもある。その場合でも Source の近くでそれを行うことが推奨されている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;State management&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Flinkでは状態はオペレーターに紐づく&lt;/li&gt;&#xA;&lt;li&gt;Operator state&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ひとつのオペレーター単位のスコープのデータ&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Keyed state&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;input のキーによってアクセスできるデータ&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;state backend&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;メモリまたは RocksDB&lt;/li&gt;&#xA;&lt;li&gt;高速な前者に対して、大きいサイズにも対応できるのは後者&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;スケールイン、アウトの際は、データの種類に応じて、リパーテイション、コピーなどを行う&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Checkpoints, Savepoints, and state recovery&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Consistent checkpoints&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ある時点での内部状態を外部ストレージに永続化する&lt;/li&gt;&#xA;&lt;li&gt;障害時はアプリケーション全体をリセットし、最新のチェックポイントから状態を復旧、そのチェックポイントが取られた時点まで Source を巻き戻し resume する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;アプリケーションの各タスクから見るとこれで exactly-once が実現されている&lt;/li&gt;&#xA;&lt;li&gt;Source がリプレイに対応していないと実現できない&lt;/li&gt;&#xA;&lt;li&gt;Sink には障害復旧前後で同じデータが二度流されることになる。そのトランザクションや冪等性を持った実装などでハンドリングする必要がある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;チェックポイントの取得&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Stop-the-world: すべてのタスクを停止し、処理中のものがすべて完了するのを待ってからチェックポイントを取得。その後処理を再開する&lt;/li&gt;&#xA;&lt;li&gt;Chandy-Lamport algorithm: タスク実行を継続しながら取得できる。Flink はこれを採用している&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;checkpoint barrier という watermark のような印を JobManager source から sink に流す&lt;/li&gt;&#xA;&lt;li&gt;Source は barrier を受け取るとその時点の状態をチェックポイントに書き込む。また JobManager に Ack を送り、下流に barrier を流す&lt;/li&gt;&#xA;&lt;li&gt;各オペレータはすべての入力からの barrier が揃うまで待ち、揃ってからチェックポイントに書き込み、下流に barrier を流す。最初に Source A から barrier を受け取り、Source B の barrier を待っている間、Source A からの入力は処理せずバッファリングする&lt;/li&gt;&#xA;&lt;li&gt;Sink は同様に barrier を受け取り、チェックポイントを書き込んだ後 JobManager に Ack を伝える&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;レイテンシを下げるための工夫も行われている&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;チェックポイントバックエンドによっては、状態をローカルコピーしている間だけタスクを止めるが、コピーをリモートストレージに書き込む処理は非同期に行うものもある (FileSystem, RocksDB など)&lt;/li&gt;&#xA;&lt;li&gt;RocksDB は incremental checkpointing をサポートしている&lt;/li&gt;&#xA;&lt;li&gt;exactly-once ではなく at-least-once が許容できる場合、複数 source の barrier が揃うのを待つ間にバッファリングせずに処理を継続する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Savepoints&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Checkpointsと同じ仕組みでアプリケーション全体の状態を保存、後ほどそこから resume できる&lt;/li&gt;&#xA;&lt;li&gt;ある状態から再生してデバッグしたり、別クラスタへの移行、クラスタバージョン更新といったユースケースで有用&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;4. Setting Up a Development Environment for Apache Flink&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/streaming-with-flink/examples-scala&#34;&gt;https://github.com/streaming-with-flink/examples-scala&lt;/a&gt; のセットアップの説明。&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;5. The DataStream API (v1.7)&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;基本的な構成&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;実行環境の取得&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;val env = StreamExecutionEnvironment.getExecutionEnvironment&lt;/code&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;local か remote かを状況に応じて返す&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)&lt;/code&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;event-time モードに設定&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;入力ストリームの読み込み&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;val readings: DataStream[SensorReading] = env.addSource(new SensorSource).assignTimestampsAndWatermarks(new SensorTimeAssigner)&lt;/code&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;SensorSource はランダムなセンサーデータを生成する&lt;/li&gt;&#xA;&lt;li&gt;その後タイムスタンプと watermark を付与&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;変換&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;map() 関数で華氏から摂氏に変換&lt;/li&gt;&#xA;&lt;li&gt;KeyBy() で id でグルーピング&lt;/li&gt;&#xA;&lt;li&gt;timeWindow() で 1 秒間のウィンドウを作成&lt;/li&gt;&#xA;&lt;li&gt;apply() でユーザー定義関数を適用し平均を計算&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;val avgTemp: DataStream[SensorReading] = readings&#xA;    .map( r =&amp;gt; SensorReading(r.id, r.timestamp, (r.temperature - 32) * (5.0 / 9.0)) )&#xA;    .keyBy(_.id)&#xA;    .timeWindow(Time.seconds(1))&#xA;    .apply(new TemperatureAverager)&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;出力&lt;/li&gt;&#xA;&lt;li&gt;今回は &lt;code&gt;avgTemp: DataStream[SensorReading]&lt;/code&gt; に結果が保持され、標準出力に出力される&lt;/li&gt;&#xA;&lt;li&gt;典型的には Apache Kafka, Filesystem, Database などの Sink に出力される&lt;/li&gt;&#xA;&lt;li&gt;実行&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;env.execute(&amp;quot;Compute average sensor temperature&amp;quot;)&lt;/code&gt; を呼び出した時点で始めて実行がトリガーされる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;それまでの API 呼び出しは実行計画の作成までで、実際の変換処理は遅延実行となる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;実行計画は JobGraph に変換され、JobManager に送信される&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;/** Object that defines the DataStream program in the main() method */&#xA;object AverageSensorReadings {&#xA;    /** main() defines and executes the DataStream program */&#xA;    def main(args: Array[String]) {&#xA;&#xA;        // set up the streaming execution environment&#xA;        val env = StreamExecutionEnvironment.getExecutionEnvironment&#xA;&#xA;        // use event time for the application&#xA;        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)&#xA;        // configure watermark interval&#xA;        env.getConfig.setAutoWatermarkInterval(1000L)&#xA;&#xA;        // ingest sensor stream&#xA;        val sensorData: DataStream[SensorReading] = env&#xA;            // SensorSource generates random temperature readings&#xA;            .addSource(new SensorSource)&#xA;            // assign timestamps and watermarks which are required for event time&#xA;            .assignTimestampsAndWatermarks(new SensorTimeAssigner)&#xA;&#xA;        val avgTemp: DataStream[SensorReading] = sensorData&#xA;            // convert Fahrenheit to Celsius using an inlined map function&#xA;            .map( r =&amp;gt;&#xA;            SensorReading(r.id, r.timestamp, (r.temperature - 32) * (5.0 / 9.0)) )&#xA;            // organize stream by sensorId&#xA;            .keyBy(_.id)&#xA;            // group readings in 1 second windows&#xA;            .timeWindow(Time.seconds(1))&#xA;            // compute average temperature using a user-defined function&#xA;            .apply(new TemperatureAverager)&#xA;&#xA;        // print result stream to standard out&#xA;        avgTemp.print()&#xA;&#xA;        // execute application&#xA;        env.execute(&amp;quot;Compute average sensor temperature&amp;quot;)&#xA;    }&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Transformation&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Stream API programming は複数の変換処理をつなぎ合わせることと同義。またユーザーロジックも User defined function の transformation として扱う&lt;/li&gt;&#xA;&lt;li&gt;Basic transformations&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Map, Filter, FlatMap (一つの入力から、0から複数の出力。Map と Filter を組み合わせて汎用化したもの)&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;KeyedStream transformations&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;KeyedStream は DataStream を特定のキーで論理的にパーティショニングしたしたもの。同じキーのイベントは同じ状態にアクセスできる&lt;/li&gt;&#xA;&lt;li&gt;KeyBy&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;キーを指定し DataStream から KeyedStream を生成する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Rolling aggregations&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;sum, min, max などを KeyedStream に適用し DataStream を生成する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Reduce&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Rolling aggregation の汎用版で、Reduce関数を全要素に適用して集約する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Multistream transformation&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Union, Connect/coMap/coFlatMap (SQL の join に近い), Split and select&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Distribution transformations&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;アプリケーションレベルでデータをパーティショニングしたい場合。通常は Flink が自動で割り振るが、skew がある場合などに使う&lt;/li&gt;&#xA;&lt;li&gt;random, round-robin (rebalance), rescale (送信先のサブセットに対してのラウンドロビン), broadcast, global, custom&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Parallelism の設定&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;環境ごととオペレーターごとの設定値がある&lt;/li&gt;&#xA;&lt;li&gt;環境ごとのものは、例えばローカル起動だと CPU のコア数がデフォルト&lt;/li&gt;&#xA;&lt;li&gt;オペレーターごとに上書きできる。その際に環境の値を取得してそこからの相対値の指定も可能&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Types&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Flink は型情報をもとにデータを伝搬している。特に型に応じて独自のシリアライズを行い効率化している部分もある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;fallback として汎用型として扱われ Kyro を使ってシリアライズされるケースもあるが、パフォーマンスは劣る&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;対応している型&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Primitive (Java, Scala のプリミティブ型)&lt;/li&gt;&#xA;&lt;li&gt;Tuples&lt;/li&gt;&#xA;&lt;li&gt;Scala case classes&lt;/li&gt;&#xA;&lt;li&gt;POJOs (Apache Avro で生成されたクラスも含む)&lt;/li&gt;&#xA;&lt;li&gt;Special types&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;型情報は TypeInformation クラスで管理されている&lt;/li&gt;&#xA;&lt;li&gt;通常 Flink は適切な型を推論するが、明示的に指定することもできる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Functions の実装&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Function Classes&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;プログラムが submit されると、Java の serialization でシリアライズされ各ワーカーに配られる&lt;/li&gt;&#xA;&lt;li&gt;そのタイミング以降は変更はできない。イベントに応じた初期化などはできない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Lambda Functions&lt;/li&gt;&#xA;&lt;li&gt;Rich Functions&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;初期化とファイナライズを行う &lt;code&gt;open()&lt;/code&gt;, &lt;code&gt;close()&lt;/code&gt; がある&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;getRuntimeContext()&lt;/code&gt; が提供されており、タスクの parallelism, subtast index, task name, partitioned state などの情報にアクセスできる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;6. Time-Based and Window Operations&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;StreamExecutionEnvironment で TimeCharacteristic を設定する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ProcessingTime, EventTime, IngestionTime (イベントが Flink に到達した時刻を Event time とみなす)&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Timestamp のアサインと Watermark の生成&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;millisecond 単位の epoch time を使う&lt;/li&gt;&#xA;&lt;li&gt;SourceFunction (後述) または UDF でアサインと生成を行う&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;TimestampAssigner インタフェースを実装する&lt;/li&gt;&#xA;&lt;li&gt;できるだけ Source の近くで実行することが推奨されるが、タスクのリディストリビューションに影響しなければ任意の箇所でもよい&lt;/li&gt;&#xA;&lt;li&gt;event time 依存の transformation を行う前にアサインする必要がある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;繰り返しになるが Watermarks は正確性とレイテンシのバランスを取る必要がある。バッチ処理構成ではなかった、区切りのないイベントを扱うストリーム処理の本質的な特徴である&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Process functions&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;これまで見てきた DataStream API の関数は timestamp へのアクセスが制限されていたが、これらは Process Function という低レベルの API の上に実装されている&lt;/li&gt;&#xA;&lt;li&gt;ProcessFunction は RichFunction を実装している、つまり &lt;code&gt;open()&lt;/code&gt;, &lt;code&gt;close()&lt;/code&gt;, &lt;code&gt;getRuntimeContext()&lt;/code&gt; を提供している&lt;/li&gt;&#xA;&lt;li&gt;加えて、&lt;code&gt;KeyedProcessFunction[KEY, IN, OUT]&lt;/code&gt; を例にとると、以下の 2 つのメソッドを提供している&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;processElement(v: IN, ctx: Context, out: Collector[OUT])&lt;/code&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Context が通常の関数との大きな違いで、timestamp へのアクセスはここから可能になっている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;onTimer(timestamp: Long, ctx: OnTimerContext, out: Collector[OUT])&lt;/code&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;事前に登録された Timer がトリガーされた際に呼び出されるコールバック&lt;/li&gt;&#xA;&lt;li&gt;こちらも timestamp へのアクセスが可能&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;TimeService&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;現在時刻の取得、タイマーのセット、タイマーの削除ができる&lt;/li&gt;&#xA;&lt;li&gt;設定したタイマーが発火すると前述の &lt;code&gt;onTimer&lt;/code&gt; が呼び出される&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Side outputs&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;通常は一つの出力を下流に流すだけだが、Process functions はその他に Side outputs という別の出力をすることができる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;CoProcess functions&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;2 つの入力ストリームを受け取り、それぞれのイベントに対して処理を行う。CoFlatMapFunction と同様の機能を持つが、こちらは Context がわたってくるのでタイマーや Side output が可能&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Window operators&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;区切りのないストリームを一定のグループ (Bucket) に区切り、そのグループに対して処理を行う&lt;/li&gt;&#xA;&lt;li&gt;Window operator は 2 つの構成要素から成る&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Window assigner: 入力ストリームをどう window にグループ分けするかを定義する。window assigner は WindowStream を生成する&lt;/li&gt;&#xA;&lt;li&gt;Window function: WindowStream に適用される処理&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Window は最初の要素がアサインされた際に作成される。Finlk は空の window を評価しない&lt;/li&gt;&#xA;&lt;li&gt;Built-in Window Assigners&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Tumbling widows&lt;/li&gt;&#xA;&lt;li&gt;固定の時間間隔のウインドウ。オーバーラップなし&lt;/li&gt;&#xA;&lt;li&gt;Sliding windows&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;固定幅でインターバルごとにスライドする&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;インターバルがウインドウ幅より小さければオーバーラップするし、大きければ間のイベントは欠損する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Session windows&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;イベントとイベントの間が一定以上インアクティブな場合に別バケットとする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Windows への関数の適用&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;関数は2種類に分類できる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Incremental aggregation function: イベントがウインドウに追加されるたびに計算する。空間効率がよい&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ReduceFunction, AggregateFunction&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Full window functions: ウインドウ内の全イベントをループして計算する。より複雑なロジックを実現できる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ProcessWindowFunction (Context として Window の開始終了時刻や、グローバル・ウインドウごとの状態へのアクセスが可能。内部的にそのウインドウのイベントが全て List で保持されており、複雑なロジックが実装できる)&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Reduce などの Incremental な関数の第二引数に ProcessWindowFunction を渡すことで、届いたイベントごとに Reduce などを適用した後に ProcessWindowFunction を適用するということもできる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;カスタムの Windows 関数の定義も可能&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;windows の Lifecycle&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Assigner が最初の要素を追加したときに作られる&lt;/li&gt;&#xA;&lt;li&gt;Window ごとに以下の要素を持つ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Window content: incremental aggregation が適用された後の計算結果&lt;/li&gt;&#xA;&lt;li&gt;Window object: window ごとに別のオブジェクト。開始終了時刻などを持つ&lt;/li&gt;&#xA;&lt;li&gt;Timers and triggers&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Windows の評価や削除のタイミングでトリガーする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;カスタムの状態&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Window の破棄時に content や object も破棄されるが、カスタムのトリガーや状態は自動では破棄されず、自身でちゃんと掃除しないとリークする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Joining streams on time&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;複数のストリームを結合するために、Interval joinと window join というビルトイン関数が準備されている。またカスタムロジックも実装できる&lt;/li&gt;&#xA;&lt;li&gt;interval join&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;指定したインターバルの期間内のイベントでキーが一致したら結合する&lt;/li&gt;&#xA;&lt;li&gt;Inner Join のセマンティクスを提供する。つまりマッチするレコードがなければ破棄される&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;window join&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;assigner が振り分けて同じ window に入ったもの同士で結合する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Handling late data&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Dropする。最もシンプルでデフォルトの動作&lt;/li&gt;&#xA;&lt;li&gt;別のストリームに Side output で流す。それをどう処理するかは要件による&lt;/li&gt;&#xA;&lt;li&gt;再計算し更新を伝搬する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;実現には以下の2点がクリアになっていないといけない&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;計算済みのウインドウ、状態をどれだけ保持し続けるか&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Allowed lateness というパラメータで調整する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Sink 先が後からの更新に対応しているか&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;7. Stateful Operators and Applications&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Statefulな関数の実装&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;KeyedState&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Keyed Steamのみ&lt;/li&gt;&#xA;&lt;li&gt;同じキーを処理する並列タスクがスコープ&lt;/li&gt;&#xA;&lt;li&gt;単体の値やList, Map, ReduceState, AggregateState&lt;/li&gt;&#xA;&lt;li&gt;StateDescriptor を登録する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Operator List State&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;オペレーター単位のスコープ&lt;/li&gt;&#xA;&lt;li&gt;スナップショットをとる時やリストアする時の処理を実装する必要がある&lt;/li&gt;&#xA;&lt;li&gt;スケールイン、アウトに合わせて状態も統合、分割する必要があり、その実装を行う必要がある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Broadcast State&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば高温検知システムで閾値を動的に変えるような場合に閾値を前オペレータにブロードキャストするというユースケース&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;CheckpointedFunction interface&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;チェックポイントと状態の整合性を管理するためのインタフェース&lt;/li&gt;&#xA;&lt;li&gt;オペレーターの起動時、再起動時に初期化を行う関数と、チェックポイント取得直前に呼び出される状態のスナップショット取得関数を実装する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;例えば exactly-once を要求する sink への書き込みは、checkpoint が完了した時点のデータを書き込まないと、障害時に再計算されデータが変わる可能性がある。そのような要件のオペレーターのために JobManager から checkpoint の完了通知を受け取るインタフェースもある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Checkpoint 作成の有効化&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;明示的に有効化する必要がある&lt;/li&gt;&#xA;&lt;li&gt;Checkpoint 取得のインターバルはアプリケーション全体のパフォーマンスと障害復旧時間のトレードオフになる&lt;/li&gt;&#xA;&lt;li&gt;インターバル以外にもチューニングポイントがある。保証レベル（exactly-once or at-least-once）、checkpoint 並列数、checkpoint のタイムアウト、backend特有のパラメータなど&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Savepoint 利用時に必須の設定&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;オペレーターごとの unique identifier と Keyed State operator の最大並列数の指定がされていないと、Savepoint を適切に利用できない&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;前者がないと状態をどのオペレーターに割り振れば良いか判断できない。後者もデータの分散に影響があるので必須&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Savepoint が利用できないとアプリケーションの改修やデバッグが極めてしづらくなる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;頑健性とパフォーマンス維持&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;どのようなセットアップにするかによって変わる&lt;/li&gt;&#xA;&lt;li&gt;State Backend&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Memory&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;TaskManager のメモリ上に状態を保存し、JobManager のメモリ上に Checkpoint する&lt;/li&gt;&#xA;&lt;li&gt;高速だが、それぞれの JVM プロセスのメモリサイズまでしか状態を持てず、必然的に GC されやすい。また JobManager の障害からは復旧できない&lt;/li&gt;&#xA;&lt;li&gt;基本的には開発環境用&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Fs&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;状態は引き続きメモリ上に持つが、checkpoint は外部ストレージに保存する&lt;/li&gt;&#xA;&lt;li&gt;レイテンシを維持しつつ永続化もできるが、TaskManager のメモリサイズの制約と GC されやすいという制約は残る&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;RocksDB&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;大きいサイズの状態が必要な場合の選択肢。テラバイト水準での実績もある&lt;/li&gt;&#xA;&lt;li&gt;その分メモリに比べるとレイテンシは劣る&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;RocksDB のようにデータをシリアライズして格納するバックエンドの場合、その実装方法とどの型を使うかでパフォーマンスが大きく変わる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば ValueState は全体をシリアライズ、デシリアライズするが、MapState はキーごとにシリアライズされるので、高速になるケースもある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;アプリケーションを長期間稼働させるには状態が肥大化しないことが重要&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;状態はオペレーターの要件と深く関わっているので、Flink が自動で各状態をクリーンアップすることはできず、各オペレーターにその管理の責任がある&lt;/li&gt;&#xA;&lt;li&gt;よくあるケースは Keyed State に stale key が残り続けるというもの。expire したものはクリアする&lt;/li&gt;&#xA;&lt;li&gt;またビルトイン関数であっても状態のクリーンアップは自前で行う必要がある。例えば KyedStream の aggregation は一定の範囲のキーが何度も登場するストリームを前提としている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Stateful Application の拡張&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;アプリケーションの更新は savepoint 取得、旧アプリケーションの停止、savepoint からの新アプリケーションの起動という手順で行われるので、新旧で savepoint compatible である必要がある&lt;/li&gt;&#xA;&lt;li&gt;savepoint の互換性にはオペレーターの識別子と状態名の一致が必要&lt;/li&gt;&#xA;&lt;li&gt;更新は 3 つのケースに分類できる&lt;/li&gt;&#xA;&lt;li&gt;1. 既存の状態は変更せず、ロジックを更新、または新しい状態を追加する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;このパターンでは常に savepoint 互換性は保たれる。新しい状態は空で初期化されるだけ&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;2. 状態を削除する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;デフォルトでは Flink はすべての状態がリストアできなければアプリケーションを起動しない。そのためこの safety check を明示的に一時無効化し新アプリケーションを起動する必要がある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;3. 状態の型やプリミティブを変更する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;1.7 時点では、一部の型変更は可能だが、プリミティブの変更はサポートされていない&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;前者は Apache Avro の schema evolution rules の範囲内で可能&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;コミュニティから要望の多い機能だがまだ実現されていない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Queryable State&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ストリーミングアプリケーションの処理結果は sink のデータストアに書き込まれ別のアプリケーションから参照されるのが通常の構成&lt;/li&gt;&#xA;&lt;li&gt;Flink は Queryable State という機能を提供しており、アプリケーション稼働中の各状態を外部から read-only で参照できる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;この機能によりリアルタイムダッシュボードなど一部のユースケースの実装が容易になる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;TaskManager ごとに Proxy プロセスが動作し、クライアントからのリクエストに応じて JobManager にその状態を管理する TaskManager やキャッシュの有無を問い合わせる&lt;/li&gt;&#xA;&lt;li&gt;Query 可能な状態はストリーミング処理のコード上で指定する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;8. Reading from and Writing to External Systems&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Application consistency guarantee&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;エンドツーエンドの一貫性の保証には source と sink 側の対応も必要になる&lt;/li&gt;&#xA;&lt;li&gt;source はチェックポイントからのリプレイに対応していないと、At-most-once 保証相当になる&lt;/li&gt;&#xA;&lt;li&gt;sink は冪等性を持つか、transactionalな書き込みに対応していないと At-least-once 相当になる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;冪等に処理を行える sink の場合、最終結果は exactly-once 相当になる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ただしリカバリ中に過去のデータが見えてしまうという一貫性の崩れが起こり得る&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;transaction の実現には WAL と 2PC のアプローチがある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;前者はチェックポイントが確定した WAL のみ sink に送る。一度に sink に書き込まれる量がスパイキーになるリスクがある&lt;/li&gt;&#xA;&lt;li&gt;後者はストリーミングアプリケーションと sink 側で 2PC を実現する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Provided connectors&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Apache Kafka&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Source&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Kafka の各パーティションのオフセットをリーダーが checkpoint として保持している&lt;/li&gt;&#xA;&lt;li&gt;Kafka 0.10.0 以降は message timestamp に対応しており、Flink  はこれを event timeとして使う&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;sink&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;at-least-once 保証の場合、書き込みに失敗した際には指定した回数のリトライを行う&lt;/li&gt;&#xA;&lt;li&gt;exactly-once の場合はトランザクション機能が使われる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ログを uncommitted 状態で記録し、確定後に committed ラベルを付ける。consumerは分離レベルに応じて uncommitted なレコードを読み取るかを決める&lt;/li&gt;&#xA;&lt;li&gt;長時間 open なトランザクションは timeout 設定に応じて自動でクローズされる&lt;/li&gt;&#xA;&lt;li&gt;そのため、Flink 側でリカバリに時間がかかりすぎた場合などに、この Kafka 側のタイムアウトを超えないよう注意する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Filesystem&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;source&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;与えられたパスのファイルをスキャンし、ファイル名とその中のレンジに分割し、複数のタスクにそれを渡して並列読み込みをする&lt;/li&gt;&#xA;&lt;li&gt;ワンショットの読み取りが、mtime ペースの継続読み取りが可能&lt;/li&gt;&#xA;&lt;li&gt;後者の典型的な実装パターンは、ファイルを書き込み、完了後にそれを Flink がモニターしているディレクトリに mv するというもの&lt;/li&gt;&#xA;&lt;li&gt;filesystem を source とする場合、watermark の生成は単純ではない。複数に分割され複数のリーダーで読み取りをしているものの中から、最小の timestamp を特定する必要がある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;sink&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ローテーションの設定や encoding の設定 (行ごと、全体をバルクで) などの設定もできる&lt;/li&gt;&#xA;&lt;li&gt;in-progress/pending/complete 各状態でファイルを管理し、checkpoint がとられたものが complete としてコミットされる。この機構で exactly-once が実現される&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Apache Cassandra&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;sink connector を提供&lt;/li&gt;&#xA;&lt;li&gt;デフォルトでは casandra の upsert semantics を利用した、eventual excatly-once 保障が提供される&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一時的な inconsistency も許容できない場合は WAL モードを使うとよい&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Custom Source Function&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Source|RichFunction (1並列用) と (Rich)ParalllelSourcFunction (複数並列用) というインタフェースが提供されている&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;それぞれ run() と cancel() を実装する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;これまでに述べたように、Flink の checkpointing 機構と連携した読み取り位置 (やファイルパス、パーティション ID などのメタデータ) を保持し checkpoint として永続化すること、savepoint からの起動ではその情報を復元すること、savepoint なしではデフォルト値から開始することが、一貫性の保障のためには求められる&lt;/li&gt;&#xA;&lt;li&gt;また run() メソッドは別スレッドで動作するので、checkpoint 取得中は読み取り位置を前に進めないよう、ロックなどで保護する必要がある&lt;/li&gt;&#xA;&lt;li&gt;Source function で timestamp と watermark を生成するのがベター&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;複数並列で読み取る場合は、それぞれの source ごとに watermark を生成する必要がある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;source が stale した際に、source function はそれを検知して idle 状態に遷移する必要がある。前述したようにそうしないと watermark が進まず、アプリケーション全体が滞留してしまう&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Custom Sink Function&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;冪等性を持つ sink との連携の場合、レコードを特定できる識別子があること、sink 先がそのキーでの upsert に対応していることが必要とされる&lt;/li&gt;&#xA;&lt;li&gt;transactional なアプローチの場合、WAL 方式か 2PC 方式かが必要になる。それぞれのテンプレートがありそれを実装する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;WAL&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;GenericWriteAheadSink の sendValues() を実装する&lt;/li&gt;&#xA;&lt;li&gt;WAL 方式は次のケースで厳密な exactly-once にはならない&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;sendValues() の途中で失敗した場合。特に sink バルクインサートに対応していない場合、途中までのレコードが書き込まれていて、その後もう一度おなじ書き込みが走ることになる&lt;/li&gt;&#xA;&lt;li&gt;sendValue() は成功したが、その後 CheckpointCommitter の呼び出し前や呼び出し中に失敗した場合。やはり全レコードが再度書き込まれる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;2PC&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;基本的に重いプロトコルなので、本当に必要かは吟味したほうがよい&lt;/li&gt;&#xA;&lt;li&gt;2PC を採用するには sink は以下を満たす必要がある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;transaction サポート (または sink function によるエミュレーション)&lt;/li&gt;&#xA;&lt;li&gt;transaction を open し checkpoint interval の間に追加の write を受け付けられること&lt;/li&gt;&#xA;&lt;li&gt;checkpoint completion notification を受け取るまでコミットを待てること&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;もし sink が timeout でトランザクションをクローズした場合、その間のデータはロストする&lt;/li&gt;&#xA;&lt;li&gt;Cassandra sink はこのケースではデータをロストするので、条件付きの exactly-once 保証になる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;問題発生時にはトランザクションを復旧できること&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;transaction id を発行し、それに対してコミットかロールバックを発行できる sink もある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;transaction のコミットは冪等であること。同じ transaction id への複数回のコミットがありえる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;オペレータ内で POST API を呼び出すなど、ストリームアプリケーションに sink は必須のものではない。ただ exactly-once の保障などが必要な場合は sink の機構を利用したほうがよい&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;外部システムへの非同期アクセス&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;source と sink 以外に、ストリームの途中で外部システムに問い合わせ、入力値の情報を補完するというユースケースがある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば &lt;a href=&#34;https://github.com/yahoo/streaming-benchmarks&#34;&gt;https://github.com/yahoo/streaming-benchmarks&lt;/a&gt; にはクリックデータにキャンペーン情報を補完するというパターンがある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;単純にオペレータ内で同期的にに外部へクエリし結果を待っていると、全体のレイテンシが悪化する&lt;/li&gt;&#xA;&lt;li&gt;非同期なアクセス手法が提供されており、この問題を緩和できる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;event-time, watermark や savepoint の考慮もされている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;9. Setting Up Flink for Streaming Applications&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;セットアップに関しては、概要は以前の章でカバーされており、詳細は実際に必要な部分をその時参照した方が良いので、メモは割愛&lt;/li&gt;&#xA;&lt;li&gt;パラメータチューニング&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;cpu&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;明示的なパラメータはない&lt;/li&gt;&#xA;&lt;li&gt;TaskManager ごとに最大何スロット起動できるかの設定はある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;主にスタンドアローンクラスタ向けのもので、k8s 上などに展開するクラスタの場合 pod ごとに 1 slot とし pod 数自体を伸縮させた方がわかりやすい&lt;/li&gt;&#xA;&lt;li&gt;後述のメモリ管理的にも、例えば jvm のヒープサイズはその上で動く slot 全体で共有になるので、1 slot ごとの方がやはり管理しやすい&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;memory&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;まずは jvm のヒープサイズ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;master プロセスは中程度のスペックで良いが、TaskManager はそれよりも豊富な方が良い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ネットワークバッファのメモリ使用量も多くなる傾向がある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Netty library が使われており、接続先ごとにネットワークバッファを持っている&lt;/li&gt;&#xA;&lt;li&gt;タスクの並列度や組み合わせによって、接続数は大きく増えることがある&lt;/li&gt;&#xA;&lt;li&gt;ネットワークバッファは native (off-heap) memory に確保される&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;RocksDB のメモリ使用量も増加することが多い&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;どの程度になるかはワークロードによる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Disk storage&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;JAR ファイル、ログ、RocksDB バックエンドの場合の状態保持などで、 disk storage を利用している&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Checkpoint and state backend&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;コードレベル、アプリケーションレベルでの指定が主だが、クラスタレベルでのデフォルト値も指定できる&lt;/li&gt;&#xA;&lt;li&gt;バックエンドによりサポート状況は異なるが、async checkpointing や incremental checkpointing はパフォーマンスに影響する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Security&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Kerberos 認証や内部、外部通信の SSL 化に対応している&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;10. Operating Flink and Streaming Applications&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Streaming application の運用&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Savepoint は checkpoint とは異なり、手動でトリガーする必要があり、また自動では削除されない&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;flink&lt;/code&gt; cli tool&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;job id: アプリケーションの識別子&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;flink savepoint &amp;lt;jobId&amp;gt; [savepointPath]&lt;/code&gt; で savepoint を取得&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;flink savepoint -d savepointPath&lt;/code&gt; で savepoint を削除&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;savepoint の取得が完了するまでは削除してはいけいない。checkpoint と同じように完了 notification に依存する処理がありうるので&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;flink cancel &amp;lt;jobId&amp;gt;&lt;/code&gt; でアプリケーションをキャンセル&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;cancel -s &amp;lt;savepointPath&amp;gt;&lt;/code&gt; で savepoint を取得してからキャンセル&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;flink run -s &amp;lt;savepointPath&amp;gt; [options] &amp;lt;jobJar&amp;gt; [arguments]&lt;/code&gt; で savepoint からアプリケーションを起動&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;scale-in/out は savepoint からの起動時に設定を変更するだけで良い&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;アプリケーション全体の並列数設定ではなく、コード中で指定している場合はリコンパイルが必要になる&lt;/li&gt;&#xA;&lt;li&gt;exactly-once を保障している場合は &lt;code&gt;cancel -s&lt;/code&gt; で savepoint を取得してからキャンセルすること&lt;/li&gt;&#xA;&lt;li&gt;アプリケーション全体の並列していだけで構成されている場合は &lt;code&gt;flink modify &amp;lt;jobId&amp;gt; -p &amp;lt;newParallelism&amp;gt;&lt;/code&gt; というコマンドだけで変更が可能&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;cli tool の内部では REST API を利用している。直接 REST API を利用することも可能&lt;/li&gt;&#xA;&lt;li&gt;ここまでは fremework style depolyment の話だったが、前述のように library style もある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;./flink-container/docker/build.sh&lt;/code&gt; でイメージを作成し、&lt;code&gt;./flinc-container/kubernetes&lt;/code&gt; 配下の yaml テンプレートを参考に構築できる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Controlling task scheduling&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Task chaning の制御&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;複数の処理を 1 スレッドにまとめ、データ転送のオーバーヘッドをなくす task chaning はデフォルトで有効になっている&lt;/li&gt;&#xA;&lt;li&gt;ただし自動判定で結合・分断されたものが最適で無い場合、無効化することもできる&lt;/li&gt;&#xA;&lt;li&gt;特定のオペレータに対して明示的に chain させる、させない指定もできる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Slot-sharing group&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;オペレーターをどのスロットグループに所属させるかを明示できる&lt;/li&gt;&#xA;&lt;li&gt;同じグループのオペレーターはそのグループのスロットで実行される&lt;/li&gt;&#xA;&lt;li&gt;各グループに何スロット割り当てられるかは parallelism 設定により割り出される&lt;/li&gt;&#xA;&lt;li&gt;&lt;figure&gt;&lt;img src=images/stream-processing-flink-fig-10-1.png /&gt;&lt;figcaption&gt;Figure 10-1. Controlling task scheduling with slot-sharing groups より引用&lt;/figcaption&gt;&lt;/figure&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Checkpointing のチューニング&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;取得するインターバル&lt;/li&gt;&#xA;&lt;li&gt;保証レベル (at-least-once or exactly-once)&lt;/li&gt;&#xA;&lt;li&gt;前回の取得からの最少インターバル&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;チェックポイントの取得完了までに時間がかかるケースのため&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;並列数&lt;/li&gt;&#xA;&lt;li&gt;タイムアウト&lt;/li&gt;&#xA;&lt;li&gt;エラー時の挙動（例外を出して再起動するか否か）&lt;/li&gt;&#xA;&lt;li&gt;圧縮の有無（バックエンドのサポート状況にもよる）&lt;/li&gt;&#xA;&lt;li&gt;アプリケーションエラー時にチェックポイントを保持するかどうか&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;State backend のチューニング&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;メモリ: 非同期のオンオフ、最大サイズ&lt;/li&gt;&#xA;&lt;li&gt;FileSystem: パス、非同期のオンオフ&lt;/li&gt;&#xA;&lt;li&gt;RocksDB: パス、インクリメンタルバックアップのオンオフ&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Recovery のチューニング&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;リカバリ時には入力が溜まっている。最新にキャッチアップするよう十分なリソースが必要&lt;/li&gt;&#xA;&lt;li&gt;時には何度再起動しても失敗し続けるケースもある。そのため再起動には３つのモードがある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;固定のインターバル後に再起動&lt;/li&gt;&#xA;&lt;li&gt;ある期間にある回数だけ失敗できるという割合を指定し、その範囲内で再起動&lt;/li&gt;&#xA;&lt;li&gt;再起動しない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;同じマシンでの再起動など、チェックポイントが外部ストレージだけでなく内部にも残っていることがある。local recovery を有効化するとそのようなケースでの復旧が速くなる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;あくまで source of truth は外部ストレージ&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;監視&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;出発点としてはバンドルされている Web UI がある&lt;/li&gt;&#xA;&lt;li&gt;Prometheus など各種監視バックエンドへのメトリクスの連携も可能&lt;/li&gt;&#xA;&lt;li&gt;独自のメトリクス定義も可能&lt;/li&gt;&#xA;&lt;li&gt;レイテンシの計測&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;各イベントの厳密なレイテンシを計測するのは分散システムでは非常に難しい&lt;/li&gt;&#xA;&lt;li&gt;そこで flink ではレイテンシ計測用の特殊なイベントを source から流し、sink に到達するまでの時間を計測することでレイテンシを概算している&lt;/li&gt;&#xA;&lt;li&gt;この専用イベントは latency marker と呼ばれる。利用時にはインターバルを指定する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ロギング&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;デフォルトは log4j で logback にも対応している&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;11. Where to Go from Here&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;本書では紹介していないハイレベル API&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;DataSet API&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;バッチ処理が可能になる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Table API&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;SQL を解釈しジョブグラフに落とし込んでくれる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;FlinkCEP&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;複雑なパターンマッチ。金融、不正検知、異常の監視などがユースケース&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Gelly&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;グラフの作成&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;コミュニティ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Mailing list&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;user@flink.apache.org&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;dev@flink.apache.org&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;community@flink.apache.org&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Blogs&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://flink.apache.org/posts/&#34;&gt;https://flink.apache.org/posts/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.ververica.com/blog&#34;&gt;https://www.ververica.com/blog&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Meetups, conferences&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.flink-forward.org&#34;&gt;https://www.flink-forward.org&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.meetup.com/topics/apache-flink/&#34;&gt;https://www.meetup.com/topics/apache-flink/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;div class=&#34;amazlet-box&#34; style=&#34;margin-bottom:0px;&#34;&gt;&lt;div class=&#34;amazlet-image&#34; style=&#34;float:left;margin:0px 12px 1px 0px;&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/149197429X/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;&lt;img src=&#34;https://m.media-amazon.com/images/I/912jjckPLoL._SY466_.jpg&#34; alt=&#34;Stream Processing with Apache Flink: Fundamentals, Implementation, and Operation of Streaming Applications&#34; style=&#34;border: none; width: 113px;&#34; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-info&#34; style=&#34;line-height:120%; margin-bottom: 10px&#34;&gt;&lt;div class=&#34;amazlet-name&#34; style=&#34;margin-bottom:10px;line-height:120%&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/149197429X/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Stream Processing with Apache Flink: Fundamentals, Implementation, and Operation of Streaming Applications&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-detail&#34;&gt;英語版  Fabian Hueske (著), Vasiliki Kalavri (著)&lt;br/&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-sub-info&#34; style=&#34;float: left;&#34;&gt;&lt;div class=&#34;amazlet-link&#34; style=&#34;margin-top: 5px&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/149197429X/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Amazon.co.jpで詳細を見る&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-footer&#34; style=&#34;clear: left&#34;&gt;&lt;/div&gt;&lt;/div&gt;&#xA;</description>
      <author>Kosei Moriyama</author>
      <pubDate>Wed, 12 Jun 2024 20:30:00 +0900</pubDate>
    </item>
    <item>
      <title>読書メモ: Web配信の技術―HTTPキャッシュ・リバースプロキシ・CDNを活用する</title>
      <link>https://please-sleep.cou929.nu/web-delivery-book.html</link>
      <description>&lt;div class=&#34;amazlet-box&#34; style=&#34;margin-bottom:0px;&#34;&gt;&lt;div class=&#34;amazlet-image&#34; style=&#34;float:left;margin:0px 12px 1px 0px;&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4297119250/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;&lt;img src=&#34;https://m.media-amazon.com/images/I/814o9cRmwqL._SY466_.jpg&#34; alt=&#34;Web配信の技術―HTTPキャッシュ・リバースプロキシ・CDNを活用する&#34; style=&#34;border: none; width: 113px;&#34; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-info&#34; style=&#34;line-height:120%; margin-bottom: 10px&#34;&gt;&lt;div class=&#34;amazlet-name&#34; style=&#34;margin-bottom:10px;line-height:120%&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4297119250/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Web配信の技術―HTTPキャッシュ・リバースプロキシ・CDNを活用する&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-detail&#34;&gt;田中 祥平 (著)&lt;br/&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-sub-info&#34; style=&#34;float: left;&#34;&gt;&lt;div class=&#34;amazlet-link&#34; style=&#34;margin-top: 5px&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4297119250/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Amazon.co.jpで詳細を見る&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-footer&#34; style=&#34;clear: left&#34;&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;Cache-Control ヘッダから始まり、クライアント (ブラウザ) のローカルのキャッシュから CDN まで、Web システムの「配信」の最適化技術を網羅的に解説してくれている本。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;業務の必要に応じて、CDN など個別のソリューション (の必要な一部の機能) は使ったことがあったり、Cache-Control の一部のディレクティブは都度調べて指定したことはあった。こうした「配信」に関する知識は各論の寄せ集めになっていて、体系的に理解できていないなという実感が以前からあった。また例えば HTTP のヘッダの仕様は歴史的経緯もあり複雑で、RFC から追おうと思っても必要な文章が複数あったり、実際の世の中の実装にばらつきがあったりと、キャッチアップコストが高い知識も多い。こうした領域を一冊でカバーしてくれている、稀有な本だった。「配信」はすべての Web システムが多かれ少なかれ必ず行っているので、ニッチな知識ではなく、活用できる場面も多いと思う。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;自分の現在の業務を鑑みる。今はモバイルアプリのバックエンド開発が主務で、クライアントはブラウザではなくネイティブアプリ。それは HTTP クライアントライブラリを使ってサーバと通信している。そのため Cache-Control ヘッダ等の知識がすぐにそのまま使えるわけではない。ただベースラインとして標準技術を把握しておくことは重要だと思う。最近、データ転送量削減のため、動画ファイル等サイズが大きいコンテンツをクライアント側で独自にローカルキャッシュする対策が行われた。これを標準技術にあてはめると、TTL をほぼ無限に、キャッシュキーは URL のみ、キャッシュストレージは LRU、更新は実質的に Cache Busting、stale-while-revalidate/stale-if-error などは未実装でエッジケースで問題が出るかどうかは要検証、といった具合に理解を整理できる。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;以降は読書メモ。&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;1. はじめに&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;定義&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;配信: Web において主に HTTP でコンテンツをサーバからクライアントに届けること&lt;/li&gt;&#xA;&lt;li&gt;配信最適化: 配信をより高速・安定・安全にすること&lt;/li&gt;&#xA;&lt;li&gt;配信システム: Proxy や CDN などの技術を組み合わせたシステム&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;配信最適化は実施コストに対して 費用削減、UX 改善のベネフィットが大きく、中小規模システムでも取り組む価値がある&lt;/li&gt;&#xA;&lt;li&gt;サービスダウン、情報漏洩といった事故につながるリスクがあり、正しい知識が必要&lt;/li&gt;&#xA;&lt;li&gt;基本的な構成&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;オリジン&lt;/li&gt;&#xA;&lt;li&gt;キャッシュレイヤー (Proxy, CDN)&lt;/li&gt;&#xA;&lt;li&gt;クライアント&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;2. 配信の基礎&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;基本的な考え方&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;最適化の目標&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;共通&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;クライアントがコンテンツを高速にダウンロードできる&lt;/li&gt;&#xA;&lt;li&gt;突発的なリクエストに耐える&lt;/li&gt;&#xA;&lt;li&gt;低コストで実現する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;個別&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;低遅延など&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;標準化されたプロトコル + 要件に応じた個別実装という視点を持つ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;よってプロトコルをまずは押さえる。その実装状況も把握する&lt;/li&gt;&#xA;&lt;li&gt;bandwidth, throughput, latency などの指標を理解する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;配信経路を理解する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ファースト|ミドル|ラストマイル、DNS、ISP や IX など&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;なお本書では実施が容易なものを扱うので、例えば ISP との直接接続といったトピックは対象外&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;そうすると、ボトルネックがどこか・ある技術はどこを最適化するのかといった見通しが立つ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば 5G はラストマイルを高速化する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;最適化の方針&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;各所でのキャッシュ&lt;/li&gt;&#xA;&lt;li&gt;その他: コンテンツの圧縮やオリジンの増強&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;キャッシュの分類&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;格納場所による分類&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;クライアント (ローカルキャッシュ)&lt;/li&gt;&#xA;&lt;li&gt;経路上 (CDN, Proxy)&lt;/li&gt;&#xA;&lt;li&gt;オリジン (ゲートウェイキャッシュ)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一般化すると CDN, Proxy と近いが、実運用で細かい差異があるため、別トピックとして本書では扱う&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;性質による分類&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;private&lt;/li&gt;&#xA;&lt;li&gt;shared&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;文献によっては格納場所による分類を private/shared と呼ぶこともあるので注意。例えばローカルキャッシュは private、CDN は shared といった具合&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Topics: EDNS Client Subnet (ECS)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;CDN は名前解決リクエストを送信した ISP の IP に応じて位置的に近いエッジの IP を返している&lt;/li&gt;&#xA;&lt;li&gt;もし Google Public DNS のような DNS キャッシュサービスを使うと、ナイーブには CDN が最適化できない&lt;/li&gt;&#xA;&lt;li&gt;ECS は DNS 問い合わせ時にクライアントの IP アドレスの一部を添えて送信する。この情報をもとに CDN が最適化した IP を返す&lt;/li&gt;&#xA;&lt;li&gt;Quad9 などプライバシー対策として ECS に対応していない DNS もある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;3. HTTPヘッダ・設定とコンテンツの見直し&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;CDN 導入やサーバ増強以前にやるべき細かい改善&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;総転送量 = リクエスト数 x 平均コンテンツサイズ&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;前者は標準にのっとったヘッダ設定でローカルキャッシュを適切に効かせる&lt;/li&gt;&#xA;&lt;li&gt;後者はコンテンツ自体の適切な圧縮やリサイズを行う&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;ローカルキャッシュ&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc7234#section-2&#34;&gt;RFC 7234 - Hypertext Transfer Protocol (HTTP/1.1): Caching&lt;/a&gt; には次のように、クライアントはデフォルトでキャッシュし、それを防ぎたい (あるいは伸ばすなど制御したい) 場合は Cache-Control 等のヘッダを適切に設定するよう記述されている。実装によるがこの原則で様々なクライアントは動作する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&amp;gt; Although caching is an entirely OPTIONAL feature of HTTP, it can be assumed that reusing a cached response is desirable and that such reuse is the default behavior when no requirement or local configuration prevents it.&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;HTTP キャッシュ仕様&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;キャッシュをする条件 RFC7234#3 と キャッシュを利用する条件 RFC7234#4&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;デフォルトでキャッシュ可能なメソッド RFC7231#4.2.3&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;GET, HEAD, POST&lt;/li&gt;&#xA;&lt;li&gt;実態として POST がキャッシュされることは少ない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;デフォルトでキャッシュ可能なステータスコード RFC7231#6.1&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;200, 203, 204, 206, 300, 301, 404, 405, 410, 414, 501&lt;/li&gt;&#xA;&lt;li&gt;421 (RFC7540#9.1.2), 451 (RFC7725#3)&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Cache-control (RFC7234, RFC9111)&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;[memo] 本書は RFC7234 ベースで記述されている。当時ドラフトだった RFC9111 は現在は発行済で、曖昧だった部分が明確化されている。&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;保存方法の指定 (未指定, public, private) RFC7234#5.2.2.5, RFC7234#5.2.2.6&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;複数クライアントで共有できる shared キャッシュ か、そうではない private キャッシュかの指定だが、通常キャッシュができないケース (例えばキャッシュできないステータスコード) でもキャッシュをするという強い指定だという点に注意する&lt;/li&gt;&#xA;&lt;li&gt;通常の shared キャッシュをしたいだけなら未指定でよい。public は一部の CDN の仕様に合わせる場合や、未認証でも見られるコンテンツに Authorization ヘッダが付いてしまい強制的にキャッシュさせたい場合など、限られたケースでしか必要ない。private は private キャッシュ利用の際に必要だが、あらためて通常キャッシュできないケースでもキャッシュされてしまうことに注意する&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;指定&lt;/th&gt;&#xA;&lt;th&gt;キャッシュ許可の対象&lt;/th&gt;&#xA;&lt;th&gt;クライアントとの対応&lt;/th&gt;&#xA;&lt;th&gt;経路上への格納&lt;/th&gt;&#xA;&lt;th&gt;ローカルへの格納&lt;/th&gt;&#xA;&lt;th&gt;通常キャッシュできないパターンもキャッシュ&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/thead&gt;&#xA;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;未指定&lt;/td&gt;&#xA;&lt;td&gt;複数クライアント (shared) に許可&lt;/td&gt;&#xA;&lt;td&gt;1:n&lt;/td&gt;&#xA;&lt;td&gt;YES&lt;/td&gt;&#xA;&lt;td&gt;YES&lt;/td&gt;&#xA;&lt;td&gt;NO&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;public&lt;/td&gt;&#xA;&lt;td&gt;複数クライアント (shared) に許可&lt;/td&gt;&#xA;&lt;td&gt;1:n&lt;/td&gt;&#xA;&lt;td&gt;YES&lt;/td&gt;&#xA;&lt;td&gt;YES&lt;/td&gt;&#xA;&lt;td&gt;YES&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;private&lt;/td&gt;&#xA;&lt;td&gt;特定のクライアントのみ (private) 許可&lt;/td&gt;&#xA;&lt;td&gt;1:1&lt;/td&gt;&#xA;&lt;td&gt;NO&lt;/td&gt;&#xA;&lt;td&gt;YES&lt;/td&gt;&#xA;&lt;td&gt;YES&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;no-store (RFC7234#5.2.2.3) と no-cache (RFC7234#5.2.2.2)&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;前者はキャッシュ格納時、後者はキャッシュ利用時に評価される&lt;/li&gt;&#xA;&lt;li&gt;no-store が指定されていると、キャッシュは保存されない&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;全くキャッシュさせないというユースケースには、仕様上 no-store 指定だけで良いことになるが、実装上走でないケースがあり、後述される&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;no-cache が指定されていると、格納されているキャッシュを利用してよいか、オリジンに条件付きリクエストで問い合わせを行う。最新であれば (302 が返れば) キャッシュから、そうでなければ (200 が返れば) 新たに取得したコンテンツを使う&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;キャッシュの再利用 must-revalidate (RFC7234#5.2.2.1), proxy-revalidate (RFC7234#5.2.2.7), immutable (RFC8246)&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;must-revalidate は有効期限切れ時に再検証を行う&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;対して no-cache は毎回検証を行う&lt;/li&gt;&#xA;&lt;li&gt;must-revalidate は max-age と共存できる&lt;/li&gt;&#xA;&lt;li&gt;期限切れ後にオリジンがダウンしている場合、must-revalidate が指定されていると検証失敗でキャッシュは使われない。指定がない場合はキャッシュが使われる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;proxy-revalidate は private キャッシュに適用されない以外は must-revalidate と同じ&lt;/li&gt;&#xA;&lt;li&gt;immutable はコンテンツが不変であると認識し、再検証は一切行われなくなる。更新したい場合は URL を変更する必要がある。慎重な取り扱いが求められる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;no-transform (RFC7234#5.2.2.4)&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;proxy などの中間でオブジェクトに対して変更、通信量削減を目的として圧縮等、を許可しない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;期限&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;キャッシュの状態遷移&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Fresh: 有効期限内, Stale: 期限切れだが条件付きで再利用可能&lt;/li&gt;&#xA;&lt;li&gt;Stale なキャッシュは検証に成功すれば Fresh に移行する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;max-age&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;オリジンがレスポンスを生成した時刻を起点とした相対的なキャッシュの期限&lt;/li&gt;&#xA;&lt;li&gt;起点となる時刻はクライアントがリクエストを行った時刻とする (RFC7234#4.2.3)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Date ヘッダもあるが、クライアントとオリジンの時刻が同期されているとは限らないため&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;CDN 等の中間キャッシュからの取得の場合、Age ヘッダも考慮される。Age は中間に格納されてからの経過時間を示す。クライアントは max-age - age を fresh な時間として扱う&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;s-maxage (RFC7234#5.2.2.9)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;経路上の CDN, proxy などにだけ有効なキャッシュ&lt;/li&gt;&#xA;&lt;li&gt;通常のユースケースでは s-maxage &amp;lt;= max-age となるのがほとんどのはず&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;逆だと、CDN で max-age 以上経過したコンテンツは、その後クライアントキャッシュが常に stale になる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;stale-while-revalidate (RFC5861)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;stale キャッシュの再検証をバックグランドで行っている間、何秒まで stale キャッシュを使ってもいいかという指定&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;stale-if-error (RFC5861)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;stale キャッシュの再検証がダウンなどで失敗した場合、何秒まで stale キャッシュを使ってもいいかという指定&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Expires&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;キャッシュの有効期限の絶対時間&lt;/li&gt;&#xA;&lt;li&gt;max-age を解さない古いクライアント向けで、基本的には max-age を使うべき。両方指定があれば max-age が優先される&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Date, Last-Modified&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;max-age などを指定していない場合、ブラウザがデフォルトでキャッシュを行うことがある&lt;/li&gt;&#xA;&lt;li&gt;その際の TTL は &lt;code&gt;TTL = Date - Last-Modified / ブラウザ実装による定数 (10 が一般的)&lt;/code&gt; となる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば 10 日間更新のないコンテンツは 1 日キャッシュする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;条件付きリクエスト&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;検証子&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Last-Modified: 最終更新日時&lt;/li&gt;&#xA;&lt;li&gt;ETag: エンティティタグ (フィンガープリント)&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;If-Modified-Since (RFC7232#3.3)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Last-Modified 以降に更新があればコンテンツを送る、そうでなければ 304&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;If-None-Match (RFC7232#3.2)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ETag が一致すれば 304、そうでなければコンテンツを送る&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;その他にも If-Unmodified-Since, If-Match, If-Range などがある&lt;/li&gt;&#xA;&lt;li&gt;サーバは 304 を返す際も 200 のときと同じヘッダをきちんと検討して返さないと事故につながる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Cache-Control, Content-Location, Date, ETag, Expires, Vary&lt;/li&gt;&#xA;&lt;li&gt;例えばオリジンは 200 で no-cache を返しており、CDN は 200 の場合は max-age=3600 で上書きしているというケース&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;CDN からは毎回条件付きリクエストで検証してほしいという意図&lt;/li&gt;&#xA;&lt;li&gt;このときオリジンが 304 を返すと、CDN は (200 でないので) Cache-Control を上書きしない&lt;/li&gt;&#xA;&lt;li&gt;結果としてクライアントは no-cache としてキャッシュを取り扱い、クライアントから毎回検証リクエストが行われてしまう&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Vary ヘッダ (RFC7234#4.1)&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;クライアントがこのヘッダの値をキャッシュ時のセカンダリーキーとして使う&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば Accept-encoding が指定されると、クライアントは URL と圧縮方式ごとにキャッシュを持つ&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;実践時のテクニック&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Cache-controlが指定されているかの確認&lt;/li&gt;&#xA;&lt;li&gt;ETag がコンテンツの更新がなくても変わるケース&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Apache が inode ペースで ETag を生成しており、LB 配下に複数のサーバがある場合は、同じコンテンツでもオリジンのサーバによって ETag が変わる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ヘッダクレンジング&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;稼働中のシステムを最適化する場合、ヘッダ設定箇所をすべて把握するのは簡単ではない&lt;/li&gt;&#xA;&lt;li&gt;まずはゲートウェイで一括書き換えを行うのがスタートポイントとして良い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;コンテンツ圧縮&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;アプリケーションサーバでやるのがおすすめ&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;そこまで CPU リソースは使わず、Proxy などよりアプリケーションサーバの方がスケールアウトしやすいことが多いため&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;S3, GCS 等はデフォルトで圧縮してくれるような機能はないため自身で設定する必要がある&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;最適化済みの画像や動画は圧縮効果が低いので外す&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;本画像とサムネイルでサイズを分ける&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;画像フォーマット&lt;/p&gt;&#xA;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;フォーマット&lt;/th&gt;&#xA;&lt;th&gt;圧縮&lt;/th&gt;&#xA;&lt;th&gt;透過&lt;/th&gt;&#xA;&lt;th&gt;アニメ&lt;/th&gt;&#xA;&lt;th&gt;サポートブラウザ&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/thead&gt;&#xA;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;jpeg&lt;/td&gt;&#xA;&lt;td&gt;非可逆&lt;/td&gt;&#xA;&lt;td&gt;なし&lt;/td&gt;&#xA;&lt;td&gt;なし&lt;/td&gt;&#xA;&lt;td&gt;主要ブラウザ全て&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;png&lt;/td&gt;&#xA;&lt;td&gt;可逆&lt;/td&gt;&#xA;&lt;td&gt;あり&lt;/td&gt;&#xA;&lt;td&gt;なし&lt;/td&gt;&#xA;&lt;td&gt;主要ブラウザ全て&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;gif&lt;/td&gt;&#xA;&lt;td&gt;可逆&lt;/td&gt;&#xA;&lt;td&gt;あり&lt;/td&gt;&#xA;&lt;td&gt;あり&lt;/td&gt;&#xA;&lt;td&gt;主要ブラウザ全て&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;webp&lt;/td&gt;&#xA;&lt;td&gt;可逆/非可逆&lt;/td&gt;&#xA;&lt;td&gt;あり&lt;/td&gt;&#xA;&lt;td&gt;あり&lt;/td&gt;&#xA;&lt;td&gt;主要ブラウザすべて (一定のOSバージョンが必要)&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;avif&lt;/td&gt;&#xA;&lt;td&gt;可逆/非可逆&lt;/td&gt;&#xA;&lt;td&gt;あり&lt;/td&gt;&#xA;&lt;td&gt;あり&lt;/td&gt;&#xA;&lt;td&gt;Chrome&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;png&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;bpp: 8bit, 24bit, 32biy (24bit+alpha)&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;jpeg&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;q値でクオリティの変更&lt;/li&gt;&#xA;&lt;li&gt;SSIM でクオリティの数値化&lt;/li&gt;&#xA;&lt;li&gt;ベースラインとプログレッシブ&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;webp&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一般的に圧縮率は高いが、古いフォーマットのほうがカバレッジは高い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;ちょっとした動画でも gif や apng より mp4 など専用フォーマットの方が効率的な場合は多い&lt;/p&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Topics: HTTP/2 misc&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ヘッダが小文字になる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;Cache-Control&lt;/code&gt; と &lt;code&gt;cache-control&lt;/code&gt; など&lt;/li&gt;&#xA;&lt;li&gt;HTTP/1.1 以前は case-insensitive だったが、サーバ実装によっては大文字小文字を区別してしまっているケースがあるので注意&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;開始行は存在せず、疑似ヘッダからメッセージが始まる&#xA;&lt;code&gt;&#xA;# リクエスト例&#xA;:method: GET # 以下4行が疑似ヘッダ&#xA;:scheme: https&#xA;:path: /&#xA;:authority: example.com&#xA;foo: bar&#xA;# レスポンス例&#xA;:status: 200&#xA;&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Topics: リクエスト時の Cache-control&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;Cache-Control: max-age=0&lt;/code&gt; などと、指定された秒数を超えるキャッシュは受け付けないというリクエストを送信することができる。実勢に Chrome のリロードではこのヘッダが付与される&lt;/li&gt;&#xA;&lt;li&gt;実態としては経路上のキャッシュはこうしたリクエストの指示を無視する&lt;/li&gt;&#xA;&lt;li&gt;ローカルキャッシュには有効で、例えば XHR を使う際にキャッシュを使わないよう &lt;code&gt;no-cache&lt;/code&gt; を指定することなどがある&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control&#34;&gt;Cache-Control - HTTP | MDN&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Topics: 中間でのデータ変更&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Chrome の Data saver 機能や通信キャリアによる通信最適化によって、中間でコンテンツが変更されることがある&lt;/li&gt;&#xA;&lt;li&gt;変更を防ぐには、根本的には HTTPS 通信を行うことが必要だが、それが難しい場合は &lt;code&gt;Cache-Control: no-transform&lt;/code&gt; を指定することで変更を禁止できる&lt;/li&gt;&#xA;&lt;li&gt;ただし中間 proxy によっては &lt;code&gt;no-transform&lt;/code&gt; を無視することがある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Topics: キャッシュをさせたくない場合の厳密な指定&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;Cache-Control: private, no-store, no-cache, must-revalidate&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;経路上のキャッシュを防ぎ、保存の不許可、利用の不許可、オリジンダウン時の再利用を防ぐための must-revalidate&lt;/li&gt;&#xA;&lt;li&gt;Last-Modified をなくすことで TTL 未指定の挙動を排除でき、より厳密になる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Topics: &lt;a href=&#34;https://datatracker.ietf.org/doc/rfc9205/&#34;&gt;RFC 9205 also known as BCP 56&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;HTTP を使う上でのベストプラクティスで RFC3205の置き換え&lt;/li&gt;&#xA;&lt;li&gt;本書執筆時点では策定中だったが現在はRFC9205となっている&lt;/li&gt;&#xA;&lt;li&gt;RFC7230-7235など HTTP の仕様が長大になっているので BCP をまず読むのがリーズナブル&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;4. キャッシュによる負荷対策&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;最も致命的な事故は、キャッシュが混ざることによる情報漏洩。中間の CDN, Proxy 視点からの、対策の基本的な考え方&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;適切なキャッシュキーの設定とキャッシュ対象の精査&lt;/p&gt;&#xA;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;キャッシュ&lt;/th&gt;&#xA;&lt;th&gt;ユーザー情報&lt;/th&gt;&#xA;&lt;th&gt;最低限設定すべきキャッシュキー&lt;/th&gt;&#xA;&lt;th&gt;オリジンへのリクエストヘッダ&lt;/th&gt;&#xA;&lt;th&gt;クライアントへのレスポンスヘッダ&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/thead&gt;&#xA;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;する&lt;/td&gt;&#xA;&lt;td&gt;含む&lt;/td&gt;&#xA;&lt;td&gt;ホスト名 + パス + ユーザー情報&lt;/td&gt;&#xA;&lt;td&gt;そのまま&lt;/td&gt;&#xA;&lt;td&gt;Set-cookie 削除&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;する&lt;/td&gt;&#xA;&lt;td&gt;含まない&lt;/td&gt;&#xA;&lt;td&gt;ホスト名 + パス&lt;/td&gt;&#xA;&lt;td&gt;Cookie, Authorization 削除&lt;/td&gt;&#xA;&lt;td&gt;Set-cookie 削除&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;しない&lt;/td&gt;&#xA;&lt;td&gt;含む&lt;/td&gt;&#xA;&lt;td&gt;na&lt;/td&gt;&#xA;&lt;td&gt;そのまま&lt;/td&gt;&#xA;&lt;td&gt;そのまま&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;しない&lt;/td&gt;&#xA;&lt;td&gt;含まない&lt;/td&gt;&#xA;&lt;td&gt;na&lt;/td&gt;&#xA;&lt;td&gt;そのまま&lt;/td&gt;&#xA;&lt;td&gt;そのまま&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Cookie, Authorization 以外にも、ACL 判定結果 (特定の IP 帯のみ許可するケースなど)、端末種別、CORS の Origin ヘッダ、URL スキーム、リクエストメソッドなども注意する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;オリジンを信用しない&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;常に適切な実装 (Cache-control ヘッダ等) はされていないと前提する&lt;/li&gt;&#xA;&lt;li&gt;中間キャッシュ側でコントロールする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Allowlist&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;「デフォルトはすべてキャッシュ」ではなく、キャッシュ対象を Allowlist で明示する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;キャッシュキーとセカンダリキー (Vary ヘッダ)&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;Vary レスポンスヘッダはキャッシュのセカンダリーキーを指定する。これは次のようにキャッシュキーの小分類としてセカンダリーキーを持つという理解に等しい&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-js&#34;&gt;// Vary: Accept-Encoding の場合の概念データ構造&#xA;{&#xA;    &amp;quot;host + path&amp;quot;: {&#xA;        &amp;quot;gzip&amp;quot;: &amp;quot;content1&amp;quot;,&#xA;        &amp;quot;none&amp;quot;: &amp;quot;content2&amp;quot;&#xA;    }&#xA;}&#xA;&#xA;&#xA;// 仮に Accept-Encoding をキャッシュキーとした場合の概念データ構造&#xA;{&#xA;    &amp;quot;host + path + gzip&amp;quot;: &amp;quot;content1&amp;quot;,&#xA;    &amp;quot;host + path + none&amp;quot;: &amp;quot;content2&amp;quot;,&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;各要素の出所で整理すると&lt;/li&gt;&#xA;&lt;li&gt;キャッシュキー: クライアント (リクエストの host, path)&lt;/li&gt;&#xA;&lt;li&gt;セカンダリキー&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;指定: サーバ (レスポンスの Vary ヘッダ)&lt;/li&gt;&#xA;&lt;li&gt;値: クライアント (リクエストの Vary で指定されたヘッダ。Accept-Encoding 等)&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;キャッシュ箇所ごとの、キャッシュキーとセカンダリキーの挙動変更可能性で整理すると&lt;/li&gt;&#xA;&lt;li&gt;中間 (CDN, Proxy)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;キャッシュキーを変更できる&lt;/li&gt;&#xA;&lt;li&gt;セカンダリキーを Vary で変更できる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ローカルキャッシュ (ブラウザ等)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;キャッシュキーは変更できず、原則 host + path でキャッシュされる&lt;/li&gt;&#xA;&lt;li&gt;セカンダリキーを Vary で変更できる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;以上を踏まえて、host + path (+ method など) 以外の情報でレスポンスが変わるコンテンツの場合、Vary を指定しないとローカルキャッシュがうまく扱えない。反対に (各クライアント視点で) host + path でレスポンスが変わることが無ければキャッシュキーで問題ない&lt;/li&gt;&#xA;&lt;li&gt;例えば、pc/sp でコンテンツが切り替わるケース。pc/sp は user-agent で判定&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;中間から見ると pc/sp 区別ごとにコンテンツが変わる&lt;/li&gt;&#xA;&lt;li&gt;ローカルのクライアントから見ると、意図的でなければ pc/sp (user-agent) が変わることはない&lt;/li&gt;&#xA;&lt;li&gt;よって中間で pc/sp の判定結果をキャッシュキーに持たせるといった対応でよい&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;例えば CORS&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;リクエスト元の Origin ヘッダによってコンテンツが変わる (コンテンツが返されたり拒否されたりする)&lt;/li&gt;&#xA;&lt;li&gt;中間から見ると Origin によってコンテンツが変わるので、キャッシュキーかセカンダリキーのどちらかの対応が必須&lt;/li&gt;&#xA;&lt;li&gt;ローカルから見ると、Origin はアクセス元によって変わるので、ローカルでも Origin ごとのキャッシュが必要。これが実現できるのは Vary を用いたセカンダリキーの指定のみ&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;TTL&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;クライアントキャッシュの TTL の決め方&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;短いところから徐々に伸ばしていくのが運用しやすい&lt;/li&gt;&#xA;&lt;li&gt;クライアントのセッションの持続時間と間隔から決めていく&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;まずは平均のセッション継続時間キャッシュが保つように設定&lt;/li&gt;&#xA;&lt;li&gt;問題なければセッション間隔の時間から、次のセッションでもキャッシュが効くように伸ばす&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;長時間のキャッシュが不可能な場合&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;多少古いコンテンツが出ても良いなら短い max-age&lt;/li&gt;&#xA;&lt;li&gt;そうでなければ no-cache&lt;/li&gt;&#xA;&lt;li&gt;いずれも頻繁に条件付きリクエストで検証が行われる動きになる。つまりリクエスト数う自体はかわらなくても、リクエストボディの転送を抑えられる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;中間キャッシュの TTL の決め方&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;全体の rps から決めていく&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;10rps なら数分のキャッシュでも効果が高い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ローカルキャッシュとのバランスを取る&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;中間でキャッシュしすぎるとローカルキャッシュの残 TTL が減り、結果として中間で受ける総リクエストは増える&lt;/li&gt;&#xA;&lt;li&gt;s-maxage を使いバランスを取る&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;通常は TTL = max-age - Age と考える。Expires では Expires - Date となるが、基本的には max-age の利用が推奨&lt;/li&gt;&#xA;&lt;li&gt;stale-while-revalidate の決め方&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;TTL (max-age) とあわせて考える。TTL の一部を stale-while-revalidate に割り当て、TTL はその分短くする&lt;/li&gt;&#xA;&lt;li&gt;その他には、リクエストの頻度やコンテンツの生成にかかる時間を考慮して決める&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;stale-if-error の決め方&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;あくまでエラー時の動作なので、TTL から割り当てるのではなく追加で指定する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;max-age=0 は「常に stale キャッシュを保存する」という意味になる。キャッシュさせたくない場合は前述の &lt;code&gt;Cache-Control: private, no-store, no-cache, must-revalidate&lt;/code&gt; を使う&lt;/li&gt;&#xA;&lt;li&gt;エラーキャッシュ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;バグなどで一時的にコンテンツが 404 になった場合、キャッシュされないとオリジンにリクエストが殺到してしまう&lt;/li&gt;&#xA;&lt;li&gt;エラーキャッシュ・ネガティブキャッシュをするとこうした事態を緩和できる&lt;/li&gt;&#xA;&lt;li&gt;ただしエラーキャッシュの TTL が長すぎると復旧が遅れる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Invalidation&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;無効化と削除&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;無効化&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;キャッシュを stale にする。stale キャッシュは使われないが、revalidate 中やエラー時には使われる、またキャッシュストアには暫く残る&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;削除&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;確実にキャッシュされたコンテンツを削除する&lt;/li&gt;&#xA;&lt;li&gt;ほとんどは無効化で問題ないが、例えば著作権侵害コンテンツの削除など、削除が必要なユースケースもある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;手法&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;cache busting&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;パスにタイムスタンプを含めるなど、キャッシュキー事態を更新ごとに変える&lt;/li&gt;&#xA;&lt;li&gt;多くのケースで必要十分な方法&lt;/li&gt;&#xA;&lt;li&gt;無効化に属する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;cdn の機能での削除&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;無効化なのか削除なのかは確認する&lt;/li&gt;&#xA;&lt;li&gt;最近は各社高速化されたが、コンピューティングリソース的には重いオペレーションで課金対象であることも多い。削除に依存した運用設計よりも、TTL をうまく調整することでコントロールできるならそのほうが良い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;削除時はオリジンへのリクエスト急増に注意する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;上流 (オリジン側) から順に削除していく。上段の中間キャッシュが更新されてから下段のキャッシュを削除するなど&lt;/li&gt;&#xA;&lt;li&gt;一度に複数コンテンツを削除するのではなく、削除タイミングをばらけさせる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Topics: POST リクエストのキャッシュ&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;考慮事項が多く基本的には推奨されない&lt;/li&gt;&#xA;&lt;li&gt;キャッシュする場合の注意点&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;キャッシュキーを注意深く選択することは GET と同じ&lt;/li&gt;&#xA;&lt;li&gt;リクエストボディの取り扱い&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;application/x-www-form-urlencoded、multipart/form-data などパースが (中間では) 容易でなかったりサイズが大きくなりがち&lt;/li&gt;&#xA;&lt;li&gt;中間ではリクエストボディは無視する、ボディのハッシュ値をキーとする、application/x-www-form-urlencoded の時のみクエリ文字列と同等の扱いでキーとする、といった対応を行う&lt;/li&gt;&#xA;&lt;li&gt;一定のボディサイズ以上はキャッシュしない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;multipart/formdata のリクエストはブラウザによって形式が違うので「リクエストボディは無視する」の対応しかできない&lt;/li&gt;&#xA;&lt;li&gt;100-continue を利用した分割リクエストはキャッシュできないので、そのようなクライアント実装を避ける&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;100-continue はリクエストボディを送信する前にヘッダだけを送信し、サーバが受け入れ可能かどうかを確認するための仕組み&lt;/li&gt;&#xA;&lt;li&gt;例えば AWS ELB は 100-continue を受け取ると即座に 100 Continue を返すため、オリジンでは検知できない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;Topics: キャッシュフレンドリなパス設計&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;/cache/user&lt;/code&gt; のようにパス自体にキャッシュキーが何かという情報を含めてしまうとわかりやすい&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;中間では &lt;code&gt;/cache/user&lt;/code&gt; のリクエストは、例えばクッキーから user_id を取得してキャッシュキーに入れるといった実装をする&lt;/li&gt;&#xA;&lt;li&gt;オリジンはその前提で実装する。この知識がパスから明確にわかるのが利点&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;5. より効果的・大規模な配信とキャッシュ&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;メトリクス&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;キャッシュヒット率が導出しやすく使いやすい指標&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ただし次のようなケースはヒット率だけを追うと隠されてしまう&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;全ユーザー共通の静的画像とユーザーごとの動的画像&lt;/li&gt;&#xA;&lt;li&gt;単純な実装だと当然前者のキャッシュヒット率が高い&lt;/li&gt;&#xA;&lt;li&gt;後者はよりオリジンの CPU リソースを使うので、ヒット率に寄らずケアすることでシステム全体の安定性を増すことができる可能性がある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Proxy/CDN システムの抽象化&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;figure&gt;&lt;img src=images/cdn-system-abstraction.png /&gt;&lt;figcaption&gt;p210 図5.1 Proxy/CDNの基本的な処理フロー より引用&lt;/figcaption&gt;&lt;/figure&gt;&lt;/li&gt;&#xA;&lt;li&gt;コンポーネント&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Client Trx, Origin Trx と、対クライアントと対オリジンでトランザクション (プロセスくらいのイメージで良い) が分かれており、それぞれ非同期に動作する。非同期な関係の代表例が stale-while-revalidate&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;処理ステップ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;以下のステップに分かれている。&lt;code&gt;Rx*&lt;/code&gt;, &lt;code&gt;Tx*&lt;/code&gt; はそれぞれフックポイントのようなもので、個別の処理を記述できる&lt;/li&gt;&#xA;&lt;li&gt;Receiver Request (RxReq)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;フックしてACL 処理、キャッシュキー操作、セカンダリキー操作、Cookie 削除、クエリソートなど&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Cache lookup&lt;/li&gt;&#xA;&lt;li&gt;Wait for cache&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;フックポイントではない&lt;/li&gt;&#xA;&lt;li&gt;一定時間待機することで、キャッシュがない場合の同時リクエストが複数オリジンに飛ぶことを防いでいる&lt;/li&gt;&#xA;&lt;li&gt;一方で、キャッシュしないリクエストが誤ってこのフローに入ると、不要な wait が生じるため注意する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Transmitter Request (TxReq)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;フックして複数オリジンがある場合の選択、キャッシュキーに影響を与えないオリジン問い合わせのホスト名・パス名変更など&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Receiver Response (RxResp)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;フックしてカスタムした TTL の設定、キャッシュ前に Set-Cookie を削除するなどキャッシュするオブジェクトの操作、Vary ヘッダの変更など&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Transmitter Response (TxResp)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;CORS などキャッシュは変わらないがクライアントごとにヘッダを変える場合の対応、内部用の不要ヘッダの削除など&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;[memo] これらのフックを組み合わせて、例えばヘッダをパース・カスタムの Vary ヘッダ生成といった、それなりに複雑な処理を実装する例が挙げられているが、第一印象としては認知負荷の高さが気になった。やむを得ないケースを除き、どの程度の処理をどのレイヤーで行うのが妥当なのかの基準があれば知りたい。一方で組織の境界やシステムの境界から、こうした処理を中間側で行った方がベターなケースも容易に想定できる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;同一 URL で複数種類のキャッシュがあるケースの対応方法比較&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;キャッシュキーを変える (パスに pc/sp と入れるなど)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;素直に単に URL を指定してもキャッシュ削除ができず、工夫が必要&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Vary でセカンダリキーを指定する (Vary に x-device を入れるなど)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;RxReq + RxResp + TxResp にてフックの実装が必要&lt;/li&gt;&#xA;&lt;li&gt;中間で追加したヘッダがオリジンに渡る&lt;/li&gt;&#xA;&lt;li&gt;クライアントにも Vary ヘッダが渡る&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;クエリパラメータを追加する (URL に &lt;code&gt;?device=pc&lt;/code&gt; など)&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;中間で追加したパラメータがオリジンに渡る&lt;/li&gt;&#xA;&lt;li&gt;削除時にクエリパラメータの指定も必要。パラメータのカーディナリティが高い場合は注意&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;キャッシュの効率化&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;クエリパラメータの正規化、ソート、不要削除&lt;/li&gt;&#xA;&lt;li&gt;セカンダリキーの正規化&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば Accept-encoding について、サーバが対応しているのは gzip のみの場合、そのままではなく gzip 値の有無をセカンダリキーにする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;キャッシュ対象の優先度付け&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;平均レスポンスタイム x 一定期間でのリクエスト数 が高いものを優先する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;動的コンテンツ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;定義&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;TTL を概ね1時間以上にできる、またはTTL を明確に定められるものは静的&lt;/li&gt;&#xA;&lt;li&gt;動的コンテンツは TTL が要件上決めづらい、あるいはそもそもキャッシュができないため難しい&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;キャッシュの難しさの分類&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;コンテンツが個別のライフサイクルを持つサブコンテンツから構成されている&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;分割してキャッシュする&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ESI&lt;/li&gt;&#xA;&lt;li&gt;クライアントスクリプトで実装&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ユーザー単位など同一の URL でもステートフルな別の挙動をする&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;同上&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;リクエストのたびに取得が必要だったり、極めて高いリアルタイム性が必要&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;キャッシュできないのはこのパターンだけ&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;API で動的生成するコンテンツの場合、検証リクエストへの対応が高コストになることが多い（ETag を生成しようにも、コンテンツ自体を普段通り組み立てないといけないなど）。そのため no-cache + 検証リクエストよりは、ごく短い TTL を設ける、CDN ではキャッシュし終端クライアントには no-cache を返すがオリジンには普通にリクエストする、といったアプローチの方が取りやすい&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ESI &lt;a href=&#34;https://www.w3.org/TR/esi-lang/&#34;&gt;https://www.w3.org/TR/esi-lang/&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;中間でコンテンツを結合しクライアントに返す&lt;/li&gt;&#xA;&lt;li&gt;一般的に平均応答時間（TTFB）は短縮し、平均ダウンロード時間が伸びる動きになる&lt;/li&gt;&#xA;&lt;li&gt;アプリケーション側の変更が必要になる、各所での結合処理にかかるオーバーヘッドが発生するといったデメリットがある&lt;/li&gt;&#xA;&lt;li&gt;まずはヘッドとボディを分けるだけでも、ブラウザがヘッドにあるコンテンツを先にロードできてメリットがある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Topics: Vary ヘッダが自動付与されるケース&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば Apache でオリジンへの直接アクセスを禁止するために &lt;code&gt;Require expr %{HTTP_REFERER} == “foobar”&lt;/code&gt; と設定すると、Vary: referrer が自動で返される&lt;/li&gt;&#xA;&lt;li&gt;S3 で CORS の設定を行うと Allow origin などが含まれる Vary ヘッダが自動的に返される&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Topics: ローカルキャッシュの削除&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;現実的な手法としては Cache Busting&lt;/li&gt;&#xA;&lt;li&gt;no-store で毎回検証リクエストをさせる方法もある&lt;/li&gt;&#xA;&lt;li&gt;更新が少ない静的ファイルなどは前者、よりリアルタイム性の高いニュースフィードなどは後者が適切&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Topics: 多段 Proxy&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;主に CDN を利用せず自前構築する際のノウハウ&lt;/li&gt;&#xA;&lt;li&gt;（オリジンへのリクエスト元を集約することで）キャッシュデータの整合性を保ちやすくする、可用性が高まる、全体としてのストレージ容量を（スケールアウト方向に）増やしやすいといったメリットがある&lt;/li&gt;&#xA;&lt;li&gt;一方で TTL の取り扱いなど考慮点も増える&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Topics: ドメイン分割の使い所&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;HTTP/1 時代にあったブラウザのドメインごとの接続数上限を緩和するためのドメインシャーディングは、HTTP/2 以降で不要になった&lt;/li&gt;&#xA;&lt;li&gt;一方でサービスの拡張しやすさのため静的・動的ファイルでドメインを分けておく（あとから CDN に載せやすい）、ZoneApex 問題対策といった場面でドメイン分割が有効なケースがある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;6. CDN を活用する&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;CDN の特徴&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;「分散されたネットワーク品質の良いコンピューティングリソースのプラットフォーム」&lt;/li&gt;&#xA;&lt;li&gt;キャッシュは機能のうちの一部。キャッシュがなくても、例えば中間 - オリジン間の接続が使い回されることで、キャッシュミスの場合でも直接オリジンアクセスよりも速いこともある&lt;/li&gt;&#xA;&lt;li&gt;DDoS 対策や WAF などセキュリティ機能、エッジコンピューティング等&lt;/li&gt;&#xA;&lt;li&gt;全面的に導入しなくても、地理的に遠いクライアントへの配信や突発的なトラフィック増加への対応などを賄う目的でも利用できる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;選定のポイント&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;対応地域&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;特に中国本土やロシア等の旧共産圏は対応の厚さや価格が異なるケースがある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ピーク帯域&lt;/li&gt;&#xA;&lt;li&gt;キャッシュ制御の方法 (VCL, Lambda@Edge など)&lt;/li&gt;&#xA;&lt;li&gt;TLS 対応 (Pinning のケアなど)&lt;/li&gt;&#xA;&lt;li&gt;キャッシュ消去の方法、Apex ドメイン対応、多段構成が可能かなど&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;利用時に気をつけるポイント&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;キャッシュされない設定を把握する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Cache-Control の解釈は各ベンダーで異なる。特にキャッシュしないケースは情報漏洩リスクにつながるため念入りに確認する&lt;/li&gt;&#xA;&lt;li&gt;オリジンがダウンしている場合にキャッシュを返さないか、同着リクエストの際にキャッシュを返さないかといったエッジケースも確認すると良い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;クエリパラメータ、Vary ヘッダの解釈&lt;/li&gt;&#xA;&lt;li&gt;トラフィック (帯域幅) やコンテンツサイズの上限と超えた場合の挙動&lt;/li&gt;&#xA;&lt;li&gt;デバッグ機能&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;独自のヘッダを付けることでデバッグ可能なベンダーもある&lt;/li&gt;&#xA;&lt;li&gt;本番ではオフにしないと、例えばオリジンの IP アドレスが漏れるなどのリスクがある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;障害&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;分類&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ファーストマイル（オリジン、CDN間）&lt;/li&gt;&#xA;&lt;li&gt;ミドルマイル（CDN内）&lt;/li&gt;&#xA;&lt;li&gt;エッジ自体&lt;/li&gt;&#xA;&lt;li&gt;ラストマイル（エッジ、クライアント間）&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;切り分け&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;キャッシュされているオブジェクトのリクエストに失敗し、かつ別 ISP からのリクエストも失敗するならエッジ障害、別 ISP から成功ならラストマイル&lt;/li&gt;&#xA;&lt;li&gt;キャッシュされていないオブジェクトのリクエストに失敗し、キャッシュされていると成功するならファーストマイルかミドルマイル&lt;/li&gt;&#xA;&lt;li&gt;ファースト、ミドルの切り分けは単純化できない&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ISP、リージョン、CDN 単位でのメトリクス確認、ステータスページ、SNS で他社障害の確認&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;キャッシュ異常&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;TLS 通信にし経路上の不確定要素を排除&lt;/li&gt;&#xA;&lt;li&gt;キャッシュとオリジンの比較&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Topics: &lt;a href=&#34;https://cpdos.org/&#34;&gt;CPDoS&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Cache Poisoning Denial of Service&lt;/li&gt;&#xA;&lt;li&gt;CDN 上のキャッシュを汚染しサービスを利用できなくさせる手法&lt;/li&gt;&#xA;&lt;li&gt;例えば許容できるヘッダサイズが オリジン &amp;lt; CDN だった場合に、大きなヘッダサイズのリクエストを送り、CDN 側にエラーレスポンスをキャッシュさせる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;RFC 通りであれば 400 Bad Request はキャッシュされないが、キャッシュする CDN ベンダーの場合この攻撃が成立する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Topics: 動的コンテンツのキャッシュ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;キャッシュ戦略を適切に設計すれば問題ない&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;静的コンテンツでも慎重に検討すべきなので、動的だから特別考え方が変わるわけではない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;キャッシュせず CDN を通すだけでもセキュリティなどメリットがある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;7. 自作 CDN&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;必要性&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;超大規模サービスで自社構築の方がコストやカスタマイズ性にメリットがある&lt;/li&gt;&#xA;&lt;li&gt;CDN を契約する予算がなく費用を抑えたい小規模サービス&lt;/li&gt;&#xA;&lt;li&gt;ハイブリッド構成を取りたい場合&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;静的コンテンツのオフロードのみ CDN を使う&lt;/li&gt;&#xA;&lt;li&gt;基本は自社配信を使い、突発的なトラフィックのみ CDN にオフロードする&lt;/li&gt;&#xA;&lt;li&gt;コンテンツを一貫させるために CDN と自社配信の多段構成にする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;div class=&#34;amazlet-box&#34; style=&#34;margin-bottom:0px;&#34;&gt;&lt;div class=&#34;amazlet-image&#34; style=&#34;float:left;margin:0px 12px 1px 0px;&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4297119250/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;&lt;img src=&#34;https://m.media-amazon.com/images/I/814o9cRmwqL._SY466_.jpg&#34; alt=&#34;Web配信の技術―HTTPキャッシュ・リバースプロキシ・CDNを活用する&#34; style=&#34;border: none; width: 113px;&#34; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-info&#34; style=&#34;line-height:120%; margin-bottom: 10px&#34;&gt;&lt;div class=&#34;amazlet-name&#34; style=&#34;margin-bottom:10px;line-height:120%&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4297119250/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Web配信の技術―HTTPキャッシュ・リバースプロキシ・CDNを活用する&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-detail&#34;&gt;田中 祥平 (著)&lt;br/&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-sub-info&#34; style=&#34;float: left;&#34;&gt;&lt;div class=&#34;amazlet-link&#34; style=&#34;margin-top: 5px&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4297119250/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Amazon.co.jpで詳細を見る&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-footer&#34; style=&#34;clear: left&#34;&gt;&lt;/div&gt;&lt;/div&gt;&#xA;</description>
      <author>Kosei Moriyama</author>
      <pubDate>Tue, 21 May 2024 20:30:00 +0900</pubDate>
    </item>
    <item>
      <title>MySQL オンラインスキーマ変更ツールの spirit と gh-ost</title>
      <link>https://please-sleep.cou929.nu/mysql-online-schema-change-spirit-ghost.html</link>
      <description>&lt;p&gt;MySQL のスキーマ変更は、データ量が大きくなると一般的に時間がかかり、その間の DML の実行にも影響が出る。このような状況でサービスを止めずにスキーマ変更をするためのツールとして &lt;a href=&#34;https://github.com/github/gh-ost&#34;&gt;github/gh-ost&lt;/a&gt; が有名だが、&lt;a href=&#34;https://github.com/cashapp/spirit&#34;&gt;cashapp/spirit&lt;/a&gt; は gh-ost をベースにして &lt;a href=&#34;https://cash.app/&#34;&gt;Cash App&lt;/a&gt; チーム (旧 Square、現 &lt;a href=&#34;https://block.xyz/&#34;&gt;Block, Inc.&lt;/a&gt;) のニーズに合わせて作られた新しい [1] オンラインスキーマ変更ツールだ。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;[1] 紹介ブログ &lt;a href=&#34;https://code.cash.app/introducing-spirit&#34;&gt;Introducing Spirit | Cash App Code Blog&lt;/a&gt; が書かれたのが 2023 年 9 月。&lt;/p&gt;&#xA;&#xA;&lt;h1&gt;spirit の特徴&lt;/h1&gt;&#xA;&#xA;&lt;p&gt;基本的なアプローチは gh-ost も spirit も同じだが、より大きなデータサイズ [2] のユースケースに対応した機能が追加されている&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;データコピーを並列に行う&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;gh-ost は一並列のため、この規模のデータコピーには数週間という水準の時間がかかる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;処理途中からの resume に対応&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;現代的なインフラはおおよそ k8s 上に構築されており、disposable な作りになっていた方が扱いやすい&lt;/li&gt;&#xA;&lt;li&gt;前述のように数日程度では済まない長い時間がかかる。この規模になると中断後、最初から処理を再開するのはかなり運用負荷が高い&lt;/li&gt;&#xA;&lt;li&gt;この機能によりマイグレーション中だけ DB インスタンスをスケールアップしておき、一時中断して戻すなど柔軟な対応も可能になる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;README にはベンチマークも記載されている。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Aurora v3 上の 10TB の &lt;a href=&#34;https://square.github.io/finch/benchmark/examples/#xfer&#34;&gt;finch.xfers&lt;/a&gt; テーブルのマイグレーションが 2.7 日で完了&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;gh-ost だと 10 日でも完了せずキャンセルした&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;テーブルはアイドル状態だったので、実際のワークロードではより差が開くと予想される&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;m1 mac 上でのマイクロベンチで比較&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;finch.balances (800MB/1M rows), idle load&lt;/code&gt; では spirit は gh-ost の約 2 倍高速&lt;/li&gt;&#xA;&lt;li&gt;同じテーブルでベンチマークツールでクエリを発行しながらマイグレーションしたところ、差は 8.5 倍まで広がった&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;[2] spirit は &lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/README.md#performance&#34;&gt;10 TiB 規模のテーブルのマイグレーションを 5 日以内に完了させる&lt;/a&gt; のが性能の目安になっている。対して &lt;a href=&#34;https://hackmysql.com/post/future-of-mysql-schema-change-spirit/&#34;&gt;gh-ost や pt-ost は ~1TiB 程度の規模が想定されていて、かつそれでも 1 週間以上かかることもある&lt;/a&gt; という水準らしい。&lt;/p&gt;&#xA;&#xA;&lt;h1&gt;gh-ost の実装&lt;/h1&gt;&#xA;&#xA;&lt;p&gt;gh-ost はおおまかに次の戦略でオンラインスキーマ変更を実現している。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;img src=&#34;images/gh-ost-general-flow.webp&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;&#xA;&#xA;&lt;figure&gt;&#xA;&lt;figcaption&gt;&lt;a href=&#34;https://github.blog/2016-08-01-gh-ost-github-s-online-migration-tool-for-mysql/&#34;&gt;gh-ost: GitHub&#39;s online schema migration tool for MySQL - The GitHub Blog&lt;/a&gt;から引用&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;セットアップ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;対象のテーブルと同じ定義のテーブルを &lt;a href=&#34;https://github.com/github/gh-ost/blob/59fd18dc90967c2540d7652b42fbd19fbe5bcb47/go/logic/applier.go#L223&#34;&gt;CREATE TABLE &amp;hellip; LIKE &amp;hellip;&lt;/a&gt; 文で作成し、後者に &lt;a href=&#34;https://github.com/github/gh-ost/blob/59fd18dc90967c2540d7652b42fbd19fbe5bcb47/go/logic/applier.go#L263&#34;&gt;ALTER を適用する&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;gh-ost では後者を ghost table と呼んでいる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;データコピー&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;元テーブルのデータを ghost デーブルにコピーする&lt;/li&gt;&#xA;&lt;li&gt;この時 gh-ost は binlog を購読し、ghost テーブルが最新の状態に常に（極力小さいラグで）追従するようにしている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;カットオーバー&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ghost テーブルと元テーブルを RENAME TABLE 文で入れ替える&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;主要な処理はコントローラー的役割の &lt;a href=&#34;https://github.com/github/gh-ost/blob/59fd18dc90967c2540d7652b42fbd19fbe5bcb47/go/logic/migrator.go#L327&#34;&gt;logic.Migrator.Migrate&lt;/a&gt; から順に呼び出されている。コードリーディングはここから始めると良い。&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;データコピー&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;データコピーは logic.Migrator と &lt;a href=&#34;https://github.com/github/gh-ost/blob/59fd18dc90967c2540d7652b42fbd19fbe5bcb47/go/logic/applier.go#L56&#34;&gt;logic.Applyer&lt;/a&gt; クラスが主に担当する&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/github/gh-ost/blob/59fd18dc90967c2540d7652b42fbd19fbe5bcb47/go/sql/builder.go#L224-L237&#34;&gt;&lt;code&gt;INSERT IGNORE INTO … SELECT …&lt;/code&gt; 文で&lt;/a&gt; 元テーブルから ghost テーブルへデータを移していく&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;コピーはチャンクという単位で処理されていくが、チャンクの大きさは &lt;a href=&#34;https://github.com/github/gh-ost/blob/59fd18dc90967c2540d7652b42fbd19fbe5bcb47/go/base/context.go#L274&#34;&gt;固定値&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;同時に自身をレプリカとして登録し binlog を購読する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;binlog を購読するのは &lt;a href=&#34;https://github.com/github/gh-ost/blob/59fd18dc90967c2540d7652b42fbd19fbe5bcb47/go/logic/streamer.go#L36&#34;&gt;logic.EventStreamer&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;EventStreamer に &lt;a href=&#34;https://github.com/github/gh-ost/blob/59fd18dc90967c2540d7652b42fbd19fbe5bcb47/go/logic/migrator.go#L1092&#34;&gt;イベントリスナーを登録&lt;/a&gt; する。リスナーは binlog の DML イベントを &lt;code&gt;applyEventsQueue&lt;/code&gt; に登録する&lt;/li&gt;&#xA;&lt;li&gt;定期的にキューから &lt;a href=&#34;https://github.com/github/gh-ost/blob/59fd18dc90967c2540d7652b42fbd19fbe5bcb47/go/logic/migrator.go#L1297&#34;&gt;イベントを取り出し&lt;/a&gt;、変更を &lt;a href=&#34;https://github.com/github/gh-ost/blob/59fd18dc90967c2540d7652b42fbd19fbe5bcb47/go/logic/applier.go#L1140&#34;&gt;ghost テーブルに適用&lt;/a&gt; していく&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;EventStreamer が実際に binlog を読み込む部分は &lt;a href=&#34;https://github.com/go-mysql-org/go-mysql&#34;&gt;go-mysql-org/go-mysql&lt;/a&gt; の &lt;a href=&#34;https://github.com/go-mysql-org/go-mysql/tree/master/replication&#34;&gt;replication&lt;/a&gt; パッケージを利用している。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;レプリケーション、binlog の購読、MySQL のクライアント、サーバーのプロトコル解釈など、MySQL 周辺ツールを作るのに便利でコアなツールが揃っている&lt;/li&gt;&#xA;&lt;li&gt;いずれも Pure Go 実装&lt;/li&gt;&#xA;&lt;li&gt;もともとは &lt;a href=&#34;https://twitter.com/siddontang&#34;&gt;現 PingCap VPoE のエンジニア&lt;/a&gt; によって開発されたライブラリの模様&lt;/li&gt;&#xA;&lt;li&gt;gh-ost は replication を使っているが、spirit はこれよりもさらにハイレベルのインタフェースを提供する canal を利用している&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;それぞれのツールは別言語に参考元と思われるものがある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/alibaba/canal&#34;&gt;alibaba/canal: 阿里巴巴 MySQL binlog 增量订阅&amp;amp;消费组件&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/julien-duponchelle/python-mysql-replication&#34;&gt;julien-duponchelle/python-mysql-replication: Pure Python Implementation of MySQL replication protocol build on top of PyMYSQL&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;カットオーバー&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;ghost テーブルが元テーブルに追いついたら、両テーブルの切り替えを行う。この作業をカットオーバーと呼ぶ。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;gh-ost がそれ以前のツールよりも優れているのは、binlog 購読によるキャッチアップがトリガーなどによる実装に比べて高速低コストにできることで、そのおかげでカットオーバー時のロック取得が短時間で済むことだと思われる。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;gh-ost のカットオーバーは次のように少々複雑な手順を踏む。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;元テーブルと swap 用の一時テーブル (名前は &lt;code&gt;元テーブル_old&lt;/code&gt;) にロックをかける&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/github/gh-ost/blob/59fd18dc90967c2540d7652b42fbd19fbe5bcb47/go/logic/applier.go#L982&#34;&gt;LOCK TABLE 元テーブル WRITE, 元テーブル_old WRITE&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;binlog 購読バッファをフラッシュし、ghost テーブルが元テーブルに追いついたことを確認する&lt;/li&gt;&#xA;&lt;li&gt;テーブルのリネームを行う&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/github/gh-ost/blob/59fd18dc90967c2540d7652b42fbd19fbe5bcb47/go/logic/applier.go#L1063&#34;&gt;RENAME TABLE 元テーブル TO 元テーブル_old, ghost テーブル TO 元テーブル&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;この時点では RENAME 処理はロックでブロックされている。ただしロック中に発行された INSERT などの DML よりも高い優先度になっている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/github/gh-ost/blob/59fd18dc90967c2540d7652b42fbd19fbe5bcb47/go/logic/applier.go#L868&#34;&gt;processlist に RENAME TABLE が出てくることを確認する&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;元テーブル_old&lt;/code&gt; を &lt;a href=&#34;https://github.com/github/gh-ost/blob/59fd18dc90967c2540d7652b42fbd19fbe5bcb47/go/logic/applier.go#L1013&#34;&gt;DROP する&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;この時点ではロックでブロッキングされている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;最後に &lt;a href=&#34;https://github.com/github/gh-ost/blob/59fd18dc90967c2540d7652b42fbd19fbe5bcb47/go/logic/applier.go#L1030&#34;&gt;ロックを開放する&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;まず RENAME が実行される。ロック中に発行され待たされていた DML はその後リネーム後のテーブルに対して実行される&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;詳細は次の issue にドキュメント化されている。&lt;a href=&#34;https://github.com/github/gh-ost/issues/82&#34;&gt;Describing safe, blocking, atomic, pure-mysql cut-over phase · Issue #82 · github/gh-ost&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;これは MySQL 8.0.13 以前はテーブルロック中にリネームができなかったことに起因している。MySQL 8.0.13 以降はこの制約がないので、ロック・リネーム・アンロックというシンプルなフローで良くなるが、gh-ost は古い MySQL バージョンをサポートしているのでまだ実装されていない。&lt;/p&gt;&#xA;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://dev.mysql.com/doc/refman/8.0/en/rename-table.html&#34;&gt;MySQL :: MySQL 8.0 Reference Manual :: 13.1.36 RENAME TABLE Statement&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;As of MySQL 8.0.13, you can rename tables locked with a LOCK TABLES statement, provided that they are locked with a WRITE lock or are the product of renaming WRITE-locked tables from earlier steps in a multiple-table rename operation.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&#xA;&lt;p&gt;なお以前はリネームのステップが (1) 元テーブルを一時テーブルにリネーム (2) ghostテーブルを元テーブルにリネームと 2 ステップに分かれていたらしい [3]。(1) と (2) の間に何らかの理由でロックを取っていたセッションが死ぬと、元テーブルがなくなった状態になってしまい事故となる。現行アルゴリズムはリネームがアトミックに行われるのでこの懸念はない。またこの点に限らず、フローのどの時点でクラッシュしても、データ一貫性が壊れる事故は起きなくなっている。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;[3] &lt;a href=&#34;https://github.com/github/gh-ost/issues/65&#34;&gt;Describing safe, blocking, pure-mysql cut-over phase · Issue #65 · github/gh-ost&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;h1&gt;spirit の実装&lt;/h1&gt;&#xA;&#xA;&lt;p&gt;基本的なアプローチは gh-ost と同じで、実装もセットアップ、データコピー、カットオーバーの流れでされている。ただし以下のアイデアがデータコピーの実装に追加されている。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;並列化によるデータコピーの短縮&lt;/li&gt;&#xA;&lt;li&gt;データコピーの途中再開&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;また MySQL 8 以降を原則サポート対象としていたり、gh-ost にある細かなオプションの対応が省かれていることで、コア部分以外のコード量が少なくすっきりしている。全体的にドキュメントも充実している。もしスキーマ変更ツールの実装を読んでみたい人がいれば、先に spirit から読むと理解しやすいと思う。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/migration/runner.go#L153&#34;&gt;migration.Runner.Run&lt;/a&gt; がコントローラー的なメソッドで、ここから順に追っていくと良い。また ghost テーブルは spirit では New Table と呼ばれている。&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;データコピー&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;spirit では &lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/row/copier.go#L33&#34;&gt;row.Copier&lt;/a&gt; クラスが担当する。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Chunker が元テーブル全体をいくつかのチャンクに分割する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;チャンクサイズは &lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/table/chunker_optimistic.go#L208&#34;&gt;処理時間に応じて動的に変更している&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Copier が &lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/row/copier.go#L186&#34;&gt;複数のスレッドを起動&lt;/a&gt; し、それぞれに &lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/row/copier.go#L188&#34;&gt;担当するチャンクを割り当てる&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/row/copier.go#L124&#34;&gt;INSERT IGNORE INTO &amp;hellip; SELECT &amp;hellip; FORCE INDEX (PRIMARY) WHERE &amp;hellip;&lt;/a&gt; で元テーブルから New テーブルにコピーする&lt;/li&gt;&#xA;&lt;li&gt;このときの WHERE 句は Chunker (&lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/table/chunk.go#L37&#34;&gt;Chunk オブジェクト&lt;/a&gt;) が生成する&lt;/li&gt;&#xA;&lt;li&gt;コピー完了後は &lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/table/chunker_optimistic.go#L280&#34;&gt;watermark を更新する&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;どこまでのコピーが済んだのかの記録&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;同時に binlog の購読も開始する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/repl/client.go#L45&#34;&gt;repl.Client&lt;/a&gt; が担当するが、後述のようにコアな実装は &lt;code&gt;go-mysql/canal&lt;/code&gt; に移譲している&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;repl.Client&lt;/code&gt; 自体を canal に &lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/repl/client.go#L266&#34;&gt;イベントハンドラの実装として登録&lt;/a&gt;、binlog イベントが &lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/repl/client.go#L52&#34;&gt;バッファ&lt;/a&gt; に貯められ、&lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/repl/client.go#L575&#34;&gt;定期的にフラッシュ&lt;/a&gt; される&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;以下の最適化を行っている&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;binlog イベント到着時、&lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/table/chunker_optimistic.go#L411&#34;&gt;watermark を確認&lt;/a&gt; し、&lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/repl/client.go#L136-L139&#34;&gt;コピー済みのレコードのみ更新する&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;未コピーのレコードは今後 copier スレッドがその時の最新状態をコピーしてくれるので、binlog のイベントは無視して良い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;binlog イベントを PK ごとのバッファにまとめ、重複排除をしている&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/repl/client.go#L52&#34;&gt;binlogChangeset&lt;/a&gt; という &lt;code&gt;map[string]bool&lt;/code&gt; という型の map で &lt;code&gt;string&lt;/code&gt; が PK に、bool が &lt;code&gt;delete か否か&lt;/code&gt; を表している&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;更新内容は保持しておらず、あくまで更新や削除があった PK のみを記録している&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;binlog イベント受信時、INSERT, UPDATE なら false で、DELETE なら true で &lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/repl/client.go#L655&#34;&gt;その PK のエントリを map に追加している&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/repl/client.go#L440&#34;&gt;フラッシュ時&lt;/a&gt; には delete の場合は &lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/repl/client.go#L515&#34;&gt;DELETE 文&lt;/a&gt; を、そうでなければ &lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/repl/client.go#L527&#34;&gt;REPLACE 文で、元テーブルのデータで新テーブルの該当レコードを置き換え&lt;/a&gt; している&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;その時の最新情報を元テーブルから取得しているので、そのため個別の binlog イベント内容は無視できる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;加えて &lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/repl/client.go#L496-L499&#34;&gt;反映クエリを並列実行し&lt;/a&gt; 高速化を図っている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;進捗状況は checkpoint テーブルに保存される&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;watermark や binlog の読み取り位置を &lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/migration/runner.go#L901&#34;&gt;定期的にこのテーブルに記録&lt;/a&gt; している&lt;/li&gt;&#xA;&lt;li&gt;resume する場合は &lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/migration/runner.go#L428&#34;&gt;セットアップ時にこのテーブルをチェック&lt;/a&gt; し、存在すれば &lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/migration/runner.go#L761&#34;&gt;進捗状況を読み取って&lt;/a&gt; 処理を再開する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/throttler/mysql80replica.go#L56&#34;&gt;replication lag を監視&lt;/a&gt; し、大きくなった場合はデータコピー処理をスロットリングする&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/throttler/mysql80replica.go#L14-L18&#34;&gt;performance_schema.replication_applier_status_by_worker&lt;/a&gt; を見ている&lt;/li&gt;&#xA;&lt;li&gt;似た機構は gh-ost にもある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;binlog の購読は gh-ost 同様 &lt;a href=&#34;https://github.com/go-mysql-org/go-mysql&#34;&gt;go-mysql-org/go-mysql&lt;/a&gt; を利用している。ただし replication ではなく &lt;a href=&#34;https://github.com/go-mysql-org/go-mysql/blob/b9563f7d56687408e9b2f8b2fe5f64687f6a3340/README.md#canal&#34;&gt;canal&lt;/a&gt; というよりハイレベルなライブラリを使っている。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;canal はある MySQL サーバーの変更を別のデータスストアに反映させるために使うことができる、汎用的なツール&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば &lt;a href=&#34;https://github.com/go-mysql-org/go-mysql-elasticsearch&#34;&gt;MySQL から elasticsearch へのデータ同期といったユースケース&lt;/a&gt; の実装に有用&lt;/li&gt;&#xA;&lt;li&gt;binlog イベントを受け取った際にやりたい処理をイベントハンドラとして登録するだけで、簡単に使える&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;カットオーバー&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;デフォルトでは MySQL 8 をサポート対象としているので、gh-ost に比べ &lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/migration/cutover.go#L117&#34;&gt;シンプルでストレートな実装&lt;/a&gt; になっている。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/migration/cutover.go#L36&#34;&gt;migration.Cutover&lt;/a&gt; が担当する&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/migration/cutover.go#L120&#34;&gt;元テーブルのロック&lt;/a&gt;、&lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/migration/cutover.go#L125&#34;&gt;binlog バッファのフラッシュ&lt;/a&gt; をしたあと、&lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/migration/cutover.go#L133&#34;&gt;RENAME TABLE 元テーブル TO _元テーブル_old, 新テーブル TO 元テーブル&lt;/a&gt; で入れ替える&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;フォールバックとして &lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/migration/cutover.go#L142&#34;&gt;gh-ost と同様のアルゴリズム&lt;/a&gt; も実装されている。&lt;/p&gt;&#xA;&#xA;&lt;h1&gt;ALGORITHM=INSTANT の試行&lt;/h1&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/cashapp/spirit/blob/fdbfa0baf31e9406227ae7fa9403c977189d715c/pkg/migration/runner.go#L382&#34;&gt;spirit&lt;/a&gt; にも &lt;a href=&#34;https://github.com/github/gh-ost/blob/59fd18dc90967c2540d7652b42fbd19fbe5bcb47/go/logic/applier.go#L213&#34;&gt;gh-ost&lt;/a&gt; にも、処理に入る前にまずは &lt;code&gt;ALGORITHM=INSTANT&lt;/code&gt; でのスキーマ変更ができないかチェックする仕組みが入っている。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;MySQL 8 から導入された &lt;code&gt;ALGORITHM=INSTANT&lt;/code&gt; は、メタデータの更新だけで ALTER 文が完了するため、非常に高速。ただし全てのスキーマ変更操作で可能なわけではなく制約がある。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;spirit も gh-ost も、重くリスクの高い作業を開始する前に &lt;code&gt;ALGORITHM=INSTANT&lt;/code&gt; を試みるのは理にかなっている。またこの機能は spirit 側から gh-ost に &lt;a href=&#34;https://github.com/github/gh-ost/pull/1201&#34;&gt;提案・移植&lt;/a&gt; してくれたらしい。&lt;/p&gt;&#xA;&#xA;&lt;h1&gt;PR&lt;/h1&gt;&#xA;&#xA;&lt;div class=&#34;amazlet-box&#34; style=&#34;margin-bottom:0px;&#34;&gt;&lt;div class=&#34;amazlet-image&#34; style=&#34;float:left;margin:0px 12px 1px 0px;&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/B09N5NWKR1/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;&lt;img src=&#34;https://m.media-amazon.com/images/I/8170eI9Ec0L._SY522_.jpg&#34; alt=&#34;Efficient MySQL Performance (English Edition)&#34; style=&#34;border: none; width: 113px;&#34; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-info&#34; style=&#34;line-height:120%; margin-bottom: 10px&#34;&gt;&lt;div class=&#34;amazlet-name&#34; style=&#34;margin-bottom:10px;line-height:120%&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/B09N5NWKR1/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Efficient MySQL Performance (English Edition)&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-detail&#34;&gt;英語版  Daniel Nichter (著)  形式: Kindle版&lt;br/&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-sub-info&#34; style=&#34;float: left;&#34;&gt;&lt;div class=&#34;amazlet-link&#34; style=&#34;margin-top: 5px&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/B09N5NWKR1/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Amazon.co.jpで詳細を見る&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-footer&#34; style=&#34;clear: left&#34;&gt;&lt;/div&gt;&lt;/div&gt;&#xA;</description>
      <author>Kosei Moriyama</author>
      <pubDate>Tue, 23 Jan 2024 20:30:00 +0900</pubDate>
    </item>
    <item>
      <title>最近読んだもの 59 - DB 動向年間レビュー、各社のシャーディング事例など</title>
      <link>https://please-sleep.cou929.nu/recently-readings-059.html</link>
      <description>&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://ottertune.com/blog/2023-databases-retrospective&#34;&gt;Databases in 2023: A Year in Review | OtterTune&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Andy Pavlo (CMU, OtterTune) による毎年恒例の DB 業界動向年間レビュー&lt;/li&gt;&#xA;&lt;li&gt;LLMs による Vector Databases の隆盛 (専用の DB ではなく、既存 DBMS の機能としての提供も追いついてきそう)&lt;/li&gt;&#xA;&lt;li&gt;新標準 &lt;a href=&#34;https://en.wikipedia.org/wiki/SQL:2023&#34;&gt;SQL:2023&lt;/a&gt;、目玉はグラフ構造へのクエリと、多次元配列のサポート。いずれも実装はまだほぼ無し&lt;/li&gt;&#xA;&lt;li&gt;MariaDB (会社側) のごたごた&lt;/li&gt;&#xA;&lt;li&gt;NOTAM というシステムの障害でアメリカ国内線が不通に。80年代に作られたままの、RDBMS を使っていない、おそらく CSV か何かを編集するような自家製 DBMS らしい&lt;/li&gt;&#xA;&lt;li&gt;などなど。相変わらず技術面からファイナンス含めた企業の隆盛までカバーしており面白い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://me.0xffff.me/db-trend-2024.html&#34;&gt;Big Data and Beyond: My Predictions for 2024 - me.0xffff.me&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;PingCap (TiDB) 共同創業者, CTO の Ed による年頭所感&lt;/li&gt;&#xA;&lt;li&gt;大規模データでのパフォーマンスのスケーリング問題はほぼ解かれ、次はコスト最適化に主眼が置かれる&lt;/li&gt;&#xA;&lt;li&gt;TiDB をはじめ Spanner や Vitess が取り組んできたスケーリングという課題は問いとしては解かれている。大量のデータでのパフォーマンスや可能性の問題が無いとすると、次に目が向くのがコストの最適化になる。通常すべてのデータが等しく使われるわけでは無いので、ホットデータもコールドデータも等しくリソースをかけてサーブするのはナンセンス。前者によりリソースを集中させ、後者は安価に保持する&lt;/li&gt;&#xA;&lt;li&gt;ベクターサーチをはじめより多様なインタフェースに対応する&lt;/li&gt;&#xA;&lt;li&gt;データベースのカーネル機能によりクラウドネイティブな作りが求められる&lt;/li&gt;&#xA;&lt;li&gt;オブザーバビリティ、多様な認証、エッジからの直接アクセスなど&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.figma.com/blog/how-figma-scaled-to-multiple-databases/&#34;&gt;The growing pains of database architecture | Figma Blog&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Figma の PostgreSQL シャーディング事例&lt;/li&gt;&#xA;&lt;li&gt;まずシャーディング開始前時点で接続多重化プロキシ (PgBouncer) の導入までは済んでいた&lt;/li&gt;&#xA;&lt;li&gt;その上でまずは垂直分割(テーブルを別インスタンスに分ける。一つのデーブルが複数インスタンスにまたがることはない) から実施した&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;水平分割よりもアプリケーションの変更量や運用懸念が少なく、また負荷分散への一定の効果が見込め、さらに将来の水平分割を妨げないため&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;分割計画&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;テーブルごとのインパクト（DBにかける負荷）と独立性（他のテーブルとの関連度）を定量化して分割方法と順序を決める&lt;/li&gt;&#xA;&lt;li&gt;前者は &lt;a href=&#34;https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-ACTIVITY-VIEW&#34;&gt;pg_stat_activity&lt;/a&gt;というクエリごとの平均セッション数を集計&lt;/li&gt;&#xA;&lt;li&gt;後者は ActiveRecord のフックで、発行したクエリとその発行元をデータレイク（SnowFlake）に送り集計&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Cut Over は独自ツールで無停止切り替えを達成&lt;/li&gt;&#xA;&lt;li&gt;まずアプリケーションをテーブルごとに適切な接続先に切り替えられるよう変更&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;デグレのリスクを下げるため、実際に DB を分ける前に、PgBouncer だけを分割しておき、接続先が間違っているとエラーになるようにした&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;分割先 DB にテーブルをコピーしレプリケーションする&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;テラバイト級のデータコピーには時間がかかるが、一旦インデックスなしでデータを移動させ、後からインデックスを追加する高速化をした&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ここまで準備ができるといよいよカットオーバーを行う&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;旧テーブルへのクエリを短時間止め、新テーブルがキャッチアップしたことを最終確認し、新 DB を primary にプロモートし、クエリ先を新テーブルに切り替える&lt;/li&gt;&#xA;&lt;li&gt;キャッチアップの確認は &lt;a href=&#34;https://www.postgresql.org/docs/current/wal-internals.html&#34;&gt;LSNs&lt;/a&gt;で行う&lt;/li&gt;&#xA;&lt;li&gt;新 DB をプロモートする際、逆向きのレプリケーションを設定し、切り戻しができるようにする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;50 回の分割作業を行い、カットオーバー時の停止時間は 30 秒程度、その間のリクエストエラー率は高々 2%、最も大きいシャードの CPU 使用率は 10% 程度になった&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.reddit.com/r/RedditEng/comments/11xx5o0/you_broke_reddit_the_piday_outage/&#34;&gt;You Broke Reddit: The Pi-Day Outage&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;RedditのPi day (&lt;sup&gt;3&lt;/sup&gt;&amp;frasl;&lt;sub&gt;14&lt;/sub&gt;) に起こった障害の振り返り&lt;/li&gt;&#xA;&lt;li&gt;k8s のバージョンで発生。直接の原因は master という語が廃止され control plane に置き換わったこと&lt;/li&gt;&#xA;&lt;li&gt;遠因として、独自構築された知識がよく共有できていないクラスタだったということ。より一般化すると、社内で使われる技術、環境がばらばらで標準化されていないこと&lt;/li&gt;&#xA;&lt;li&gt;少なくともマネージドでできるだけデフォルトパラメータで運用するように心がけようと個人的には思った&lt;/li&gt;&#xA;&lt;li&gt;またバックアップからの復旧を試みるなど、障害対応の緊張感が伝わる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://decomposition.al/blog/2023/12/31/a-cap-tradeoff-in-the-wild/&#34;&gt;A CAP tradeoff in the wild - decomposition ∘ al&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;k8s のとある &lt;a href=&#34;https://github.com/kubernetes/kubernetes/issues/59848&#34;&gt;issue&lt;/a&gt; が典型的な CAP 定理のトレードオフに該当するという話&lt;/li&gt;&#xA;&lt;li&gt;あるホストA, B の状態が何らかの理由で同期されていない場合、A, B からの応答は「レイテンシを重視して間違っていても即座に返す」か「一貫性を重視して同期が取られるまで待つ（ただし無限に取られない可能性もある）」かのいずれかにしか、本質的にはならないというジレンマ&lt;/li&gt;&#xA;&lt;li&gt;k8s のケースでは etcd の状態とより手前にあるキャッシュの状態が同期しないケースで、同じジレンマが避けようなく存在する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.blog/2019-11-21-debugging-network-stalls-on-kubernetes/&#34;&gt;Debugging network stalls on Kubernetes - The GitHub Blog&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;GitHub の k8s クラスタで起こった定期的なレイテンシの悪化のデバッグ。2019 年の記事&lt;/li&gt;&#xA;&lt;li&gt;これだけの深さまで掘り進めるのは難しいが、状況（関連するコンポーネント）を図示して原因を切り分けていくステップは参考になった&lt;/li&gt;&#xA;&lt;li&gt;k8s は特にそうだがコンポーネントの層が厚く複雑なので、図を取り入れて進めるやり方はよさそう&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://medium.com/pinterest-engineering/sharding-pinterest-how-we-scaled-our-mysql-fleet-3f341e96ca6f&#34;&gt;Sharding Pinterest: How we scaled our MySQL fleet | by Pinterest Engineering | Pinterest Engineering Blog | Medium&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;2015 年の記事。当時の Pinterest での MySQL シャーディング事例&lt;/li&gt;&#xA;&lt;li&gt;今仕事で Vitess を使っておりそれでも複雑さを感じるが、このようにすべて自前で行うのに比べれば、遥かに楽だと改めて認識できた&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://instagram-engineering.com/sharding-ids-at-instagram-1cf5a71e5a5c&#34;&gt;Sharding &amp;amp; IDs at Instagram. With more than 25 photos and 90 likes… | by Instagram Engineering | Instagram Engineering&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;2012年の記事。当時の Instagram のシャードを跨いだ ID 生成を、タイムスタンプ・シャード番号をベースに DB の &lt;code&gt;PL/PGSQL&lt;/code&gt; で行ったという話&lt;/li&gt;&#xA;&lt;li&gt;UUID に比べて短くソート可能で、専用の採番サービスを用意する方針に比べて単一障害点になりづらいなど運用面で有利&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://openai.com/research/scaling-kubernetes-to-7500-nodes&#34;&gt;Scaling Kubernetes to 7,500 nodes&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;2021 年の記事。当時の OpenAI で 7,500 ノードを k8s で管理する話&lt;/li&gt;&#xA;&lt;li&gt;リサーチャーが学習で利用するクラスタで、GPU を利用するのでワークロードごとに 1 ノードを占有する必要がある、パブリックにサービスを公開しないなど、普通の Web アプリケーションクラスタとは異なるポイントも多い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.brendangregg.com/usemethod.html&#34;&gt;The USE Method&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Brendan Gregg による、システム高負荷時に素早く原因を切り分ける USE Method の解説&lt;/li&gt;&#xA;&lt;li&gt;リソースごとの Utilization, Saturation, Errors を見ること&lt;/li&gt;&#xA;&lt;li&gt;具体的なコマンドやチェックリストもあり&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://netflixtechblog.com/linux-performance-analysis-in-60-000-milliseconds-accc10403c55&#34;&gt;Linux Performance Analysis in 60,000 Milliseconds | by Netflix Technology Blog | Netflix TechBlog&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Linuxで問題調査をする際に使う基本的なコマンド集。2015 年の記事&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;PR&lt;/h2&gt;&#xA;&#xA;&lt;div class=&#34;amazlet-box&#34; style=&#34;margin-bottom:0px;&#34;&gt;&lt;div class=&#34;amazlet-image&#34; style=&#34;float:left;margin:0px 12px 1px 0px;&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4873119545/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;&lt;img src=&#34;https://m.media-amazon.com/images/I/71By-qn40MS._SY466_.jpg&#34; alt=&#34;詳説 データベース ―ストレージエンジンと分散データシステムの仕組み&#34; style=&#34;border: none; width: 113px;&#34; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-info&#34; style=&#34;line-height:120%; margin-bottom: 10px&#34;&gt;&lt;div class=&#34;amazlet-name&#34; style=&#34;margin-bottom:10px;line-height:120%&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4873119545/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;詳説 データベース ―ストレージエンジンと分散データシステムの仕組み&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-detail&#34;&gt;Alex Petrov (著), 小林 隆浩 (監修), 成田 昇司 (翻訳)&lt;br/&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-sub-info&#34; style=&#34;float: left;&#34;&gt;&lt;div class=&#34;amazlet-link&#34; style=&#34;margin-top: 5px&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4873119545/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Amazon.co.jpで詳細を見る&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-footer&#34; style=&#34;clear: left&#34;&gt;&lt;/div&gt;&lt;/div&gt;&#xA;</description>
      <author>Kosei Moriyama</author>
      <pubDate>Tue, 09 Jan 2024 20:30:00 +0900</pubDate>
    </item>
    <item>
      <title>読書メモ: A Philosophy of Software Design</title>
      <link>https://please-sleep.cou929.nu/philosophy-of-software-design.html</link>
      <description>&lt;div class=&#34;amazlet-box&#34; style=&#34;margin-bottom:0px;&#34;&gt;&lt;div class=&#34;amazlet-image&#34; style=&#34;float:left;margin:0px 12px 1px 0px;&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/B09B8LFKQL/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;&lt;img src=&#34;https://m.media-amazon.com/images/I/7146AYGD0AL._SY466_.jpg&#34; alt=&#34;A Philosophy of Software Design, 2nd Edition (English Edition)&#34; style=&#34;border: none; width: 113px;&#34; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-info&#34; style=&#34;line-height:120%; margin-bottom: 10px&#34;&gt;&lt;div class=&#34;amazlet-name&#34; style=&#34;margin-bottom:10px;line-height:120%&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/B09B8LFKQL/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;A Philosophy of Software Design, 2nd Edition (English Edition)&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-detail&#34;&gt;英語版  John K. Ousterhout (著)  形式: Kindle版&lt;br/&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-sub-info&#34; style=&#34;float: left;&#34;&gt;&lt;div class=&#34;amazlet-link&#34; style=&#34;margin-top: 5px&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/B09B8LFKQL/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Amazon.co.jpで詳細を見る&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-footer&#34; style=&#34;clear: left&#34;&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;良い設計をするためのコンセプトを解説した本。類書はいろいろとあるが、自分が読んだものの中では一番良かった。ソフトウェアエンジニアリングを行う人には広くおすすめできる。コンパクトですぐに読み切れるのも良い。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;複雑さをいかに削減するかという観点と、その対策としての深いモジュールというコンセプトを導入し、この軸ですべての章を論じている。筋が通っていて読みやすいし、納得感も高い&lt;/li&gt;&#xA;&lt;li&gt;これらのコンセプトを通して、従来は良しとされているプラクティスの再検討も行っていて、こちらも面白く納得しながら読めた。例えばできるだけメソッドは小さくするという慣習や、Clean Code でのコメントの不要論など。同意する部分は認めつつ、それがマッチしない、むしろシステム全体の複雑さを増やしてしまうケースを示している&lt;/li&gt;&#xA;&lt;li&gt;ソフトウェアを書いて終わりではなく時間をかけて保守開発していくものという観点に立っているのも良い&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4873119650/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Googleのソフトウェアエンジニアリング&lt;/a&gt; もそのような視点で書かれていたのが思い出された。単純なプログラミングとソフトウェアエンジニアリングの違いは、この時間経過の概念があるかどうかと定義していた記憶がある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;目から鱗が落ちる、すぐに普段の開発に取り入れたい知見がいろいろとあった&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;印象に残っているのは &lt;code&gt;somewhat general-purpose&lt;/code&gt; にモジュールを設計しようというアイデア&lt;/li&gt;&#xA;&lt;li&gt;先が見通せない状態で汎用的に作りすぎるのはリスクがあるので、まずは要件を最短で満たす実装をし、そこから必要に応じてリファクタリングするという、インクリメンタルな進め方は良いものだと考えていた&lt;/li&gt;&#xA;&lt;li&gt;それはそうなのだが、最初からある程度 (この塩梅も重要) 汎用性のあるつくりにしたほうが、シンプルさも実装量も減るという知見が目新しく、取り入れていきたいと思った&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;先にコメントを書いてから実装に入るというコメントファーストという設計手法は、生成 AI 時代にもマッチしている方法論だと思った&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;まずはできるだけ Copilot に書いてもらい、それを手で修正するやり方を、普段から取るようにしている&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ref. &lt;a href=&#34;https://speakerdeck.com/kurochan/saihaesientonogithub-copilotdao-ru-to-kai-fa-sheng-chan-xing?slide=41&#34;&gt;サイバーエージェントのGitHub Copilot導入と 開発生産性 - Speaker Deck&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;コメントファーストの方針はこうしたプログラミングスタイルともマッチしている気がする&lt;/li&gt;&#xA;&lt;li&gt;もちろん第一義は実装に入る前に設計をブラッシュアップできるという、設計ツールとしての側面だが&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;以降は読書メモだが、本文のコード例も合わせて読んだほうが理解が深まる。&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;1 - Introduction (It’s All About Complexity)&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;この本では、デザインの良し悪しの判断基準を複雑さ complexity に置いている。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;物理的な制約がないソフトウェアエンジニアリングでは「複雑さ」、いかにシステムを容易に理解でき変更できるか、が生産性のボトルネックになる&lt;/li&gt;&#xA;&lt;li&gt;複雑さは時間を経るごとに増し、反比例して開発生産性は減るので、なおさら重要&lt;/li&gt;&#xA;&lt;li&gt;良いデザインは複雑さをなくしたり、モジュールに押し込めることで、トータルの複雑さを減らす&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;2 - The Nature of Complexity&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;ここでは複雑さを「システムの理解と変更を難しくする、ソフトウェアの構造に関するすべてのもの」と定義している。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;簡単に理解でき変更できるソフトウェアは複雑さが少ない。また複雑なシステムは小さな変更に労力がかかるので、費やしたコストに対して便益が少ないとも言える&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;コードベースの大きさとは独立した概念。大きいコードは理解するのが大変なので複雑な傾向にはあるが、小さくても複雑なソフトウェアは存在し得る&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;次のように定式化できる。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;img src=&#34;images/posd-complexity-formula.gif&#34; alt=&#34;C = \sum_p c_p t_p&#34; /&gt;&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;定義&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;C&lt;/code&gt; はシステム全体の複雑さ&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;p&lt;/code&gt; はそのシステムを構成するサブシステムやコンポーネント&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;c_p&lt;/code&gt; はサブシステムの複雑さ&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;t_p&lt;/code&gt; はそのサブシステムに開発者が費やす時間&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;つまり以下のアプローチがある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;複雑さそのもの &lt;code&gt;c_p&lt;/code&gt; を減らしたり無くす&lt;/li&gt;&#xA;&lt;li&gt;複雑さをモジュールに切り出す。サブシステムに費やす時間 &lt;code&gt;t_p&lt;/code&gt; が小さければ、その複雑さ &lt;code&gt;c_p&lt;/code&gt; が大きくても全体はシンプルになる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;複雑なソフトウェアには以下の症状が現れる。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;変更箇所の増幅 Change amplification&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;簡単な変更でも多くの作業箇所が発生する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;認知負荷の増加 Cognitive load&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;作業するために把握すべきことが多い・把握が難しい&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;知らない知らないこと Unknown unknowns&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;把握すべきことがコードからもドキュメントからも知りようがない状況&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;この中で Unknown unknowns がワーストケース。開発時には問題にすら気づくことができず、リリース後のバグ報告で把握するしかなくなる&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;こうした複雑さの原因は依存関係 Dependency と不明瞭さ Obscurity によって引き起こされる。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;依存関係はある変更をする際に特定の箇所以外の変更も必要になる場合と定義している&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;依存関係は本質的に必要なものだが、その数を減らし、関係を明確にすることで複雑さを下げることができる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;重要な情報が分かりづらい場合不明瞭さが現れる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;設計とドキュメントで解決すべき問題。また良い設計は必要なドキュメントを減らす&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;依存関係は Change amplificationと Cognitive loadを増やし、不明瞭さは Cognitive loadと Unknown unknownsを増やす関係にある&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;また複雑さは一つの大きな原因があるのではなく、小さな問題が累積して全体の大きな複雑さになる。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;素早く仕事をこなすために追加した小さな複雑さが、時間を経て incrementalに全体の複雑さを増加させていく&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;3 - Working Code Isn’t Enough&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;日々の開発では、戦略的目線で少しずつ投資をしていくマインドセットが求められる。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;機能開発やバグ修正を最短で行う方針を戦術的プログラミングと呼ぶ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;最速で動くことが最優先される&lt;/li&gt;&#xA;&lt;li&gt;複雑さは追加、蓄積される。多くの場合技術的負債が全て返済されることはなく、複雑さは常に増える&lt;/li&gt;&#xA;&lt;li&gt;一般的な組織では高速に実装する人は高く評価されがちで、その時どれだけ複雑さが追加されたかが評価に加味されることは少ない。また負債を残さない開発をしている人、負債を返している人が高く評価されることは少ない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;反対に良い設計を目指して進める方針を戦略的プログラミングと呼ぶ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;「動くこと」は当然満たしながら、あるべき設計への投資も行う&lt;/li&gt;&#xA;&lt;li&gt;良い設計のためにはこちらのマインドセットを持つ必要がある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;開発期間の 10-20 % を、この戦略的目線での投資にあてることを推奨&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一度に大きなリファクタリングは失敗しやすい。インクリメンタルな進め方を推奨している&lt;/li&gt;&#xA;&lt;li&gt;複雑さは個々の小さなものの累積なので、それを出さない、減らす活動にも大きな意味がある&lt;/li&gt;&#xA;&lt;li&gt;この水準の時間投資ならば比較的早く回収できる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;客観的ではないが、筆者の見込みでは 6-18 ヶ月で投資が回収できるとのこと&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;images/posd-tactical-vs-strategic.png&#34; alt=&#34;tactical-vs-strategic&#34; width=300 /&gt;&#xA;&lt;figcaption&gt;A Philosophy of Software Design (p. 16) Figure 3.1 より&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;スタートアップなど速度が重視される環境でも例外ではない&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;戦術的プログラミングを行う組織の代表例として Facebook が挙げられる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;彼らの &lt;code&gt;Move fast and break things&lt;/code&gt; というモットーはそれを象徴しているが、後日 &lt;code&gt;Move fast and solid infrastructure&lt;/code&gt; に変更されている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;戦略的アプローチをとる代表例は Googleや VMWare&lt;/li&gt;&#xA;&lt;li&gt;これらを見ると戦略的・戦術的プログラミングどちらでも事業が大きく成功することがあるとわかる。そうであればエンジニアとしてより満足度が高い後者のアプローチをとってもいいのではないか&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;4 - Modules Should Be Deep&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;モジュール化は代表的な複雑さへの対応方法で、その良し悪しは「深さ」で測る。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;以下の議論はメソッドやクラスからサブシステム、サービスまで、あらゆる粒度に適用できる。わかりやすさのためモジュールと呼んでいる。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;モジュールはインタフェースと実装に分けて考えることができる&lt;/li&gt;&#xA;&lt;li&gt;インタフェースは広義にそのモジュールを使うために利用者が把握しなければいけないことで、以下の二つに分類できる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;フォーマルなインタフェース&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;プログラミング言語により検証されるもの&lt;/li&gt;&#xA;&lt;li&gt;メソッドのシグネチャが例。メソッド名や引数、戻り値の型にミスがあればコンパイラが指摘してくれる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;インフォーマルなインタフェース&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;フォーマルなもの以外で、利用者が把握しなければ、そのモジュールを正しく使えないような情報&lt;/li&gt;&#xA;&lt;li&gt;例えば「別のメソッドを事前に呼び出さなければならない」といった情報や、どんな例外が投げられるかなど&lt;/li&gt;&#xA;&lt;li&gt;基本的にはドキュメントに記載するしかない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;「深い」モジュールとは、シンプルなインタフェースに対して多くの仕事をするもの。&lt;/p&gt;&#xA;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;images/posd-deep-and-shallow-modules.png&#34; alt=&#34;deep and shallow modules&#34; width=300 /&gt;&#xA;&lt;figcaption&gt;A Philosophy of Software Design (p. 23) Figure 4.1 より&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;四角形の縦軸がそのメソッドのこなせる仕事の量、上辺の太線がインタフェースで、短いほどシンプル&lt;/li&gt;&#xA;&lt;li&gt;深いモジュールは縦に長い&lt;/li&gt;&#xA;&lt;li&gt;例えば Unix の File IO の仕組みが挙げられる。５つのシステムコールで単純なファイルからネットワーク通信まであらゆる入出力に対応している&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;反対に浅いモジュールは相対的に複雑なインタフェースに対して仕事が少ないもの。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば別のメソッドに値を受け渡しているだけのメソッドが極端な例&lt;/li&gt;&#xA;&lt;li&gt;浅いメソッドは悪い設計のレッドフラグ（兆候）&lt;/li&gt;&#xA;&lt;li&gt;メソッドやクラスを小さく保つというプラクティスがあるが、それはこの議論の文脈では浅いモジュールを引き起こす悪いものということになる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;実装を理解しやすくするのは重要だが、単に行数で複雑さを判断するのは間違っている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;インタフェースの設計は、その対象をどう抽象化するかと言い換えることができる。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;あるエンティティから不要な詳細を省き、シンプルな見方を提供するのが抽象化の定義&lt;/li&gt;&#xA;&lt;li&gt;「不必要なものをインタフェースに含めてしまう」「必要なものがインタフェースから漏れてしまう」とインタフェースの複雑さが増し、浅いモジュールに近づいてしまう&lt;/li&gt;&#xA;&lt;li&gt;不必要なものをインタフェースに含めてしまった場合、利用者の認知負荷が高まる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;標準的な使い方では不要なケースへの対応のために必要な引数がが必須になっているなど&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;必要なものがインタフェースから漏れてしまった場合、Unknown unknownsを引き起こす&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;インフォーマルなインタフェースがドキュメント化されていないなどが例になる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;5 - Information Hiding (and Leakage)&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;情報をモジュールに隠す（漏らさない）ことは、深いモジュールを作るための方法論のひとつ。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Information hidingはある情報を実装の中だけにとどめてインタフェースには出さないこと&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えばデータを B-tree に格納するとか、スレッドをどうスケジューリングするかといったデータ構造・アルゴリズムの詳細はインタフェースには現れるべきではない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Information leakageはその反対で、これが起こるとインタフェースが複雑化し浅いモジュールに近づく&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば順序によるモジュール分割&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ファイルを読み込み、編集して保存する機能を考える。この順序に着目し読み込み、編集、保存の 3 クラスを作ったとする。読み込みと保存を担うクラスはファイルフォーマットの知識を暗黙に共有していることになり、フォーマットに変更があるとこの2クラスを両方修正しなくてはいけない&lt;/li&gt;&#xA;&lt;li&gt;設計の際にはタスクの順序ではなく、各タスクが必要とする知識に着目したほうが良い。この例の場合は読み書きを両方行う汎用的なクラスを作り、ファイルに関する知識を一箇所にまとめたほうが良い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;他にも特定のケースのチューニングに必要なパラメータという例もある。理想的には設定値をユーザーに指定させるのではなく、最適な数値を自動計算できたほうが良い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Information leakageに気づくことができる能力は、設計スキルにおいて重要&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;6 - General-Purpose Modules are Deeper&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;モジュールを汎用的に (General-Purpose) に作ることで深いモジュールを実現できる。始めから適度に汎用的に設計したほうが良いことが経験からわかった。ロジックの特殊な部分を汎用的な部分から分離する方針には、上方向・下方向の二種類がある。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;汎用的なモジュールをつくり、色々な用途で共通して利用できれば、そのモジュールは深く、複雑さも少なくなる&lt;/li&gt;&#xA;&lt;li&gt;ただし始めから汎用的な設計をすることは簡単ではなく、世の中にはそれを推奨しない方針もある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;まずは要件にあわせて最低限の（特殊な Special-purpose）実装をして、後から別の要件が出た際に汎用的にリファクタリングする&lt;/li&gt;&#xA;&lt;li&gt;完全に未来を見通すことは不可能だし、インクリメンタルな進め方という見方でも好ましい&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;筆者ももともとこの方針を支持していたが、経験を通じて始めから適度に汎用的な設計をしたほうが良いことに気がついた&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;最低限の特殊な実装は、ほとんどのケースで、汎用的に作るよりもインタフェースの複雑さ、コード量で劣った。つまり仮に将来の再利用がされなくてもメリットがあると言える&lt;/li&gt;&#xA;&lt;li&gt;もちろん過度な汎用化は非推奨。目安として、インタフェースの設計は汎用性を目指し、実装はそこまではやらないという塩梅&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;テキストエディタの設計という課題での例&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;テキストをファイルから読み込み、変更し、保存するテキストクラスを基盤として、その上に UI のクラスを作る方針&lt;/li&gt;&#xA;&lt;li&gt;ここで削除操作について考える。バックスペースを押すとカーソルの前、デリートを押すとカーソルの後、範囲選択中ならその範囲を削除する&lt;/li&gt;&#xA;&lt;li&gt;ここで操作ごとの専用メソッドをテキストクラスに作るとする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;void backspace(Cursor cursor);&#xA;void delete(Cursor cursor);&#xA;void deleteSelection(Selection selection);&#xA;&#xA;// Ousterhout, John K. . A Philosophy of Software Design, 2nd Edition (p. 41). Yaknyam Press. Kindle Edition.&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;この方針には次の問題がある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;大量の浅いメソッドが生まれる。UI の操作ごとにメソッドが必要で、それぞれは簡単なロジックしか持たない。利用者は多くのメソッドを把握しないと UI を実装できないし、各メソッドのドキュメントやコードを把握しないと実際の挙動がわからない&lt;/li&gt;&#xA;&lt;li&gt;UI の要件の変更ごとに基盤のはずのテキストクラスに変更が必要になる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;代わりにテキストクラスには次のシンプルなメソッドだけを持たせる方針を考える&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;void insert(Position position, String newText);&#xA;void delete(Position start, Position end);&#xA;&#xA;// Ousterhout, John K. . A Philosophy of Software Design, 2nd Edition (p. 42). Yaknyam Press. Kindle Edition. &#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;p&gt;シンプルで汎用的な少数のメソッドだけを覚えれば、UI 側はほとんどの要件を実装できる&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;UI ごとの特殊な処理は UI 側に切り出されており、基盤側の変更が不要になる&lt;/p&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;設計時のチェックポイント&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;全ての要件を満たす最もシンプルなインタフェースは何か&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;もしメソッドとして一つでも多くの引数が必要な場合、うまくシンプルにできていないと言える&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;このメソッドがカバーするユースケースはどれだけあるか&lt;/li&gt;&#xA;&lt;li&gt;この API は現在の要件を満たすのにシンプルに使えるか&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えたくさんのユースケースをカバーするインタフェースでも、利用に複雑な手続きが必要ならそれはシンプルではない。汎用的かつシンプルなものを目指す&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&#xA;&lt;li&gt;&lt;p&gt;汎用的な部分から特殊な部分を切り出す際、上への切り出し、下への切り出しの2種類がある&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;テキストエディタの例は上への切り出し。基盤となるテキストクラスは汎用性を保ち、UI に紐づく特殊な処理は上側（UI 側）に寄せられた&lt;/li&gt;&#xA;&lt;li&gt;下への切り出しの例は Linux のデバイスドライバ。共通のインタフェースが定義され、各デバイスの特殊な部分は下側（ドライバ側）に切り出されている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;7 - Different Layer, Different Abstraction&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;レイヤー分けはソフトウェアには必須の抽象化技法。レイヤーごとに違う抽象化を提供するのが原則（それがレイヤーを分ける意味）だが、そうなっていなければ、つまり別のレイヤーに同じような API が重複しているのは、危険な兆候。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;そのひとつが Pass-through methodというパターン。ほぼ別レイヤーの関数を呼び出しているだけのメソッドで、シグネチャも重複している。&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;クラス間（レイヤー間）の役割の切り分けに失敗している可能性が高い&lt;/li&gt;&#xA;&lt;li&gt;シグネチャが同じだけでは Pass-though methodとは限らない。それぞれのレイヤーでなにか役に立つ機能が提供されているかどうかが問題（別の関数を呼び出すだけの一行メソッドは価値を追加しておらず、層が増え複雑さを増しているだけなので問題）&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;デコレーターも浅いモジュールになりがちで、濫用注意&lt;/li&gt;&#xA;&lt;li&gt;Peas-through variableというパターンもある。レイヤー間で引数を引き回しているもの。変更箇所が増えてしまうので避けたほうがよいが、解消は簡単ではない&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;グローバル変数を使うか（それはそれで別問題をはらむ）、コンテキストオブジェクトを導入するといった対策&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;8 - Pull Complexity Downwards&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;避けることのできない複雑さが存在することはままある。通常、モジュールの制作者より利用者の方が多い。そのため複雑さはモジュールの中に押し込めたほうが、全体がシンプルになる場合が多い。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば TCP の下位レイヤーの仕事、パケロスの検出や再送など、が上位レイヤーに漏れていると、利用者の認知負荷が高まる&lt;/li&gt;&#xA;&lt;li&gt;モジュール制作者はいくらか追加の工数を払うことになるが、長期目線、全体目線では効率的&lt;/li&gt;&#xA;&lt;li&gt;ただ通常は制作者はモジュールの実装をシンプルに保つため利用者に知識や実装を求める方に短期的なインセンティブがあるので、注意しなければいけない&lt;/li&gt;&#xA;&lt;li&gt;例えばチューニングパラメータを提供することも、できるだけ避けたほうが良い。そのモジュールが最適なパラメータを自動で算出するなど、追加で実装して済むならそのほうが良い&lt;/li&gt;&#xA;&lt;li&gt;常に複雑さを下に押し込めればいいわけではない。以下を満たしている場合に下に切り出すのがよい&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;その複雑さが、下位レイヤーのクラスの役割と関係が深い&lt;/li&gt;&#xA;&lt;li&gt;そうすることで利用方法がシンプルになる&lt;/li&gt;&#xA;&lt;li&gt;そうすることでインタフェースがシンプルになる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;9 - Better Together or Better Apart?&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;ロジックを分割するか、統合するかの判断基準。内容が近いものは近くに置くのが原則。メソッドやクラスを細かく分割すると、ひとつひとつの量は小さくなり一見理解しやすいかもしれないが、インタフェースの数が増えること（浅いモジュールが増えること）そのものが全体の複雑さを増加させるので注意。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;同じデータ、情報、知識を共有しているものは統合する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;前述のテキストエディタの例だと、ファイルの読み込みと書き込みクラスはいずれもファイルフォーマットの知識を共有している&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;統合することでインタフェースがシンプルになるのならそうする&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ある一連の処理のステップごとにクラスが分かれているような場合。統合すると中間生成物の受け渡しが不要になる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;同じコードが繰り返し登場したら赤信号。統合を検討する&lt;/li&gt;&#xA;&lt;li&gt;汎用処理と特殊処理はわける&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;これらの考え方はクラスだけでなくメソッドの設計にも適用できる。ただしメソッドの場合は（も）細かく分けすぎないように注意する&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ある単体のメソッドだけをみて実装が理解できないようなら赤信号。本来一箇所にあるべきものが分けられている可能性がある&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Clean Code ではメソッドを細かく分割することを推奨しているが、単に行数だけで判断するのは浅いモジュールを増やすためよくない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;10 - Define Errors Out of Existence&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例外の難しさ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;正常系より実装が複雑になりがち&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例外発生時に、それでも処理を前に進めるにしてもその処理は複雑になりがちだし、abort する場合でも不整合が起こらないよう状態を巻き戻す作業が煩雑になりがち&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;例外状態が別の問題を引き起こすこともある&lt;/li&gt;&#xA;&lt;li&gt;例外ハンドリングのコードは読みづらくなりがち&lt;/li&gt;&#xA;&lt;li&gt;例外をテスト環境で実際に起こすのは難しく、長い間気づかれないバグが入り込みやすい&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;分散データ指向システムのバグの 90% はエラーハンドリングが原因という報告もある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;例外もインタフェースの一種&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;利用者はそれを把握していないといけないので&lt;/li&gt;&#xA;&lt;li&gt;さらに、呼び出し元だけでなく、その上のレイヤーにまで例外は波及することもある。例外は複雑さの増加に大きく影響する&lt;/li&gt;&#xA;&lt;li&gt;無駄な例外を可能な限り無くすほうがよい&lt;/li&gt;&#xA;&lt;li&gt;しかし意識しないとなかなか難しい&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;予期しない状態が起こった時、例外を出してしまうのが簡単。それを内部でハンドリングするほうが難しい。ただそうしないとシンプルなインタフェースが実現できない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;可能なら例外そのものをなくしてしまえると良い。異常な状態を内部でケアして、正常系の範疇に収める&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば Unix のファイル削除。削除時に別のプロセスがそのファイルを開いていても、削除は成功する。OS がファイルに削除マークをつけておき、実際の削除は全プロセスがアクセスを辞めた後に行う。一方で Windows では削除時にエラーを出すため、呼び出しごとに毎回、他に開いているプロセスが居ないかのチェックを書かなければいけない&lt;/li&gt;&#xA;&lt;li&gt;Java の substring 関数は、指定した範囲が境界を超えていたら例外を出す。これをエラーとせず単に空文字列を返したり最大長へ切り詰めて返すことで例外を無くせる。関数の定義を「何であれ指定した範囲の部分文字列を返す」とすることで、エラーをエラーでは無くしている&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;この例外で気づけていたバグもあったかもしれない。しかし全ての substring 呼び出し箇所に指定範囲チェックのコードを入れるほうがバグの確率を高めている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;例外のマスク&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;下位のレイヤーで例外をハンドリングしてしまう&lt;/li&gt;&#xA;&lt;li&gt;例えば TCP では下位のレイヤーが問題時に自動的にパケットを再送する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;例外の集約&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例外を各所でハンドリングするのではなく、一箇所で行う&lt;/li&gt;&#xA;&lt;li&gt;例えば Web サーバを考える。リクエストに応じてディスパッチャーがハンドラを呼び出す。リクエストパラメータのパースで例外が起こった場合、各ハンドラで例外を捕捉するよりも、上位のディスパッチャーがまとめて対応したほうがよい&lt;/li&gt;&#xA;&lt;li&gt;ディスパッチャーはエラーに応じてどのようなレスポンスをクライアントに返すかを知っている。また例外の raise 元はどのような場合にどの例外を出すか、また例外のヒューマンリーダブルな表現は何かを知っている&lt;/li&gt;&#xA;&lt;li&gt;マスクと集約はそれぞれ別方向で同じことをしている&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;色々なところから呼び出されるモジュールの場合、マスクで補足箇所をまとめられる。色々な箇所から発せられる例外の場合、集約で補足箇所をまとめられる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;プログラムの Abort&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;エラーをハンドリングしようが無く、また頻度も低い場合、エラー時にプログラムを即座に Abort する&lt;/li&gt;&#xA;&lt;li&gt;例えばメモリ不足で malloc に失敗した場合、それ以上できることが無ければ Abort してしまった方がシンプルになる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;ここで、必要なエラー情報まで隠してしまうのは問題であることを再確認すること。何が重要で何が重要でないかを見極め、後者だけを取り除く。&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;11 - Design it Twice&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;「二度設計する」というスローガン。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;設計をする際に、最初に思いついたアプローチだけだなく、他のアプローチも比較検討する&lt;/li&gt;&#xA;&lt;li&gt;その際にあえて極端なものを考えると長所短所や問題がわかりやすい&lt;/li&gt;&#xA;&lt;li&gt;あきらかに一案目が正しいと確信していても、別アプローチとの比較はした方がよい。隠れた問題点がないかの確認や長所短所の明確化につながる&lt;/li&gt;&#xA;&lt;li&gt;検討したどちらの案も適当ではない場合。比較するプロセスで問題点が明らかになっているはずなので、それを解消する別のアプローチを検討する&lt;/li&gt;&#xA;&lt;li&gt;検討にかかる時間はそれほど多くない。クラスレベルの設計なら 1, 2 時間程度で、まずい設計が将来与える生産性低下に比べるとすぐに元がとれる。より大きなコンポーネントの設計の場合さらに時間がかかるが、その際は実装にもさらに時間がかかるはず&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;12 - Why Write Comments? The Four Excuses&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;ドキュメントは開発者の理解を効率化するため、良い抽象化のために必須で、設計において重要な位置を占める。コード中のコメントも同様。しかしコメントに対するネガティブな印象やポリシーは業界内に多い。ここではコメントを書かない理由を検討しながら、必要性を確認する。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;良いコードを書けばコメントは不要である&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;確かに命名などを適切に行うと可読性は劇的に良くなる&lt;/li&gt;&#xA;&lt;li&gt;ただコードで説明できることとコメントが説明できることは別である。メソッドのシグネチャをだけを見て、例えば引数の単位といったインフォーマルな情報を読み取ることはできない（または説明のために名前が長くなる）&lt;/li&gt;&#xA;&lt;li&gt;コードを読めばわかるが、そもそもコードを読まなければ利用できないモジュールは抽象化が不完全といえる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;コメントを書く時間がない&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;前述の投資するマインドセットを持つ。少ない時間の投資で長期のリターンが得られる&lt;/li&gt;&#xA;&lt;li&gt;後述するコメントファーストのデザイン技法であればコメントを書くストレスはさらに下がる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;コメントは古くなり、ミスリードを誘いがちになる。コメントを最新に保つのは工数がかかる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;後述するコメントを書く技術で低減できる&lt;/li&gt;&#xA;&lt;li&gt;キーポイントは「重複をなくし、関連するドキュメントとコードを近くに置く」こと&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;役に立つコメントを見たことがない&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;コメントには悪い書き方と良い書き方があり、技術で解決できる部分&lt;/li&gt;&#xA;&lt;li&gt;後述の章で解説している&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;コメントの意義はコードでは表現できない情報を記載できること。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;複雑なシステムの問題である Cognitive load、Unknown unknowns に有効&lt;/li&gt;&#xA;&lt;li&gt;複雑さは依存関係と不明瞭さから産まれるが、良いコメントは依存関係を明確にし不明瞭さを減らす&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;Clean Code はコメントに対して強く否定的な議論をしている。確かに悪い書き方をすると、何の情報も伝えない、ミスリードを誘うコメントになる。一方で繰り返しになるがコメントはコードで表現できない情報を伝達するという重要な役割がある。一律にコメントを否定すべきではない。&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;13 - Comments Should Describe Things That Arent Obvious from the Code&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;コードだけでは全ての情報を伝えることができないので、コメントにはコードからは明確にわからないことを記載すべき。全てのコードを読むのは時間がかかるし、詳細すぎるため、コメントも通じて抽象化（必要な情報だけを伝えること）を実現する。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;悪いコメントの例として、コードをリピートしているだけのものが挙げられる。次のようなコメントはほとんど何の情報も伝えられていない。よくある兆候として、変数名やメソッド名にある単語がそのまま使われているだけのコメントは不要なことが多い。&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;ptr_copy = get_copy(obj)              # Get pointer copy&#xA;if is_unlocked(ptr_copy):             # Is obj free?&#xA;    return obj                        # return current obj&#xA;if is_copy(ptr_copy):                 # Already a copy?&#xA;    return obj                        # return obj&#xA;thread_id = get_thread_id(ptr_copy)&#xA;if thread_id == ctx.thread_id:        # Locked by current ctx&#xA;    return ptr_copy                   # Return copy&#xA;&#xA;# Ousterhout, John K. . A Philosophy of Software Design, 2nd Edition (p. 103). Yaknyam Press. Kindle Edition. &#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;大切なのはコードとは違う抽象レベルの情報を記載すること。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;抽象レベルを下げたコメント: より正確な情報&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;変数のコメントを書く際は、例えば変数の単位は何か、範囲の場合境界は含むか、nullを受けいる場合何が起こるか、誰がメモリ解放に責任を持つか、普遍条件はあるかといった、変数についてのより詳細な情報を提供する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;抽象レベルを上げたコメント: より直感的理解を助ける情報&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;詳細は省き、全体感を素早く把握するための情報を提供する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;コメントは次の 4 つに分類できる。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;インタフェースコメント&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;クラスやメソッドの冒頭に記載する。概要とそれを使うために必要な知識（引数や返り値の説明など）を伝える&lt;/li&gt;&#xA;&lt;li&gt;実装に関するコメントとインタフェースに関するコメントとを分けて考え、後者を記載すること。後者はこの機能を利用するために必要な情報を提供し、実装には立ち入らない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;データ構造のメンバー&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;インスタンス変数の説明など&lt;/li&gt;&#xA;&lt;li&gt;動詞ではなく名詞で考えるのがコツ。その変数がどう扱われるのではなく、何を表しているのかにフォーカスする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;実装コメント&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;実装コード中に記載されるコメント&lt;/li&gt;&#xA;&lt;li&gt;理想的には、一つのことをするメソッドでインタフェースコメントがあれば、全く不要なことも多い&lt;/li&gt;&#xA;&lt;li&gt;what と why を記載する。howは記載しない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;クロスモジュールコメント&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;複数のモジュールにわたる依存関係を説明するコメント&lt;/li&gt;&#xA;&lt;li&gt;理想的には全ての重要な知識はそれぞれのモジュール内に隠蔽されるが、現実的には難しい。ひとつの知識に対して複数のモジュールがどうしても関連してしまうことがある&lt;/li&gt;&#xA;&lt;li&gt;そのような情報をどこに記載するかが最大の問題&lt;/li&gt;&#xA;&lt;li&gt;ひとつの解決法としては、中央の Wiki などにまとまった情報を記載し、コードの各所にはそこへの参照を記載する方法&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;このうち重要なのは最初の 2 つ。実装コメントは適切な命名や分割によってはほぼ必要なくなる。クロスモジュールコメントが必要になることは稀だが、必要になった先には非常に重要な情報となる。&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;14 - Choosing Names&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;命名ほど重要性が過小評価されているものはない。良い名前は理解を助け、ドキュメントの必要性を減らし、ミスに気づきやすくする。反対に悪い名前は認知負荷を高め、バグの温床になる。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;良い名前はそれだけで多くの情報を伝える。命名する際には、それだけで（定義やドキュメントや実装をの読まなくても）どれだけ多くそのエンティティを推測できるかと、自分に問いかけてみると良い。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;良い命名に必要とされるのは正確さと一貫性。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;正確さについて。よくあるミスは一般的で曖昧な名前をつけてしまうこと&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば &lt;code&gt;getCount&lt;/code&gt;よりも &lt;code&gt;numActiveIndexlets&lt;/code&gt;、&lt;code&gt;blinkStatus (bool)&lt;/code&gt; よりも &lt;code&gt;cursorVisible (book)&lt;/code&gt; のほうが好ましい&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;一貫性について。同じ概念には同じ単語を使う&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;違う概念には違う単語を使うことも重要&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;また良い名前が思いつかない時は、ひとつのエンティティで複数の概念を表そうとしているなど、そのエンティティ自体が適切でない可能性がある。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;File クラスのプロパティに FileBlock と名付けるなど、重複した単語は避けると良い。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Go のコーディングスタイルで、&lt;code&gt;b []byte&lt;/code&gt; といった非常に短い変数名を推奨するものがある。筆者の考えではわざわざこんなに短くしなくても、&lt;code&gt;buffer&lt;/code&gt; といった普通の名前をつけても可読性は落ちないと述べている。ただ定義場所と利用場所が離れている場合は短い名前をつけないという方針には賛同している。&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;15 - Write the Comment First&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;一般的にコメントは開発フェーズの最後に書かれることが多いが、ここではコメントファーストの開発プロセスを推奨している。コメントを先に書くことで、ドキュメントの質はもとより、設計の質も上げることができる。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;一般的なコメントを後に書くアプローチには次のデメリットがある。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;コメントがそもそも書かれない&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;実装完了後には次の新しい開発に早く取り掛かることにインセンティブがある&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;設計時から時間が経っており、良いドキュメントを書くのが難しい&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;一方コメントファーストの場合、次のような流れで設計実装は進む。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;新しいクラスを作る際、そのインタフェースコメントをまず書く&lt;/li&gt;&#xA;&lt;li&gt;次に重要な公開メソッドのシグネチャとインタフェースコメントを書く。この時中身は実装はまだ空&lt;/li&gt;&#xA;&lt;li&gt;全体の大まかな構造ができるまでこれを繰り返す&lt;/li&gt;&#xA;&lt;li&gt;重要なインスタンス変数があればそのコメントを記載する&lt;/li&gt;&#xA;&lt;li&gt;最後にロジックを実装する。必要があれば実装コメントも記載する&lt;/li&gt;&#xA;&lt;li&gt;実装中にメソッドの不足や設計の不備が見つかることもある。既存のコメントを更新しながら修正する&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;このアプローチには以下のメリットがある。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ドキュメントの質が上がる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;記憶がフレッシュなうちにコメントが書かれるし、実装に応じて最新に保たれる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;設計の質が上がる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;コメントを先に書くことが、初期設計に対するレビュー効果を生み出す。実装に入る前に設計を改善できる&lt;/li&gt;&#xA;&lt;li&gt;コメントが長くなったり書きづらかったりしたら、それは設計が悪い兆候&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;コメントを書く作業が楽しくなる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;設計はソフトウェアエンジニアリング全体の中でも楽しいフェーズ。その時にコメントを設計ツールとして使い、質を高めるのは楽しい作業&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;実装に入る前に設計を改善できるので、コメントファーストは投資対効率も良い。&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;16 - Modifying Existing Code&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;これまでは主に初期設計を念頭に置いた議論をしてきたが、既存のシステムに手を入れる際の注意点も考察する。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;投資マインドセットを改修時にも持つ&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;機能追加やバグ修正はいかに小さい変更で完了させるかにインセンティブが働くので、戦術的プログラミングになりがち&lt;/li&gt;&#xA;&lt;li&gt;戦略的にすることでシンプルな設計を保てるようにする&lt;/li&gt;&#xA;&lt;li&gt;理想的には「はじめからこの要件だっとして、最初から設計し直すとしたらこうなっている」という状態にすること&lt;/li&gt;&#xA;&lt;li&gt;ただ既存の仕組みのままコードを追加するだけだと、複雑さは維持ではなく悪化していると考える&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;改修にあわせてコメントも追従する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;これはコードの位置とコメントの位置を近づけておくとそれほど大変ではなくなる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;例えば実装コメントは、メソッドの先頭に全文記載するのではなく、そのコードの位置にそれぞれのコメントを記載するなど&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;コミットログに詳細な情報を載せるのではなく、コメントに載せる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;コミットログに比べるとコメントの方が開発者の目につきやすい&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;コメントの重複を省く&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;複数の影響箇所がある場合、どこに書くのが一番適切かを考える&lt;/li&gt;&#xA;&lt;li&gt;適切な一箇所がない場合は別の文章に記載し、コードからはそれをリファレンスする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;PR を作る前に差分全体を見返す習慣をつけるのも良い&lt;/li&gt;&#xA;&lt;li&gt;抽象度の高いコメントは書き換え頻度は低くなる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;不要な箇所で詳細なコメントを避ける&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;17 - Consistency&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;一貫性は知識にレバレッジをかけ認知負荷を減らし、ミスも防ぐ。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;一貫性を持たせる対象は多岐にわたる。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;名前&lt;/li&gt;&#xA;&lt;li&gt;コーディングスタイル&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;小さな組織では既存のスタイルを取り入れるところから始めると良い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;インタフェース&lt;/li&gt;&#xA;&lt;li&gt;デザインパターン&lt;/li&gt;&#xA;&lt;li&gt;invariants&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;複数人のチームで一貫性を保つのには相応の努力が必要になる。新メンバーがそれと気付かず一貫性違反を犯してしまうことはままある。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ドキュメントに記載する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;コーディングスタイルのような重要で対象範囲のひろい規約は目につきやすい場所に置いておく&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ツールで強制する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;違反がある場合コミット、マージできないようにし担保する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;コードレビューで共有する&lt;/li&gt;&#xA;&lt;li&gt;（個人のマインドセットとして）郷に従う&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;開発時には既存のコードベースをよく観察する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;既存の規約を安易に変更しない。新しい方針のほうが優れている確かな証拠と、時間をかけて変更するメリットが確実にあることを確認してから行う。&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;18 - Code Should Be Obvious&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;前述のように明確さはシステムの複雑さに影響する重要な要素。コードが不明確だと認知負荷が高まりバグの温床にもなる。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;明確さはあくまで読み手にとってのもの。書き手にとっては明確でもレビュアーがわかりづらいと感じたらそれは分かりづらい。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;明確なコードのために気をつけるポイント。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;良い名前をつける&lt;/li&gt;&#xA;&lt;li&gt;一貫性を保つ&lt;/li&gt;&#xA;&lt;li&gt;空白を使ってわかりやすくコードを整える&lt;/li&gt;&#xA;&lt;li&gt;コメントの活用&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;どうしてもコードにわかりづらい箇所は残る。そうした箇所はコメントで補う&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Event-driven programmingは可読性が落ちる&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;処理の流れが追いづらいので&lt;/li&gt;&#xA;&lt;li&gt;その処理がいつ呼び出されるのかをコメントで補足する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Java の pair のような generic containerは別々のものを一つの変数に曖昧な名前でまとめてしまうので、避ける&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ソフトウェアは書き手よりも読み手が楽できるようにするべき&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;読み手の予想を裏切る実装にはコメントで補足する&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;19 - Software Trends&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;オブジェクト指向と継承&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;継承にはインタフェースの継承と実装の継承の2種類があり、後者は複雑さを増やしかねない&lt;/li&gt;&#xA;&lt;li&gt;インタフェースの継承: シグネチャだけが定義されていて、サブクラスはそれを実装する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;様々な用途に同じインタフェースを使い回せるので、知識のレバレッジが効く。利用者は別の問題を同じやり方で解くことがてきるので、認知負荷が下がる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;実装の継承: ベースとなる実装がされていて、サブクラスはそれを使うかオーバーライドする&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;実装に関する知識がサブクラス（継承ヒエラルキーの各所）に漏れることになる&lt;/li&gt;&#xA;&lt;li&gt;そのため最悪のケースでは、変更のために継承ヒエラルキー全体を影響確認しないといけない。変更にかかるコストが非常に高くなる&lt;/li&gt;&#xA;&lt;li&gt;できるだけ使用せずコンポジションでの代替などを検討する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;アジャイル開発&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ソフトウェアの開発をインクリメンタルな取り組みと捉えている点が良いところ&lt;/li&gt;&#xA;&lt;li&gt;リスクがあるのは、アジャイルの開発プロセスは動くものを短期間に作ることにフォーカスしているため、戦術的プログラミングをしがちになること&lt;/li&gt;&#xA;&lt;li&gt;機能に対してではなく、抽象化に対してインクリメンタルにアプローチする、戦略的な進め方が理想&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;ユニットテスト&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;リファクタリングをするためにはユニットテストは必須。非常に重要な役割を担っている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;テスト駆動開発&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ユニットテストが大切なことに対して、テスト駆動開発にはリスクがある&lt;/li&gt;&#xA;&lt;li&gt;テスト駆動開発はある機能が動作することにフォーカスを置いており、これは戦術的なアプローチ&lt;/li&gt;&#xA;&lt;li&gt;ただしバグ修正には有効。まずバグを再現するテストを追加、失敗を確認してから修正し成功を確認する&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;デザインパターン&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;過剰適用のリスクに注意する。フィットしない問題に無理やりパターンを適用しない&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Getter, Setter&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Java ではポピュラーなパターン&lt;/li&gt;&#xA;&lt;li&gt;こちらも過剰適用に注意。盲目的に全ケースで必要なものでは無いはず&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;20 - Designing for performance&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;シンプルでパフォーマンスも良いプログラムはチャレンジングな面がある。遅いがシンプルなコードと、速いが複雑なコードの両極端の間を取る必要がある。ただ原則的には、シンプルなコードはやることが少ない分複雑なものよりも速いはずなので、シンプルかつ速いプログラムを目指す。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;まずは処理の速度に対する一般的な常識を身につけて普段の開発時には発揮する。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;常識とは例えば DC 内のネットワーク通信はローカルと比べて 10-100 倍オーダーで時間がかかるといったもの&lt;/li&gt;&#xA;&lt;li&gt;二つの同じようなアプローチがある場合、こうした常識に照らし合わせて、要件を満たしつつ速い方を選ぶ&lt;/li&gt;&#xA;&lt;li&gt;例えばマップ構造のデータを保持したいケースで、Hash Table と Ordered Map （インタフェースはほぼ同じ）の選択肢がある場合、順序が不要ならば前者を選ぶ&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;速い方のアプローチが複雑さを増やす場合、&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;あまり複雑にはならず、インタフェースも変わらない場合は、悩まずに実施する&lt;/li&gt;&#xA;&lt;li&gt;かなり複雑になりそうな場合、まずはシンプルなアプローチから始めて、実際にパフォーマンスの問題が発生してから対処する&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;変更の前後では必ず計測をすること。現状を計測し、影響が大きいボトルネックを特定し、計測値をベースラインに改善する。改善効果は同じ計測で評価する。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;ボトルネックを特定した後、まずは根本的な変更（キャッシュ導入、別アルゴリズムの導入など）が可能かを検討する。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;それができない場合はコードの再設計が必要になる。その際は Critical-path から始めると良い。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一般的なケースだけに対応していて最低限のコードからまず始める&lt;/li&gt;&#xA;&lt;li&gt;特殊ケースのハンドリングが入っていないので、無駄な分岐がなくシンプルで最速になっているはず&lt;/li&gt;&#xA;&lt;li&gt;この状態を理想として、特殊ケースのハンドリングを加えていく。できるだけ理想状態から乖離させないことを目指す&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;21 - Decide What Matters&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;良い設計の重要な要素のひとつに、何が重要で何がそうで無いかを決めるということがある。重要なことを中心に設計し、重要じゃないことの影響を可能な限り下げる。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;重要なことは、時にはパフォーマンス要件といった外部要因に起因する&lt;/li&gt;&#xA;&lt;li&gt;レバレッジに着目する。汎用化で議論したような、多くの場所から使われたり影響したりするものに着目する&lt;/li&gt;&#xA;&lt;li&gt;特定が難しい場合、あるオプションが最重要と一度仮定して設計をすすめ、評価するのが良い&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;要素数を最小化する。引数を減らす、例外のハンドリング箇所を減らすなど。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;重要なことが特定できたらそれを強調する。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ドキュメントや変数、引数名に使い目立たせる&lt;/li&gt;&#xA;&lt;li&gt;何度も繰り返し利用する&lt;/li&gt;&#xA;&lt;li&gt;その概念を中心にして設計する&lt;/li&gt;&#xA;&lt;li&gt;重要で無い概念にはこれらの反対をする&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;よくある失敗。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;重要な要素が多すぎる&lt;/li&gt;&#xA;&lt;li&gt;重要性を見誤る&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;div class=&#34;amazlet-box&#34; style=&#34;margin-bottom:0px;&#34;&gt;&lt;div class=&#34;amazlet-image&#34; style=&#34;float:left;margin:0px 12px 1px 0px;&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/B09B8LFKQL/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;&lt;img src=&#34;https://m.media-amazon.com/images/I/7146AYGD0AL._SY466_.jpg&#34; alt=&#34;A Philosophy of Software Design, 2nd Edition (English Edition)&#34; style=&#34;border: none; width: 113px;&#34; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-info&#34; style=&#34;line-height:120%; margin-bottom: 10px&#34;&gt;&lt;div class=&#34;amazlet-name&#34; style=&#34;margin-bottom:10px;line-height:120%&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/B09B8LFKQL/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;A Philosophy of Software Design, 2nd Edition (English Edition)&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-detail&#34;&gt;英語版  John K. Ousterhout (著)  形式: Kindle版&lt;br/&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-sub-info&#34; style=&#34;float: left;&#34;&gt;&lt;div class=&#34;amazlet-link&#34; style=&#34;margin-top: 5px&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/B09B8LFKQL/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Amazon.co.jpで詳細を見る&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-footer&#34; style=&#34;clear: left&#34;&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&#xA;&lt;div class=&#34;amazlet-box&#34; style=&#34;margin-bottom:0px;&#34;&gt;&lt;div class=&#34;amazlet-image&#34; style=&#34;float:left;margin:0px 12px 1px 0px;&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4873119650/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;&lt;img src=&#34;https://m.media-amazon.com/images/I/61Gx3rNo4LL._SY466_.jpg&#34; alt=&#34;Googleのソフトウェアエンジニアリング ―持続可能なプログラミングを支える技術、文化、プロセス&#34; style=&#34;border: none; width: 113px;&#34; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-info&#34; style=&#34;line-height:120%; margin-bottom: 10px&#34;&gt;&lt;div class=&#34;amazlet-name&#34; style=&#34;margin-bottom:10px;line-height:120%&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4873119650/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Googleのソフトウェアエンジニアリング ―持続可能なプログラミングを支える技術、文化、プロセス&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-detail&#34;&gt;竹辺 靖昭 (監修), Titus Winters (編集), Tom Manshreck (編集), Hyrum Wright (編集), 久富木 隆一 (翻訳)&lt;br/&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-sub-info&#34; style=&#34;float: left;&#34;&gt;&lt;div class=&#34;amazlet-link&#34; style=&#34;margin-top: 5px&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/4873119650/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Amazon.co.jpで詳細を見る&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-footer&#34; style=&#34;clear: left&#34;&gt;&lt;/div&gt;&lt;/div&gt;&#xA;</description>
      <author>Kosei Moriyama</author>
      <pubDate>Thu, 16 Nov 2023 23:00:00 +0900</pubDate>
    </item>
    <item>
      <title>GCP CloudSQL MySQL での Replication Lag と innodb_flush_log_at_trx_commit</title>
      <link>https://please-sleep.cou929.nu/gcp-cloudsql-mysql-replication-lag-and-fsync-frequency-of-logs.html</link>
      <description>&lt;p&gt;Primary (Source) - Replica 構成の MySQL クラスタを GCP の CloudSQL で構築し本番運用している。このうち一台の Replica の Replication Lag が急増したことがあった。同僚のアドバイスで &lt;code&gt;innodb_flush_log_at_trx_commit&lt;/code&gt; を調整し Lag は解消した。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;これは後日 &lt;code&gt;innodb_flush_log_at_trx_commit&lt;/code&gt; の意味を調べた際のメモ。&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;レプリケーションの仕組みのおさらい&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://please-sleep.cou929.nu/efficient_mysql_performance.html&#34;&gt;以前読んだ Efficient MySQL Performance&lt;/a&gt; の図と説明がわかりやすいのでそちらから引用する。&lt;/p&gt;&#xA;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;images/efficient_mysql_performance_7-1_replication_foundation.png&#34; alt=&#34;efficient_mysql_performance_7-1_replication_foundation&#34; width=300 /&gt;&#xA;&lt;figcaption&gt;Efficient MySQL Performance (p. 235) Figure 7-1. Foundation of MySQL source to replica replication より&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Source でトランザクションがコミットされるとデータの変更が Binarly Log (binlog) に書き出される&lt;/li&gt;&#xA;&lt;li&gt;Replica の IO Thread が Source の binary log events を取得する&lt;/li&gt;&#xA;&lt;li&gt;Replica の IO Thread が取得した binary log events を Relay Log に書き出して永続化する&lt;/li&gt;&#xA;&lt;li&gt;Replica の SQL Thread (applier と呼ばれることもある) が Relay Log から binary log events を読み込む&lt;/li&gt;&#xA;&lt;li&gt;Replica の SQL Thread が読み込んだ binary log events をデータに反映させる&lt;/li&gt;&#xA;&lt;li&gt;Replica は適用された変更を Replica の binlog に書き出す&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;冗長性のため (Source にプロモートするときに備えて) 書き出している。後述するが CloudSQL ではデフォルトオフのようだった&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;Source にトランザクションがコミットされてから、それが Replica に反映されるまでの遅延が Replication Lag と呼ばれる。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;SQL Thread で遅延することが多い&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;ネットワークの遅延は相対的に少なく、IO Thread はイベント単位にまとめられた情報をシーケンシャルにログファイルに書き出すだけなので、それを解釈して適用する SQL Thread に比べてスループットが出る&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;Source が Replica の ack を待ったりしない限り、仕組み上レプリケーションにラグは不可避だが、以下の理由で通常は Source よりも高速に動作する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Replica は Source よりも少ないワークロードしか扱わないので。特に Read しか発生しないので&lt;/li&gt;&#xA;&lt;li&gt;binary log events (変更適用済みの row image) を元に作業するので。Source はクエリを解釈し対象行を探しそこに変更を加えるが、Replica は変更の適用の作業だけをしている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;Binary Log&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;補足として、バイナリログについて MySQL のドキュメントから引用しておく。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://dev.mysql.com/doc/refman/8.0/en/binary-log.html&#34;&gt;MySQL :: MySQL 8.0 Reference Manual :: 5.4.4 The Binary Log&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;バイナリログはデータベースへの変更を &amp;ldquo;イベント&amp;rdquo; として記録したもの。主に以下の用途がある。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;レプリケーション。バイナリログが Source から Replica に転送され、レプリカはそれを順に適用することで Source と同じ状態に追従して言っている&lt;/li&gt;&#xA;&lt;li&gt;リカバリ。ある時点のバックアップを空のデータベースにリストアした後、バイナリログから残りの差分を適用することで Source と同じ状態にすることができる&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;今回の主旨とは関係ないが、バイナリログの &lt;a href=&#34;https://dev.mysql.com/doc/refman/8.0/en/replication-options-binary-log.html#sysvar_binlog_format&#34;&gt;フォーマット&lt;/a&gt; には Row-based, Statement-based, Mixed-base がある。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Row-based はあるデータがどう変更されたかが記録されている。デフォルト&lt;/li&gt;&#xA;&lt;li&gt;Statement-based は発行されたクエリが記録されている。クエリによっては結果が非決定的なリスクがある&lt;/li&gt;&#xA;&lt;li&gt;Mixed-base は statement-based と row-based を適切に使い分ける&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;またレプリケーション時にトランザクションごとにグローバルな ID (GTID) が割り振られている。この ID を使うことで binlog のどこから反映すればよいかの特定などが容易になる。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://dev.mysql.com/doc/refman/8.0/en/replication-gtids.html&#34;&gt;MySQL :: MySQL 8.0 Reference Manual :: 17.1.3 Replication with Global Transaction Identifiers&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;CloudSQL では &lt;a href=&#34;https://cloud.google.com/sql/docs/mysql/replication/replication-lag#high_performance_flushing&#34;&gt;デフォルトで Row-based フォーマット、GTID 有効&lt;/a&gt; となっている。&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;遅延箇所の切り分け&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;前述のように Replication Lag は SQL Thread で起こることが多いとのことだが、実環境で遅延がどこで起こっているかを切り分ける方法が CloudSQL のドキュメントに簡潔にまとまっていた。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://cloud.google.com/sql/docs/mysql/replication/replication-lag#verify_replication&#34;&gt;Replication lag  |  Cloud SQL for MySQL  |  Google Cloud&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;GCP では &lt;code&gt;Replication lag (cloudsql.googleapis.com/database/replication/replica_lag)&lt;/code&gt; と &lt;code&gt;Network lag (cloudsql.googleapis.com/database/replication/network_lag)&lt;/code&gt; というモニタリング指標が提供されている。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;Replication lag&lt;/code&gt; はいわゆる &lt;a href=&#34;https://dev.mysql.com/doc/refman/8.0/en/replication-administration-status.html&#34;&gt;&lt;code&gt;SHOW REPLICA STATUS&lt;/code&gt; の &lt;code&gt;Seconds_Behind_Master&lt;/code&gt;&lt;/a&gt; で Source からの遅れを秒単位で記録している&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;Network lag&lt;/code&gt; は Source の Binlog が Replica の IO Thread に到着するまでの時間&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;&lt;code&gt;Replication lag&lt;/code&gt; が大きい状況で &lt;code&gt;Network lag&lt;/code&gt; が小さければ、遅延は SQL Thread で起こっていると判断することができる。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;より一般的には &lt;code&gt;SHOW REPLICA STATUS&lt;/code&gt; の結果を解釈する。&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sql&#34;&gt;-- 出力例。&#xA;-- https://cloud.google.com/sql/docs/mysql/replication/replication-lag#verify_replication より引用。&#xA;mysql&amp;gt; SHOW SLAVE STATUS\G;&#xA;*************************** 1. row ***************************&#xA;               Slave_IO_State: Queueing master event to the relay log&#xA;                  Master_Host: xx.xxx.xxx.xxx&#xA;                  Master_User: cloudsqlreplica&#xA;                  Master_Port: 3306&#xA;                Connect_Retry: 60&#xA;              Master_Log_File: mysql-bin.199927&#xA;          Read_Master_Log_Pos: 83711956&#xA;               Relay_Log_File: relay-log.000025&#xA;                Relay_Log_Pos: 24214376&#xA;        Relay_Master_Log_File: mysql-bin.199898&#xA;             Slave_IO_Running: Yes&#xA;            Slave_SQL_Running: Yes&#xA;              Replicate_Do_DB:&#xA;          Replicate_Ignore_DB:&#xA;           Replicate_Do_Table:&#xA;       Replicate_Ignore_Table:&#xA;      Replicate_Wild_Do_Table:&#xA;  Replicate_Wild_Ignore_Table:&#xA;                   Last_Errno: 0&#xA;                   Last_Error:&#xA;                 Skip_Counter: 0&#xA;          Exec_Master_Log_Pos: 24214163&#xA;              Relay_Log_Space: 3128686571&#xA;              Until_Condition: None&#xA;               Until_Log_File:&#xA;                Until_Log_Pos: 0&#xA;           Master_SSL_Allowed: Yes&#xA;           Master_SSL_CA_File: master_server_ca.pem&#xA;           Master_SSL_CA_Path: /mysql/datadir&#xA;              Master_SSL_Cert: replica_cert.pem&#xA;            Master_SSL_Cipher:&#xA;               Master_SSL_Key: replica_pkey.pem&#xA;        Seconds_Behind_Master: 2627&#xA;Master_SSL_Verify_Server_Cert: No&#xA;                Last_IO_Errno: 0&#xA;                Last_IO_Error:&#xA;               Last_SQL_Errno: 0&#xA;               Last_SQL_Error:&#xA;  Replicate_Ignore_Server_Ids:&#xA;             Master_Server_Id: 321071839&#xA;                  Master_UUID: 437d04e9-8456-11e8-b13d-42010a80027b&#xA;             Master_Info_File: mysql.slave_master_info&#xA;                    SQL_Delay: 0&#xA;          SQL_Remaining_Delay: NULL&#xA;      Slave_SQL_Running_State: System lock&#xA;           Master_Retry_Count: 86400&#xA;                  Master_Bind:&#xA;      Last_IO_Error_Timestamp:&#xA;     Last_SQL_Error_Timestamp:&#xA;               Master_SSL_Crl:&#xA;           Master_SSL_Crlpath:&#xA;           Retrieved_Gtid_Set: 437d04e9-8456-11e8-b13d-42010a80027b:52111095710-52120776390&#xA;            Executed_Gtid_Set: 437d04e9-8456-11e8-b13d-42010a80027b:1-52113039508&#xA;                Auto_Position: 1&#xA;         Replicate_Rewrite_DB:&#xA;                 Channel_Name:&#xA;           Master_TLS_Version:&#xA;1 row in set (0.00 sec)&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;&lt;code&gt;Master_Log_File&lt;/code&gt; (IO Thread が読み込んだ最新の Binary log ファイル名) と &lt;code&gt;Relay_Master_Log_File&lt;/code&gt; (SQL Thread が適用した最新の Relay log ファイル名) を比較し、ここが大きく離れていれば SQL Thread で遅延しているとみなすことができる。また &lt;code&gt;Network lag&lt;/code&gt; 相当の情報は Source の &lt;code&gt;SHOW MASTER STATUS&lt;/code&gt; での binlog 位置と Replica のそれとを比べることで判断することができる。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;なお Binary log, Relay log は &lt;a href=&#34;https://dev.mysql.com/doc/refman/8.0/en/binary-log.html&#34;&gt;ファイル名に通し番号がついている&lt;/a&gt; ので、そこを見比べで比較できるようになっている。上記の例では &lt;code&gt;mysql-bin.199898&lt;/code&gt; のようなフォーマットになっている。&lt;/p&gt;&#xA;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;mysqld appends a numeric extension to the binary log base name to generate binary log file names. The number increases each time the server creates a new log file, thus creating an ordered series of files.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&#xA;&lt;p&gt;この他にも IO Thread、SQL Thread のエラーも確認することができる。また同じ情報は GCP のモニタリング指標として &lt;code&gt;Last I/O thread error number (cloudsql.googleapis.com/database/mysql/replication/last_io_errno)&lt;/code&gt;、&lt;code&gt;Last SQL thread error number (cloudsql.googleapis.com/database/mysql/replication/last_sql_errno)&lt;/code&gt; が提供されている。&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;innodb_flush_log_at_trx_commit&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;SQL Thread で遅延している場合、解決策のひとつとして &lt;code&gt;innodb_flush_log_at_trx_commit&lt;/code&gt; の調整が挙げられる。&lt;a href=&#34;https://cloud.google.com/sql/docs/mysql/replication/replication-lag#high_performance_flushing&#34;&gt;GCP のドキュメントにも記載がある&lt;/a&gt; とおり、前提としてこれは durability を犠牲にしてパフォーマンスを向上させるというトレードオフを伴う。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_flush_log_at_trx_commit&#34;&gt;MySQL :: MySQL 8.0 Reference Manual :: 15.14 InnoDB Startup Options and System Variables&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;code&gt;innodb_flush_log_at_trx_commit&lt;/code&gt; は Redo log を Disk に flush する頻度を管理しているフラグ。デフォルトは &lt;code&gt;1&lt;/code&gt; でこれはトランザクションのコミットごとに flush する。そのため意図しない DB インスタンスのクラッシュなどがあった場合も、確実にトランザクションが永続化されていることを保証できる。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;code&gt;0&lt;/code&gt; は 1 秒ごとに Redo log への書き込みと flush を行う。&lt;code&gt;2&lt;/code&gt; はトランザクションのコミットごとに Redo log への書き込みは行うが、flush は 1 秒おきになる。いずれも意図しないクラッシュが起こるとコミットされたトランザクションの変更が失われる可能性があるが、相対的に時間がかかる Disk への sync を非同期にすることでパフォーマンスは向上する。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;一般的に Replica においては &lt;code&gt;innodb_flush_log_at_trx_commit&lt;/code&gt; を &lt;code&gt;1&lt;/code&gt; 以外にする余地がある。Replica インスタンスがクラッシュし一部のトランザクションが欠けるとたいていは Replication でエラーが出て開発者が検出可能だと思われる。またクラッシュが起こった時点でその Replica を作り直すという運用方針も考えられる。&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;Redo Log&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;Redo Log について補足しておく。こちらも &lt;a href=&#34;https://please-sleep.cou929.nu/efficient_mysql_performance.html&#34;&gt;以前読んだ Efficient MySQL Performance&lt;/a&gt; から引用する。&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Transaction log、あるいは単に The log と呼ばれることもある&lt;/li&gt;&#xA;&lt;li&gt;データに変更があった際にログに書き込みディスクに永続化する&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;データの耐久性のため&lt;/li&gt;&#xA;&lt;li&gt;buffer pool の page flushing とは別の概念&lt;/li&gt;&#xA;&lt;li&gt;transaction log への書き込みはすぐに (buffer pool とは別に) フラッシュされ永続化される。それとは別に buffer pool の dirty page のフラッシュがされているという考え方になる。例えば transaction log は永続化されているが、buffer pool への適用はまだ disk にフラッシュされていないという状態も起こる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3&gt;sync_binlog&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;&lt;code&gt;sync_binlog&lt;/code&gt; オプションについても補足しておく。Cloud SQL ドキュメントの以下のセクションには、&lt;code&gt;innodb_flush_log_at_trx_commit&lt;/code&gt; と共に &lt;code&gt;sync_binlog&lt;/code&gt; オプションも調整するよう記載されている。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://cloud.google.com/sql/docs/mysql/flags#tips-flush-log&#34;&gt;Configure database flags  |  Cloud SQL for MySQL  |  Google Cloud&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://dev.mysql.com/doc/refman/8.0/en/replication-options-binary-log.html#sysvar_sync_binlog&#34;&gt;sync_binlog&lt;/a&gt; は、Binary log のディスクへの flush を管理するフラグ。Redo log に対する &lt;code&gt;innodb_flush_log_at_trx_commit&lt;/code&gt; と似ている。&lt;code&gt;1&lt;/code&gt; はトランザクションのコミットごとで、&lt;code&gt;0&lt;/code&gt; は明示的な sync はしない (OS の挙動に任せる)、&lt;code&gt;N (2 以上)&lt;/code&gt; は binary log commit group が N 以上溜まったら sync するという設定になる。デフォルトは &lt;code&gt;1&lt;/code&gt; で、意図しないクラッシュ時でもトランザクションがレプリカに反映されない状況を防いでいる。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;その DB インスタンスで Binary log を有効化している場合、デフォルトではトランザクションのコミットごとに、(1) Redo log への書き込みと sync、(2) Binary log への書き込みと sync が同期的に行われていることになる。&lt;code&gt;innodb_flush_log_at_trx_commit&lt;/code&gt; を &lt;code&gt;1&lt;/code&gt; 以外の値に変更した場合、&lt;code&gt;(1)&lt;/code&gt; の sync は非同期化されるが &lt;code&gt;(2)&lt;/code&gt; は同期のままなので、あわせて &lt;code&gt;sync_binlog&lt;/code&gt; も &lt;code&gt;1&lt;/code&gt; 外の値に調整しておいたほうが、よりパフォーマンス向上効果が発揮されることになる。Redo log の durability を一部犠牲にできるのなら、binlog だけコミット都度 sync にするメリットはあまりないので、合わせて調整しておくのは妥当だと思われる。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;なお CloudSQL の場合、Replica インスタンスではデフォルトで Binary log は無効化されているようだった。その場合 &lt;code&gt;sync_binlog&lt;/code&gt; の調整は必要ない。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://cloud.google.com/sql/docs/mysql/replication#bin-log-replica&#34;&gt;About replication in Cloud SQL  |  Cloud SQL for MySQL  |  Google Cloud&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Binary logging is supported on read replica instances (MySQL 5.7 and 8.0 only).&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&#xA;&lt;h2&gt;CloudSQL MySQL の挙動&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://cloud.google.com/sql/docs/mysql/replication/replication-lag#high_performance_flushing&#34;&gt;Replication lag  |  Cloud SQL for MySQL  |  Google Cloud&lt;/a&gt; の以下の記載によると、CloudSQL MySQL では Replica で &lt;code&gt;innodb_flush_log_at_trx_commit&lt;/code&gt; が変更されていて、かつクラッシュが検出された場合、自動でその Replica を作り直してれるらしい。実際に遭遇したことは無いが、運用の手間が減り助かる機能だと思う。&lt;/p&gt;&#xA;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;When the innodb_flush_log_at_trx_commit flag is set on the read replica and Cloud SQL detects that a crash might have occurred, Cloud SQL automatically recreates the replica.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&#xA;&lt;p&gt;CloudSQL MySQL では &lt;code&gt;innodb_flush_log_at_trx_commit&lt;/code&gt; として &lt;code&gt;0&lt;/code&gt; は指定できない。また &lt;code&gt;2&lt;/code&gt; が指定されている Replica を Primary にプロモートした際には、自動で &lt;code&gt;1&lt;/code&gt; に書き換えられる。&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://cloud.google.com/sql/docs/mysql/flags#mysql-i&#34;&gt;Configure database flags  |  Cloud SQL for MySQL  |  Google Cloud&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;参考&lt;/h2&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://please-sleep.cou929.nu/efficient_mysql_performance.html&#34;&gt;Efficient MySQL Performance を読んだ - Please Sleep&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_flush_log_at_trx_commit&#34;&gt;MySQL :: MySQL 8.0 Reference Manual :: 15.14 InnoDB Startup Options and System Variables&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://dev.mysql.com/doc/refman/8.0/en/binary-log.html&#34;&gt;MySQL :: MySQL 8.0 Reference Manual :: 5.4.4 The Binary Log&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://dev.mysql.com/doc/refman/8.0/en/replication-options-binary-log.html#sysvar_binlog_format&#34;&gt;MySQL :: MySQL 8.0 Reference Manual :: 17.1.6.4 Binary Logging Options and Variables&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://dev.mysql.com/doc/refman/8.0/en/replication-administration-status.html&#34;&gt;MySQL :: MySQL 8.0 Reference Manual :: 17.1.7.1 Checking Replication Status&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://cloud.google.com/sql/docs/mysql/replication/replication-lag&#34;&gt;Replication lag  |  Cloud SQL for MySQL  |  Google Cloud&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://cloud.google.com/sql/docs/mysql/flags#tips-flush-log&#34;&gt;Configure database flags  |  Cloud SQL for MySQL  |  Google Cloud&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://cloud.google.com/sql/docs/mysql/replication&#34;&gt;About replication in Cloud SQL  |  Cloud SQL for MySQL  |  Google Cloud&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2&gt;PR&lt;/h2&gt;&#xA;&#xA;&lt;div class=&#34;amazlet-box&#34; style=&#34;margin-bottom:0px;&#34;&gt;&lt;div class=&#34;amazlet-image&#34; style=&#34;float:left;margin:0px 12px 1px 0px;&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/B09N5NWKR1/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;&lt;img src=&#34;https://m.media-amazon.com/images/I/4151TBrJq1L.jpg&#34; alt=&#34;Efficient MySQL Performance (English Edition)&#34; style=&#34;border: none; width: 113px;&#34; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-info&#34; style=&#34;line-height:120%; margin-bottom: 10px&#34;&gt;&lt;div class=&#34;amazlet-name&#34; style=&#34;margin-bottom:10px;line-height:120%&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/B09N5NWKR1/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Efficient MySQL Performance (English Edition)&lt;/a&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-detail&#34;&gt;英語版  Daniel Nichter  (著)  形式: Kindle版&lt;br/&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-sub-info&#34; style=&#34;float: left;&#34;&gt;&lt;div class=&#34;amazlet-link&#34; style=&#34;margin-top: 5px&#34;&gt;&lt;a href=&#34;http://www.amazon.co.jp/exec/obidos/ASIN/B09N5NWKR1/pleasesleep-22/ref=nosim/&#34; name=&#34;amazletlink&#34; target=&#34;_blank&#34;&gt;Amazon.co.jpで詳細を見る&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;amazlet-footer&#34; style=&#34;clear: left&#34;&gt;&lt;/div&gt;&lt;/div&gt;&#xA;</description>
      <author>Kosei Moriyama</author>
      <pubDate>Thu, 27 Jul 2023 20:30:00 +0900</pubDate>
    </item>
    <item>
      <title>最近読んだもの 58 - SQLite の過去現在未来など</title>
      <link>https://please-sleep.cou929.nu/recently-readings-058.html</link>
      <description>&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.vldb.org/pvldb/vol15/p3535-gaffney.pdf&#34;&gt;SQLite: Past, Present, and Future&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.barroso.org/publications/TheTailAtScale.pdf&#34;&gt;The tail at scale&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://blog.pragmaticengineer.com/project-management-at-big-tech/&#34;&gt;How Big Tech Runs Tech Projects and the Curious Absence of Scrum - The Pragmatic Engineer&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.percona.com/blog/mysql-data-caching-efficiency/&#34;&gt;MySQL Data Caching Efficiency&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;Innodb_buffer_pool_read_requests&lt;/code&gt; と &lt;code&gt;Innodb_buffer_pool_reads&lt;/code&gt; からいわゆるバッファプールのキャッシュヒット率を計算できる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://biriukov.dev/docs/fd-pipe-session-terminal/0-sre-should-know-about-gnu-linux-shell-related-internals-file-descriptors-pipes-terminals-user-sessions-process-groups-and-daemons/&#34;&gt;GNU/Linux shell related internals | Viacheslav Biriukov&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;File descripter, pipe, process groups, terminal といった話題を解説する一連の記事&lt;/li&gt;&#xA;&lt;li&gt;リチャードスティーブンスの APUE のファイルに関する章のような内容を説明しているが、より現代の視点から書かれているのと、説明のテンポが良く読みやすいのがいい&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.percona.com/blog/mysql-5-7-upgrade-issue-reserved-words/&#34;&gt;MySQL 5.7 Upgrade Issue: Reserved Words&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;MySQL 8 系にアップグレードすると、予約語が増える&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;rank&lt;/code&gt;, &lt;code&gt;system&lt;/code&gt;, &lt;code&gt;skip&lt;/code&gt;, &lt;code&gt;lead&lt;/code&gt; などは引っかかりやすそう&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://hackmysql.com/post/commit-latency-aurora-vs-rds-mysql-8.0/&#34;&gt;COMMIT Latency: Aurora vs. RDS MySQL 8.0&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;SELECT&lt;/code&gt; や &lt;code&gt;UPDATE&lt;/code&gt; はおおむねメモリ上での操作ナノに対して、&lt;code&gt;COMMIT&lt;/code&gt; はディスクへの書き込みを伴う&lt;/li&gt;&#xA;&lt;li&gt;Aurora と RDS で &lt;code&gt;COMMIT&lt;/code&gt; のレイテンシを調査したところ、Aurora のほうが安定して良い結果だった&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://dev.mysql.com/blog-archive/what-is-the-scanning-variant-of-a-loose-index-scan/&#34;&gt;MySQL :: What is the &amp;ldquo;(scanning)&amp;rdquo; variant of a loose index scan?&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;GROUP BY&lt;/code&gt; などでインデックスを走査する際に、インデックスを全走査せず不要な部分を飛ばす最適化をした際に、実行計画に loose index scan と表示される&lt;/li&gt;&#xA;&lt;li&gt;例えば &lt;code&gt;GROUP BY&lt;/code&gt; でグループごとの最小値を集計する場合、インデックスのソート順がうまく使える場合は、GROUP BY の各キーの最初の値だけを見てそれ移行は飛ばすことができる&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://hackmysql.com/post/announcing-blip-mysql-monitor/&#34;&gt;Announcing Blip: A New MySQL Monitor&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;新しい MySQL のモニタリングツール&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://blog.gregbrockman.com/how-i-became-a-machine-learning-practitioner?ref=matt-rickard.com&#34;&gt;How I became a machine learning practitioner&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;OpenAI の共同創業者で元 Stripe の CTO である Greg Brockman が、OpenAI に参画してから、自分の専門性とは異なる AI 分野に対して試行錯誤した記録&lt;/li&gt;&#xA;&lt;li&gt;このレベルの人でも泥臭く失敗しながらやっていることが綴られていて興味深い&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://planetscale.com/blog/connection-pooling&#34;&gt;Connection pooling in Vitess&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Vitess のコネクションプールについて、その意義、セッション単位の設定とコネクションプールの相性の悪さ、それを回避するための各種方法など&lt;/li&gt;&#xA;&lt;li&gt;セッション単位の設定があるクエリでコネクションプールの旨味をできるだけ活かすために、書き換えられるものは SET_VAR に書き換えて、それが難しければ専用の接続をそのクライアント用に確保する&lt;/li&gt;&#xA;&lt;li&gt;Vitess15 からは &lt;code&gt;Settings Pool&lt;/code&gt; という新しい仕組みが導入された。特定の設定に変更されたセッションをプールに保持するというアプローチ&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.percona.com/blog/comparisons-of-proxies-for-mysql/&#34;&gt;Comparisons of Proxies for MySQL&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;MySQL Proxy の比較&lt;/li&gt;&#xA;&lt;li&gt;性能は haproxy &amp;gt; Porxy SQL だが、機能的には後者が多機能なので使い分け&lt;/li&gt;&#xA;&lt;li&gt;MySQLRouter はかなり悪い結果となっている&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://thoughtbot.com/blog/a-healthy-bundle&#34;&gt;A Healthy Bundle&lt;/a&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Gemfile などパッケージ管理システムで定義するライブラリのバージョン指定は、特定のバージョン固定やメジャーバージョンだけ指定するなど柔軟にできるが、どのような方針で指定すべきか&lt;/li&gt;&#xA;&lt;li&gt;例えば Rails といった影響範囲が大きいものは細かくバージョンを指定する、まだ安定していないライブラリ (後方互換性を壊す変更が入ったり、ブレイキングチェンジがまだまだあり得るもの) はメジャーバージョン・マイナーバージョンまで固定する悲観的な指定を、安定していて後方互換性を壊す変更が入ることもないものはバージョン指定をなくすなど楽観的な指定をする&lt;/li&gt;&#xA;&lt;/ul&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
      <author>Kosei Moriyama</author>
      <pubDate>Sun, 23 Apr 2023 23:20:00 +0900</pubDate>
    </item>
  </channel>
</rss>