PostgreSQLアンカンファレンスに参加した

あと発表もしました。ので日記を書きます。

pgunconf.connpass.com

日記

人の話をまったく聞けない人間なので、勉強会は基本的に発表目的あるいは発表ドリブン学習目的で参加することにしています。ただ、今回は諸々が立て込んでおり発表を諦めていました。

PostgreSQLアンカンファレンスは、「会場の入口に空のタイムテーブルが張り出され、参加者がその枠内に付箋で発表内容を貼り付ける」というスタイルでコンテンツが決まっていきます。

参加者の割に枠がけっこう空いていたのと、まあせっかく来たしっていうのと、いまから自分を追い込んだら発表までいけるのでは?というゲーマー精神により、とりあえず付箋を貼っつけました。

そこから2時間くらいで資料を作成しました*1。内容は、昨年のPostgreSQLアドベントカレンダーに書いた以下の記事をベースに、宣伝とかを散りばめました。

kyabatalian.hatenablog.com

ディスプレイケーブルの不調などありつつも(手間取ってごめんなさい)、なんとか発表は無事に終わりました。強いエンジニアの方々からご質問やご意見をいくつもいただきました。PostgreSQLの実装にまで食い込んだ議論になり、楽しかったです。ありがとうございました。

最後のセッションでは、参加者がこれまでに遭遇したクソ趣のあるクエリを共有してみんなで解決策を考える、という会が催されていました。アンカンファレンスとは別かもしれませんが、これはこれで独立した会としてあってもおもしろそうと思いました。

終了後には懇親会があったのですが、持ち前のコミュ障を遺憾なく発揮して、普通に帰宅しました。次回もしあればがんばって参加できるようにコミュ力を鍛えておきます。

ふりかえり

勢いで発表してよかったです。人前で話すのが苦手なのでよい経験になりましたし、強いエンジニアの方々と議論もできました。発表のハードルを下げることにも寄与できたと思います。

一方で、やっぱりちゃんと準備せなアカンと思いました。まず前半はスライドづくりに必死で、他の方の発表をまともに聞けていないです。すみません。また、資料の推敲や発表練習などもできておらず、全体的に荒くなってしまっていたと思います。内容としても勉強不足を感じました。発表するにしても目的とそれに応じた準備期間を設計すべきでした。承認欲求と人生に対する焦りからか、アウトプットに意識が寄りすぎていました。勉強します。

発表を聞いてくれた方、コメントしてくれた方、運営のみなさま、ありがとうございました!!!

*1:あえて公開はナシで

『エンジニアの知的生産術』を読んだ

意識高そうな本を読んで意識が高まったので書いています。たぶん寝て起きたらもとに戻っています。

どんな本か

gihyo.jp

仕事をするうえで,どのように学び,整理し,アウトプットするのか。ソフトウェアエンジニア向けに,プログラミングと執筆を具体例として,知的生産の方法を解説した書籍です。

とのこと。著者は西尾泰和さんという方で、現在はサイボウズ・ラボにお勤めで、コーディングを支える技術の著者でもあります。平たく言ってめっちゃデキる人間のようです。昨年8月に発売され、かなり話題になっていました。

なぜ読んだか

知的な生産性をあげたいからです。自分はオッサンに分類される年齢ですが、エンジニアを名乗るにはあまりに能力が足りないと自覚しており、やるべきこと、やりたいことが山積みです。それらを愚直にやっていくには人生が足りなさすぎますし、若者たちに淘汰されて終了しそうです。本書は特にエンジニア向けに書かれているとのことで、目次をチラ見してこれはイイゾとなったので読みました。

読んでどうだったか

良かったです。どこかでみたことあるような…という内容も多い気がするものの、ある程度学術的に意味のあるリファレンスと紐付いて各手法が紹介されています。また、著者が本書を書くにあたって実践した内容もあわせて紹介されており、読みやすく有益な内容にまとまっています。とはいえ、これらを実践していかないと意味がないです。ついついこういう意識高い本を読んで満足しがちなダメ人間なので、今回こそ実践していきたい気持ちでいっぱいです。

以下、印象に残った箇所のメモや感想など。

やる気のでるタスクを設計する

知的生産性の研究者である著者が、学習のサイクルを回す原動力を「やる気」というエモい言葉で表現しているのが印象的でした。その上で、脳に報酬を与えてやる気を維持するタスク設計について理路整然と書かれています。

明確な目標設定が重要です。例えば「Pythonをマスターする」とか「DDDを理解する」というのは曖昧な目標であり、やる気を阻害します。仕事など必要に迫られるのがベストですが、そうではない場合にも「ブログを書く」「人に説明する」など、アウトプットという形で達成したか否かが計測可能な目標を設定します。ここで、目標自体が大きい場合、さらに分割します。例えば、「○○という本を読んで得た知識をブログに書く」という目標の前段階として、「得た知識を付箋に100枚書き出す」という過程の目標を置きます。これがいいのは、タスクが小さいことと中断可能なことです。このように、やる気を維持するには、「達成したいこと」に対して、闇雲に向かうのではなく、タスク設計をちゃんとやる必要があります。

自分がよくやってしまうのは、「○○を検討する」とか「○○を整理する」みたいな、達成点が曖昧なことをぼんやり頭に残っている状態です。信じられないくらい進捗が無になります。時間で区切ったり、だれかに説明したり、社内共有のドキュメントにまとめたり、なるべく具体化する作業をやるべきです。頭ではわかっていますが、それ自体がだるかったりします。ちゃんとやります。

全体像を把握する

技術書を普通に通読するとかなりの時間がかかります。そこでボトルネックとなっているのは、ページ送りや目の動きではなく、脳の理解であり、さらには「理解の組み立て」です。この組み立てを効率よく行うために、まず目次を把握するという方法や、ページをパラパラと読んでキーワードを抜き出す、など、全体を把握する読み方が有効だとしています。これはコードを読む場合も同じで、READMEやドキュメントを読み、ディレクトリ構造を把握してからコードを読んだほうが効率的です。自分も本に応じて、また読書の目的に応じて、読み方を変えたいです。すぐ通読しようとしてしまいます。グッと堪えます。

自分は小~中学生のころけっこう本を読む人間でしたし、その後も主に小説を好んで読んでいたりして、通読するクセみたいなものがついている気がします。あと、脳が負荷を避けているのもありそうです。目次を読んで頭に入れたり内容を想像するのは、トータルでみると効率を上げるはずですが、思考疲れというか、文字を追うだけの読書に逃げている感覚です。その意味で、学習のための読書はかなり消耗することを自覚し、ちゃんと休みましょう。

いったんぜんぶ書き出す

「GTD」というメソッドが紹介されています。この文字列はどこかでみたことがあったのですが、内容は初めて知りました。Getting Things Doneです。「やる気が出ない人の65%はタスクを1つに絞れていない」らしいです。わかる。人間の脳はマルチタスクができないように作られていないみたいですし、自分は特にそうです。コンテキストスイッチは想像以上に消耗します。タスクを絞るためには全体を把握します。

GTDはDavid Allenというひとが開発したメソッドで、なんと日本版公式サイトもあります。概要はそちらをどうぞ。超絶雑にいうと、「いま気になっていることを全部書きだし、それを上から順番にやっていく」です。一般的なタスク管理と違うのは、「タスクかどうかにかかわらず気になることを全部書く」という点と、「優先順位をつけない」という点です。それら自体が重い作業なので、放棄する、というものです。

タスク管理はそれ自体が目的になって、きちんとやりたくなってしまいますが、そこにかけるコストを自覚してバランスをとっていきたいです。タスク管理をあまりがんばらないようにします。仕事ではいまはAsanaを使っています。一日の頭にタスクを整理する時間をとっています。この前段階としてGTDをやる時間をとってもいいかもと思いました。

不確かなときは楽観的に

GTDで優先順位をつけないとはいえ、現実にはそれぞれのタスクには時間的な制約があり、優先順位をつけざるを得ない場面がほとんどです。優先順位付けというタスクが重いのは、不確定要素のためです。「不確かなときは楽観的に」というのは、ポジティブバンザイ脳ではなく、ちゃんとした根拠があります。強化学習の領域だと「探索と利用のトレードオフ」と言われたりしますが、超絶雑には以下のとおりで、2つの勘違いは対称ではない(悲観的な勘違いのほうが深刻)ということです。

  • 楽観的な勘違い:実際に行動するため、結果をみて悪い選択だと気づける
  • 悲観的な勘違い:その選択をしないため、結果が得られず悪い選択だと気づけない

とはいえ、不確かさがなるべくない状態での判断が一番確実で負荷も少ないため、なるべく不確かさを減らす努力もするべきかもしれません。

記憶のために知識を使う

人間の脳の記憶は、よくコンピュータの記憶媒体に例えられるためか、「ファイルを保存」みたいなイメージを持ちがちです。ところが実際はむしろ、筋力トレーニングに近いらしいです。つまり、単一の知識についてインプットとアウトプットを複数回おこなうことにより、記憶が定着していきます。

人間の脳に海馬という部位があります。この中にあるニューロンという神経細胞で起こる、長期増強という現象が、記憶を司っていると言われています。その内容についてはWikipediaとかをみればいいとおもいます。重要なのは、「記憶は繰り返し使うことで増強される」ということです。とにかくアウトプットです。復習のためのテストを自分で作りそれを解くことをオススメされています。AdaBoost的な発想です。

モチベーション的にも記憶的にも、「必要なことを学ぶ」と、必然的にアウトプットするので、学習の効率がよさそうです。遠い将来を追いかけるよりもまず目先の課題と向き合おうと思いました。ごめんなさい。必要に応じて学んだ知識を体系化してまとめておくことも自分には必要だと思いました。本書でいうところの「復習のための教材をつくる」的なことです。本を読んだり、技術的な何かをやったら、咀嚼・言語化してブログに書きます。自分のために。

手作りで温もりのあるMSBuildプロジェクト

あけましておめでとうございます。

最小限のMSBuildプロジェクトファイルをテキストエディタで書いてみる回です。本当に知りたい方はこれを読むより公式ドキュメントのチュートリアルを読んでもらうのが一番いいです。おつかれさまでした。

MSBuildとは

The Microsoft Build Engine is a platform for building applications. This engine, which is also known as MSBuild, provides an XML schema for a project file that controls how the build platform processes and builds software. Visual Studio uses MSBuild, but it doesn't depend on Visual Studio. By invoking msbuild.exe on your project or solution file, you can orchestrate and build products in environments where Visual Studio isn't installed.

MSBuild - Visual Studio | Microsoft Docs

ということで、アプリケーションをビルドするためのMicrosoft製のエンジンです。.NET Framework 2.0から同梱されていて、それまで使われていたNMakeの.NET Framework版みたいなもんです*1

.NET Framework 1系までは、Visual Studioでのビルドは、IDE組み込みのdevenv.exeがビルドしている一方、コマンドラインからのビルドはcsc.exeやvbc.exeを直接使っていました。コンパイラオプションを指定するのが大変だったり、ビルド以外の処理が複雑でした。これらをまとめてスクリプト化したのがMSBuild及びプロジェクトファイルです。

例えば、Visual Studioで作ったプロジェクトをJenkinsなどのCIツールでもビルドしたいとき、そのままプロジェクトファイル(.csprojとか)を入力にしてMSBuild.exeを実行すれば同様の成果物が生成されます。便利ですね。

Why 手作り

前述のとおり、通常?はVisual Studio様がご創造なされますが、細々とした設定をしたいときや、ビルド環境によって切り替えたいとき、またコンソール実行で設定を渡したいときなど、けっこう触る機会があります。少しでも手を出そうとすると、結局MSBuildが何をするかを把握しておかないといけません。こういう場合、往々にして既存のプロジェクトファイルをいじるところから始めてしまって結局無為な時間を過ごしてしまいがちです。でした。私が。

主にプロパティ、項目、ターゲット、タスクという独特な概念を理解する必要があります。ドキュメントを読んで理解できるならそれで十分ですが、実際に動かすのが一番だと思います。のでやってみた次第です。

Let's 手作り

あえてテキストファイルでプロジェクトファイルを作ってみます。MSBuildでビルドする最小限からはじめて、少し遊んでみます。OSはmac OS Mojave 10.14、MSBuildは15.6.0.0ですが、超基本的なことしかやってないので、各自の環境でも再現できると思います。

最小構成でMSBuild

最小限のC#のコードを書きます。ファイル名を helloworld.cs とします。

using System;

class HelloWorld  
{  
    static void Main()  
    {
        Console.WriteLine("Hello, world!");  
    }
}

最小限のプロジェクトファイルを書きます。ファイル名は Helloworld.proj とします。

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <Compile Include="helloworld.cs" />
  </ItemGroup>
  <Target Name="Build">
    <Csc Sources="@(Compile)"/>
  </Target>
</Project>

XMLで記述します。根本は <Project> 要素です。 <ItemGroup> 要素は、MSBuildへの入力となるファイルを指定します。ここでは helloworld.cs というファイルを、Compileという種類のItem(=項目)にIncludeしています。 <Target> 要素は、MSBuildが順次実行する処理(タスク)をまとめたものです。Targetの名前をBuildとしています。BuildというTargetを実行すると、配下のタスクが実行されます。ここでは、 Cscタスクに対してCompileというItemを渡しており、ItemGroupで指定したhelloworld.csのみが渡されます。Cscタスクはcsc.exeをラップしており、実際のビルド処理はこのタスクが行います。

この状態で、MSBuildを実行します。 -t スイッチでTargetを指定します。

$ msbuild Helloworld.csproj -t:Build
Microsoft (R) Build Engine version 15.6.0.0 (xplat-master/ca830585 Sun Mar 25 19:24:09 EDT 2018) for Mono
Copyright (C) Microsoft Corporation. All rights reserved.

Build started 1/5/2019 9:50:17 PM.
Project "/Dev/msbuild/HelloWorld/Helloworld.csproj" on node 1 (Build target(s)).
Build:
  /Library/Frameworks/Mono.framework/Versions/5.10.1/lib/mono/msbuild/15.0/bin/Roslyn/csc.exe /out:helloworld.exe helloworld.cs
Done Building Project "/Dev/msbuild/HelloWorld/Helloworld.csproj" (Build target(s)).

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:01.01

これでhelloworld.exeが出力され、実行できます。これで最低限のビルドができます。Visual Studioが自動生成するプロジェクトファイルに比べるとかなりシンプルです。

ビルドプロパティの追加

最低限のビルドはできるようになったので、もう少し細かい制御をしてみます。

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>  
   <AssemblyName>MSBuildSample</AssemblyName>  
    <OutputPath>Bin\</OutputPath>  
  </PropertyGroup>  
  <ItemGroup>
    <Compile Include="helloworld.cs" />
  </ItemGroup>
  <Target Name="Build">
    <MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
    <Csc Sources="@(Compile)" OutputAssembly="$(OutputPath)$(AssemblyName).exe" />
  </Target>
</Project>

<PropertyGroup> 要素は各種のプロパティを設定します。ここでは、出力するアセンブリ名と出力先のディレクトリを指定しています。合わせてBuildターゲットにおいて、Cscタスクの前に出力ディレクトリの作成と、Cscタスクにおいて出力アセンブリ名を指定しています。

この状態でMSBuildを実行すると、 Binディレクトリが作成され、その配下にMSBuildSample.exeが作成されます。

ビルドターゲットの追加

Build以外に、CleanとRebuildというターゲットを追加します。また、プロジェクトのデフォルトのターゲットを指定します。

<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <AssemblyName>MSBuildSample</AssemblyName>  
    <OutputPath>Bin\</OutputPath>  
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="helloworld.cs" />  
  </ItemGroup>
  <Target Name="Build">
    <MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
    <Csc Sources="@(Compile)" OutputAssembly="$(OutputPath)$(AssemblyName).exe" />
  </Target>
  <Target Name="Clean" >  
    <Delete Files="$(OutputPath)$(AssemblyName).exe" />  
  </Target>
  <Target Name="Rebuild" DependsOnTargets="Clean;Build" />  
</Project>

Cleanターゲットでは、Deleteタスクを用い、Buildターゲットで生成したアセンブリを削除します。RebuildターゲットはCleanとBuildのターゲットをこの順序で実行します。

この状態でCleanターゲットを実行してみます。

$ msbuild Helloworld.csproj -t:Clean
Microsoft (R) Build Engine version 15.6.0.0 (xplat-master/ca830585 Sun Mar 25 19:24:09 EDT 2018) for Mono
Copyright (C) Microsoft Corporation. All rights reserved.

Build started 1/5/2019 10:21:19 PM.
Project "/Dev/msbuild/HelloWorld/Helloworld.csproj" on node 1 (Clean target(s)).
Clean:
  Deleting file "Bin/MSBuildSample.exe".
Done Building Project "/Dev/msbuild/HelloWorld/Helloworld.csproj" (Clean target(s)).

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:00.42

Bin下のMSBuildSample.exeが削除されています。また、MSBuildを-tスイッチなしで実行すると再度MSBuildSample.exeが作成されます。

インクリメンタルビルド

ターゲットが依存しているファイルのタイムスタンプを見て、変更があった場合にのみビルドします。BuildターゲットにInputsとOutputsを指定します。

  <Target Name="Build" Inputs="@(Compile)" Outputs="$(OutputPath)$(AssemblyName).exe">>
    <MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
    <Csc Sources="@(Compile)" OutputAssembly="$(OutputPath)$(AssemblyName).exe" />
  </Target>

この状態で msbuild -v:d を実行します。ビルドの詳細を出力するスイッチをしています。

Skipping target "Build" because all output files are up-to-date with respect to the input files.
Input files: helloworld.cs
Output files: Bin/MSBuildSample.exe

入力ファイルhelloworld.csに変更がないので、Buildターゲットがスキップされています。

ビルドしない

ここまでくるとわかるかと思いますが、上記のプロジェクトにおいて、実際にビルドを実行しているのは結局Cscタスクであり、その前後は付随する処理でしかありません。つまり、MSBuildが行うのはビルドだけでなくその前後を含めたプロセスといってよさそうです。ビルドしないMSBuildプロジェクトもありえます。

例として、まったく意味のないプロジェクトを作ってみます。

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="Greeting">
    <Message Text="Hello" Importance="low" />
    <Message Text="World" />
    <Message Text="!!!!!" Importance="high" />
  </Target>
  <Target Name="Copy">
    <Touch Files="File1.cs;File2.cs" AlwaysCreate="true" />
    <Copy SourceFiles="File1.cs" DestinationFolder="Bin\" />
  </Target>
</Project>

GreetingターゲットとCopyターゲットの2つがあります。まずGreetingターゲットは、Messageタスクを使って、文字列を出力するだけです。実行してみます。

$ msbuild NoBuild.csproj -t:Greeting -v:d
(略)
Target "Greeting" in project "/Dev/msbuild/HelloWorld/NoBuild.csproj" (entry point):
Using "Message" task from assembly "Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
Task "Message"
  Hello
Done executing task "Message".
Task "Message"
  World
Done executing task "Message".
Task "Message"
  !!!!!
Done executing task "Message".
Done building target "Greeting" in project "NoBuild.csproj".
Done Building Project "/Dev/msbuild/HelloWorld/NoBuild.csproj" (Greeting target(s)).
(略)

「Hello」「World」「!!!!!」が出力されています。HelloはImportanceがlowなので、 -v:dオプションがないと出力されません。

次に、Copyターゲットを実行してみます。Touchタスクを使ってFile1.csとFile2.csを作成した上で、CopyタスクでFile1.csをBin配下にコピーします。

$ msbuild NoBuild.csproj -t:Copy
(略)
Project "/Dev/msbuild/HelloWorld/NoBuild.csproj" on node 1 (Copy target(s)).
Copy:
  Touching "File1.cs".
  Touching "File2.cs".
  Copying file from "File1.cs" to "Bin/File1.cs".
Done Building Project "/Dev/msbuild/HelloWorld/NoBuild.csproj" (COpy target(s)).
(略)

まとめ

MSBuildプロジェクトファイルを0から手で書いてみました。ほとんど公式チュートリアルの丸パクリです。本年度のブログのハードルを下げていく方針なので大成功です。

Visual Studioを使って普通に開発を進めていくと気づいたらプロジェクトファイルが巨大になっていて面くらいがちです。でもやっていることは単純なことがわかります。基本的に手で書くものではないですが、上記内容くらいは知っておいて損はない気がします。他にもいろいろなタスクがあったり、条件指定ができたり、細かい制御ができます。そのあたりはぜひリファレンスを参照ください。

*1:MSBuildの開発コード名は「XMake」でした

2018年の振り返り

帰省中です。雪だし寒いしもうダメです。

2018年の振り返り

技術ブログ

このブログだと、2018年は技術関係のエントリが5本だけでした。残念の極みです。2019年は5000本くらい書きたいです。また、勤務先の公式ブログに記事を書きました*1。はてブのホットエントリになった、ということを聞きました。そこそこ褒めてもらえたので、たぶんいいことなのだと思います。

勉強会

技術勉強会は発表側にならないと何も学ばず時間を浪費してしまう自覚があるので、だいたい発表前提で参加します。2018年は勉強会自体にほとんど参加しませんでした。ただ、こちらも初めて勤務先オフィシャルな場で社員として登壇しました*2。中身は課題だらけ過ぎて忘れたいです。

技術書典

ノリで応募して勉強しながら書きすすめましたが、結果的に楽しく充実した体験ができました。BOOTHにも出品しており、現時点で126冊のご購入をいただいております。ありがとうございます。次回は春頃に開催されるはずです。一般参加よりもサークル参加を強くおすすめします*3

kyabatalian.hatenablog.com

扁桃切除手術

扁桃切除の手術をうけるため、10日間の入院をしました。以降、扁桃炎にはなっていないのですが、手術による改善はまあそれほど体感できていません。食べ物が飲み込みやすくなったような気がします。入院中にキングダムを読破し、中華統一への思いが高まりました。

kyabatalian.hatenablog.com

旅行

宿泊を伴う観光でいうと、京都と石川に行きました。京都へはGWと夏(祇園祭の直前頃)の2回行きました。常に最高です。石川は和倉温泉に行きました。技術書典の前日かつ台風が来ていて怖かったです。こちらも最高でした。2019年は海外に行きたいです。

kyabatalian.hatenablog.com

お笑い

趣味です。2018年は主にド地下のライブに足繁く通っていたのですが、2019年はわりとメジャーどころも観に行くようになりました。推しの単独ライブ、最高でした*4。直接みにいったライブに限らず、キングオブコントやM-1グランプリも楽しみました。2018年の私的ベストは、5月にルミネでみた、かまいたちの漫才でした。異次元の面白さでした。

kyabatalian.hatenablog.com

人生

このブログを書くにあたり、1年間のツイートをさかのぼってみたところ、前半はかなりつらそうでした。お仕事がしんとかったのか、何かあったのだと思うのですが、まったく思い出せないので、人生そんなものなのかもしれません。後半は今後の人生について考える機運が高まり、それはそれでハードでした。2019年は行動にうつしていきたいです。

2019年の抱負

技術をちゃんとやります。自分がもし第三者だったらいまの自分をみて「いいから黙ってコード書けよ」と言いたくなりそうなので黙ってコード書きます。

そのためには健康第一です。来年もよろしくおねがいします。良いお年を。

拡張統計情報とテーブル結合

PostgreSQL Advent Calendar 2018、23日目の記事です。

今更感満載ですが、PostgreSQL 10で導入された拡張統計情報について、直感と異なる挙動だったので調査してみました。小ネタですみません。

TL;DR

  • PostgresSQLのオプティマイザは、カラム間の関数従属性を考慮しない
  • 拡張統計情報を使えば、テーブル探索で関数従属性を考慮できる
  • テーブル結合では考慮されない

行数推定の課題

PostgreSQLのオプティマイザはコストベースであり、アクセスパスツリーを生成して最もコストの低いパスを選択します。

このコスト計算において、条件句が複雑な場合に、実体と沿わないコストを算出してしまう場合があります。具体的には、条件句に AND で複数の条件が指定され、かつ両条件に指定されたカラム間に関数従属性がある場合、実際の行数よりも推定行数が少なく見積もられます。

この時点で???となった方は、ググるか、恐縮ながら以下をさらっとみてもらえるといいかもしれません。

PostgreSQL:行数推定を読み解く/row-estimation - Speaker Deck

拡張統計情報

任意のテーブルがもつカラム間の相関を取得し、オプティマイザに使わせる事ができる機能です。PostgreSQLのバージョン10で導入されました。

以下ではまず、この機能がどういうものか確認してみます。公式ドキュメントの例を使います。

-- テスト用のテーブルt1を作成する
CREATE TABLE t1 (a int, b int);

-- t1にデータを挿入する
INSERT INTO t1
  SELECT i/100, i/500
  FROM generate_series(1,1000000) s(i);

こうすることにより、t1のデータは以下のようになります。

i a b
1 0 0
2 0 0
3 0 0
99 0 0
100 1 0
101 1 0
102 1 0
199 1 0
200 2 0
201 2 0
499 4 0
500 5 1
501 5 1
999 9 1
1000 10 2
1001 10 2

つまり、というかSQLのとおりなんですが、aは100ごとに、bは500ごとに値がインクリメントされます。例えばaが12とわかればbは2と確定しますし、逆にbが2とわかればaは10以上20未満であるとわかります。このとき、カラムaとカラムbは相互に関数従属であるといいます。

この状態で、カラムa、bの両方に対する条件句をもつSQLの実行計画をみてみます。

-- 統計情報を収集する
ANALYZE t1;

-- 実行計画を取得する
EXPLAIN ANALYZE SELECT * FROM t1 WHERE (a = 1) AND (b = 0);
Seq Scan on t1  (cost=0.00..19425.00 rows=1 width=8) (actual time=0.034..113.435 rows=100 loops=1)
  Filter: ((a = 1) AND (b = 0))
  Rows Removed by Filter: 999900
Planning time: 0.155 ms
Execution time: 113.467 ms

t1に対するSeq Scanの実際の結果は100行にもかかわらず、推定行数が1行になっています。これは、カラムaとbの間の関数従属性が考慮されていないためです。つまり、各条件句を独立とみなし、それぞれ別のSQLで取得した結果の和集合としています。

この問題に対する解決策として、拡張統計情報があります。ためしてみます。

-- 拡張統計情報を作成する
CREATE STATISTICS s1 (dependencies) ON a, b FROM t1;

-- 統計情報を収集する
ANALYZE t1;

-- 実行計画を取得する
EXPLAIN ANALYZE SELECT * FROM t1 WHERE (a = 1) AND (b = 0);
Seq Scan on t1  (cost=0.00..19425.00 rows=98 width=8) (actual time=0.020..89.729 rows=100 loops=1)
  Filter: ((a = 1) AND (b = 0))
  Rows Removed by Filter: 999900
Planning time: 0.083 ms
Execution time: 89.750 ms

今度はカラム間の関数従属性が考慮され、行数は正確に見積もられています。

テーブル結合

本題、テーブル結合です。t1と全く同様の方法で、t2とt3という2つのテーブルを作り、これらをクロス結合して実行計画をみてみます。

CREATE TABLE t2 (a2 int, b2 int);
CREATE TABLE t3 (a3 int, b3 int);

INSERT INTO t2 SELECT i/100, i/500 FROM generate_series(1,1000000) s(i);
INSERT INTO t3 SELECT i/100, i/500 FROM generate_series(1,1000000) s(i);

単一条件の場合

まず、t2に対する条件句が単一の場合の実行計画を見てみます。

EXPLAIN ANALYZE SELECT * FROM t2, t3 WHERE (t2.a2 = 1) AND (t2.b2 = t3.b3);
Hash Join  (cost=30832.00..52282.01 rows=48922 width=16) (actual time=614.425..622.979 rows=49900 loops=1)
  Hash Cond: (t2.b2 = t3.b3)
  ->  Seq Scan on t2  (cost=0.00..16925.00 rows=98 width=8) (actual time=0.030..85.213 rows=100 loops=1)
        Filter: (a2 = 1)
        Rows Removed by Filter: 999900
  ->  Hash  (cost=14425.00..14425.00 rows=1000000 width=8) (actual time=475.964..475.965 rows=1000000 loops=1)
        Buckets: 131072  Batches: 16  Memory Usage: 3334kB
        ->  Seq Scan on t3  (cost=0.00..14425.00 rows=1000000 width=8) (actual time=0.024..146.287 rows=1000000 loops=1)
Planning time: 0.331 ms
Execution time: 626.888 ms

t2に対するSeq Scanの推定行数が98となっており、実際には100なので、乖離はありません。結合条件としてはHash Joinが選択されています。

複合条件の場合(拡張統計情報なし)

テーブルt2のカラムa2、b2それぞれに対して条件句を指定します。関数従属性により、実行計画が狂ってしまうのを確認します。

EXPLAIN ANALYZE SELECT * FROM t2, t3 WHERE (t2.a2 = 1) AND (t2.b2 = 0) AND (t2.b2 = t3.b3);
Nested Loop  (cost=0.00..36354.86 rows=486 width=16) (actual time=0.056..8576.923 rows=49900 loops=1)
  ->  Seq Scan on t2  (cost=0.00..19425.00 rows=1 width=8) (actual time=0.032..79.398 rows=100 loops=1)
        Filter: ((a2 = 1) AND (b2 = 0))
        Rows Removed by Filter: 999900
  ->  Seq Scan on t3  (cost=0.00..16925.00 rows=486 width=8) (actual time=0.007..84.883 rows=499 loops=100)
        Filter: (b3 = 0)
        Rows Removed by Filter: 999501
Planning time: 0.113 ms
Execution time: 8580.450 ms

前項のt1と同じく、t2は推定行数が大きくずれ、Seq Scanとなっています。結合に関しては、t2が1行となる予測なので、Nested Loopが選択されています。

複合条件の場合(拡張統計情報あり)

テーブルt2のカラムa2、b2に対して、拡張統計情報を作成します。

CREATE STATISTICS s2 (dependencies) ON a2, b2 FROM t2;
ANALYZE t2;
EXPLAIN ANALYZE SELECT * FROM t2, t3 WHERE (t2.a2 = 1) AND (t2.b2 = 0) AND (t2.b2 = t3.b3);
Nested Loop  (cost=0.00..36945.60 rows=47628 width=16) (actual time=0.045..231.646 rows=49900 loops=1)
  ->  Seq Scan on t3  (cost=0.00..16925.00 rows=486 width=8) (actual time=0.019..119.208 rows=499 loops=1)
        Filter: (b3 = 0)
        Rows Removed by Filter: 999501
  ->  Materialize  (cost=0.00..19425.49 rows=98 width=8) (actual time=0.000..0.205 rows=100 loops=499)
        ->  Seq Scan on t2  (cost=0.00..19425.00 rows=98 width=8) (actual time=0.022..97.727 rows=100 loops=1)
              Filter: ((a2 = 1) AND (b2 = 0))
              Rows Removed by Filter: 999900
Planning time: 0.194 ms
Execution time: 235.587 ms

実行時間が8580msから246msに改善しました。実行計画の中身をみてみると、t2の推定行数が98になっており、実行結果の100行と僅差になっています。それにより、Materializeされるようになりました。Materializeは、t2をSeq Scanした結果をメモリにのせます。これにより、t3とのNested Loopにおいてt2の再スキャンを高速化します。

考察

拡張統計情報によって見事に実行速度が改善したわけですが、Nested Loopではなく単一条件の場合と同じくHash Joinが選択されてもよさそうです。なぜNested Loopのままなのか、PostgreSQLのコードを見てみます。

拡張統計情報を参照するのは clauselist_selectivity() の以下の箇所です。該当コードはここ

 /*
    * Determine if these clauses reference a single relation.  If so, and if
    * it has extended statistics, try to apply those.
    */
    rel = find_single_rel_for_clauses(root, clauses);
    if (rel && rel->rtekind == RTE_RELATION && rel->statlist != NIL)
    {
        /*
        * Perform selectivity estimations on any clauses found applicable by
        * dependencies_clauselist_selectivity.  'estimatedclauses' will be
        * filled with the 0-based list positions of clauses used that way, so
        * that we can ignore them below.
        */
        s1 *= dependencies_clauselist_selectivity(root, clauses, varRelid,
                                                  jointype, sjinfo, rel,
                                                  &estimatedclauses);

        /*
        * This would be the place to apply any other types of extended
        * statistics selectivity estimations for remaining clauses.
        */
    }

    /*
    * Apply normal selectivity estimates for remaining clauses. We'll be
    * careful to skip any clauses which were already estimated above.

スキャン方法の選択時、 src/backend/optimizer/path/costsize.c がこのコードを使っています。

void
set_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel)
{
    double     nrows;

    /* Should only be applied to base relations */
    Assert(rel->relid > 0);

    nrows = rel->tuples *
        clauselist_selectivity(root,
                               rel->baserestrictinfo,
                               0,
                               JOIN_INNER,
                               NULL);

一方、結合条件について。PostgreSQLのオプティマイザはデフォルトでNested Loopのパスを作り、そのあとにMerge Join、Hash Joinとそれぞれを検討して、そっちのほうが速そうならそっちに置き換えます。 このへんでHash Joinを選択するか判断しているのですが、この段階で上記コードを参照していません。つまり、結合条件の選択に、拡張統計情報はつかわれません。

改めて問題

何が困るかというと、まあそのままなのですが、「巨大なテーブル同士をNested Loopでジョインしてしまう」ことです。これについては、バージョン10以前と同様にpg_hint_planをつかって結合ルールを手動制御せざるを得なさそうです。

そんな人はいないと思いますが、「関数従属な複数カラムに対する条件句」と「テーブル結合」の両方を持つSQLがたくさんあり、pg_hint_planでがんじがらめになっているサービスを運用しており、拡張統計情報をメシア視している方がもし仮に存在するなら、要注意です。

まとめ

普通に結合条件でも考慮されてほしいと思いました。拡張統計情報という壮大な名前なので、今後さらに賢くなっていくことが期待できます。と思っていたらバージョン11では特にアップデートがなかったです。悲しいです。以降、「文句言ってないでOSSなんだから自分でなおせよ」禁止。

参考

ポストモーテムにおける根本原因分析

SRE Advent Calendar 2018、僭越ながら3日目を担当させていただきます、@kabaomeです。

勢いでカレンダーに登録しましたが、私は明示的にSREというポジションではなく、一アプリケーションエンジニアです。 半年くらい前にSRE本*1を読み、「こういうのやっていくゾ☆」と思い立って、社内で勝手にいろいろやっています。いまのところ怒られていません。

ポストモーテム、その中でもっとも重要であり難しい(と思っている)根本原因分析について、個人的見解を書いてみます。

TL;DR

  • ポストモーテムやその根幹である根本原因分析の情報あんまない
  • 根本原因分析の結論は「客観的」「単純」「制御可能」であるべき
  • 根本原因分析はスキルだと思います、がんばっていきましょう

ポストモーテム

まずはポストモーテムについて簡単にご紹介します。 SREの皆様におかれましては常識だと思いますので、読み飛ばしてください。

ポストモーテムとは、もともとは医学の世界で、「検死」を意味する英単語です。 検死とは、「死体について医学的に検査し、死因や死亡の状況について判断を下す」仕事を指します*2。翻って、プロジェクトマネジメントの世界においても、プロジェクトの結果、主に失敗ケースに対して振り返りを実施し、その原因や再発防止策を策定する営みとして広く知られています。

私を含むソフトウェアエンジニアに広くこの言葉が知られたのは、やはりGoogleのSREによる取り組みであり、それを流布したSRE本にあると思います。以後、ポストモーテムを(本来の用語ではなく狭義の)言葉として用います。

ポストモーテムとよく似た文脈で、いわゆる「インシデント報告書」がよく出てきます。 こちらも一般的な定義はありませんが、「サービスに障害が発生し、顧客が不利益を被った場合に、顧客に対する説明として提出する文書」を指します。インシデント報告書とポストモーテムはその目的・存在意義から明確に異なっています。

目的 想定読者
インシデント報告書 インシデントの報告・情報共有・謝罪 顧客、上司などのレポートライン
ポストモーテム インシデントからの学び・サービス改善 開発者

ポストモーテムにおいて、インシデントは学びの対象であり、サービスの信頼性を向上させるための材料です。その視点で振り返り、アクションプランをたて、またチーム内に共有します。

ポストモーテムそのものについては、SRE本及びWorkbookを読んでいただければ良いのですが、いかんせんGoogle以外での実例を聞いたことがほとんどありません。SRE的改善!自動化!みたいなのはカッコよくて対外的にもオープンにしやすい一方で、ポストモーテムはインシデントに関する情報でセンシティブなため、あまり外に出さない力学が働くのではないかと邪推しています。

そこで今回は、SRE本のポストモーテム章をさらに焦点を絞って、「根本原因分析」について、これまでやってきた経験をもとに書いてみます。

根本原因分析

Root cause analysis (RCA) is a method of problem solving used for identifying the root causes of faults or problems.

この定義からわかるように、根本原因分析はあくまで「問題の解決」を目的としており、そのために原因を特定する手法です。

ドラッカーが再三説いたように、ソフトウェアの運用にとどまらずビジネスなど人間の知的活動のあらゆる局面において、物事を進める上で最も重要視されるべきものの1つが「正しい問いをすること」です。人間は「いかに正解を出すか」に陥りがちですが、真に重要なのはその前段、解こうとしている問題が正しいかに頭をつかうべきです。間違った問いに対する正しい答えが最悪、というのはよく言われる話です。

インシデントの分析においても同様で、間違った原因に対する正しいアクションは最悪です。余計な運用手順を増やしたり、象徴的にはドキュメントが無限に膨れ上がっていきます。そこでRCAを行うわけですが、これが激ムズです。

根本原因に到達するための問い

よりよい学び、ひいてはサービスの信頼性向上のために、根本原因分析の結論に対する心構えを、問いの形で3つ挙げてみます。それは、「客観的か?」「単純か?」「制御可能か?」です。

以下では例としてデプロイ作業時のインシデントに関するRCAの結論に対し、何が良くないのか、どうすればいいのかを考えてみます。

  • 作業:あるWebサービスに新機能を追加してデプロイする
  • 事故:デプロイ手順にある、ロードバランサを切り離す作業を実施しなかった
  • 結果:デプロイ中にアクセスした複数のユーザがエラー画面を経験した

客観的か?

事実を客観的に書くように努めるべきです。 人間が絡むあらゆる営みで、主観を完全に排除することは不可能です。先のインシデント例において、客観性を欠いた原因として、例えば以下です。

  • 原因:デプロイ手順をしっかり確認していなかった

これは、デプロイ作業を行った担当者が自らRCAとポストモーテムの記述を行ったときに出てきやすい結論です。デプロイ手順を確認していなかったかどうかは程度問題であり担当者の主観によります。「しっかり」とか「確実に」とか、程度を示す言葉が書かれていれば要注意です。このような原因をおいてしまうと、「気をつける」みたいな主観ありあり、実効性のないアクションに結びつきがちです。

往々にしてポストモーテムを書くのはそのインシデントに詳しい人間が担当者になりやすく、インシデントとの距離が近ければ近いほど、主観をもって分析をしてしまいます。客観的な分析のための方法として、客観性を担保する方法として確実に有効なのは、第三者の利用です。例えば以下のようなものが考えられます。

  • インシデントとは直接関係のない人間が関係者にヒアリングして記述する
  • 主観的なワード(「しっかり」「確実に」など)を排除する

重要なのは、前者では「客観視できる人間の視点を入れる」、後者では「機械的に修正点を見つける」としているように、「インシデントを書く人間に客観性を求める」という精神論ではなく、分析自体も客観性のあるアプローチを取ることです。人間を変えるよりも環境を変えるほうがやりやすいということを前提とします。

単純か?

なるべく単純に、平易に、わかりやすく書くように努めるべきです。 先の例でいうと、以下のように複数の原因が複雑に記述されると、単純さを欠いています。

  • 原因1:デプロイ手順が複雑すぎた
  • 原因2:デプロイ手順を確認することの重要性が認識されていなかった
    • 新入社員研修のカリキュラムに含まれていなかった
    • 先輩社員によるデプロイ手順のレビューがなされていなかった
  • 原因3:スケジュールの都合上、余裕がなかった

複雑な原因が良くない理由として、3つ挙げて紹介します。

問題を正確に捉えきれていない

起きた事象が明確ならば、その原因も明確に存在するはずです。もちろん複数の要因が絡み合ってひとつのインシデントを形成する場合もありますが、ほとんどの場合は表面的な分析にとどまっており、深掘りが足りないケースが多そうです。起きた事象の記述自体が複雑ならば、まず事実確認まで立ち返ったほうがいいかもしれません。

アクションの実効性を落とす

複雑な原因から導き出されるアクションは、同様に煩雑になってしまいがちです。例えば上記例だと、原因が3つ挙げられているため、それぞれを解決するアクションを検討します。本来は「デプロイ手順を自動化する」というアクションが最も解決に近かったのに、「ドキュメントを整備する」「新人研修カリキュラムをみなおす」など、本質的でないアクションに陥り、運用コストや整備の工数が無駄に発生することになりかねません。

読みづらい

他のドキュメントと同じくポストモーテムもまた人間が読むことを想定した文書です。読みづらい文書を読みたい人間はいません。

原因をシンプルに落とし込むための手法としては、まずは愚直に「3行以内にまとめる」「1つだけにする」といった方法が考えられますが、実際に起きた問題が本当に複雑な原因を持っているケースも多々あり、本質的ではありません。

そこで掘り下げる方向での分析が必要ですが、手法は様々あります。有名どころでひとつ紹介しておくと、「なぜなぜ分析」があります。これは、トヨタ生産方式を構成する手段の一つです。方法はとてもシンプルで、「なぜ」を問い、それに答え、そうなった理由についてまた「なぜ」を問う、ということを繰り返していきます。

制御可能か?

コードやマネジメントのレベルでうまく制御できていれば解決していたのか、という視点です。 例えば前述のインシデント例で、制御不可能な原因は以下です。

  • 原因:ロードバランサが自動で切り離れなかった

振り返りの常ですが、結果論に陥りがちです。そうなっていれば起きなかったこと、を挙げること自体は間違いではないのですが、起こったあとだから言えることは当時知り得ないことなので、原因とは言えません。この例でいうと、自動化されていなかったことが原因として起きる問題というのは本来ありえないはずで、人の手で行ったことにより生じた不具合が原因としてあり、それに対するアクションとして「自動化」が挙がるのが正しい姿です。

制御可能性に関するもうひとつの大きな視点として、「人間はそのとき最善の判断をした」ことを前提とすることも重要です。SRE本にはそのように書いてあるのですが、「Google様は神エンジニア集団だからその前提でいけるけどウチは無理」と解釈しがちです。これは不遇な誤解だと思っていて、人間に原因を求めないというのは組織やサービス改善の重要な指針になりますし、そもそも人間は改善できないです。人間がミスったと思うなら、なぜその人間はミスったのか、深掘りすべきです。

まとめ

根本原因分析は、多くの場合に正解がなく、主観や複雑さとの闘いでもあり、非常に難しいです。

あらゆるロールの人間がインシデントに向き合い、その貴重な機会から学びや改善を得るために、根本原因分析をがんばっていくといいと思います。

偉そうに書きながら私もまったくできていません。しかし、思考の訓練をしながらそのスキルを上げていくことができると思っています。

他の組織での話もぜひ聞いてみたいので、ご興味のある方はコメント欄か@kabaomeまでご連絡ください。やっていきましょう。

参考

*1:https://landing.google.com/sre/books/

*2:法律上の定義はないため、参考文献より引用

扁桃切除手術の記録

扁桃切除手術*1を受けました。
退院後、周囲の人がいろいろと聞いてくれたり、扁桃切除を検討しているので教えてほしい、と聞かれることが幾度かあったため、ここに記しておこうと思いました。

基礎知識

どういう手術なのかを簡単に紹介します。

扁桃とは

まず扁桃という器官は、喉にあるリンパ器官です。 リンパ器官というだけあって、鼻や口から入ってきた細菌やウイルスをとらえる免疫の役割を持っています。

免疫機能が未熟な子どもは、扁桃の機能に頼る割合が高く、ウイルスや細菌に感染しやすいのですが、成長するにつれて扁桃以外の免疫機能が発達してくるために、感染しにくくなってきます。それにつれて、扁桃自体も徐々に縮小します。 ただし、幼少期に扁桃炎を頻発するなどで、大人になっても比較的大きなサイズを保つ人もいます。

ちなみに「扁桃」というのはアーモンドの種子の別称です。 アーモンドの種子に形がにているという理由でこの名前がつけられたらしいです。 かつては「扁桃腺」と呼ばれていましたが、「腺」というのは身体の中で分泌活動を行う器官の名称であり、以後の研究によって腺の役割ではない説が有力となったため、現在では「扁桃」と呼ばれています。

扁桃には「咽頭扁桃(アデノイド)」「耳管扁桃」「口蓋扁桃」「舌扁桃」の4つがありますが、一般に扁桃と呼ぶ場合、口蓋扁桃を指します。

口蓋扁桃摘出術とは

口蓋扁桃を切除する手術です。 以下を基準として、手術適応か否かを判断されます。

  • 習慣性扁桃炎
    • 年に3,4回以上の頻度で急性扁桃炎になるひと
  • 扁桃周囲膿瘍
    • 急性扁桃炎になりすぎて炎症が周囲に波及し、膿瘍になったひと
  • 扁桃肥大
    • 睡眠時無呼吸症候群で扁桃が大きい*2ひと
  • 病巣扁桃
    • 扁桃が他の疾患*3の原因になっているひと

自分の場合は習慣性扁桃炎と診断されました。 扁桃肥大ぎみでもあったのですが、頻繁に喉を起点とした風邪をひくためです。明確な検査があるわけではなく基本的に自己申告です。

経緯と注意点

かかりつけ医の診察から退院までの経緯をメモります。

診断〜入院

入院に至るまでに複数のステップがあります。

  • かかりつけ医で診断&紹介状取得
  • 大規模病院で診察&手術の予約
  • 術前検査(手術1週間前)
  • 検査結果説明&入院手続き

手術を受ける病院の選定

多くの場合、かかりつけ医からの紹介状をもって大規模病院で手術をうけることになります。 いきなり大規模病院*4に行くと、選定療養費として初診時5,000円がかかります。

どこの病院にするかはかかりつけ医と相談ですが、口蓋扁桃摘出術を都内で受けるなら、神尾記念病院JR東京総合病院あたりが有名なようです。ただ、口蓋扁桃摘出術は耳鼻咽喉科の手術としては基礎中の基礎らしく、どこで受けても特にリスク等違いはありません。手術前後で数回の通院を考慮して、自宅から近い病院にするのをオススメします。

自分の場合も自宅から近い東邦大学医療センター大橋病院にお世話になりました。2018年6月に新病棟が設立されたばかりで、とても綺麗でした。10日間ほど寝食を過ごすため、自分にとっては綺麗な病院というのはとても重要な要素でした。

入院までの心構え

重要なのは、入院直前に風邪を引かないこと、虫歯にならないことです。 ここまでの検査や10日間のお休み調整がすべて無に帰します。 虫歯がダメなのは、手術のときに口の中にいろいろと突っ込むので、悪い歯があるとさらに悪くなったり欠けたりするから、と説明されました。

費用と高額療養費制度の活用

口蓋扁桃摘出術はどの病院でも10日前後の入院を要するため、高額療養費制度が使えます。 これは、医療費が一定額*5を超えた場合に、その額を上限としてオーバー分を国が負担してくれる制度です。ここで注意されたいのは、以下の点です。

医療機関や薬局の窓口で支払う医療費が1か月(歴月:1日から末日まで)で上限額を超えた場合、その超えた額を支給する

例えば、10月25日に入院して11月4日に退院した場合、「10月25日〜31日」と「11月1日〜4日」はそれぞれ独立した医療費とみなされ、高額療養費制度の上限はそれぞれに適用されます。したがって費用を最小に抑えるためには、月をまたがない入院期間を設定する必要があります。自分の場合は1日オーバーして、トータルで10万円強の負担となりました。

また、入院は病室によってグレードがあり、例えば個室を希望すると高くなります。差額ベッド代は高額療養費に含まれません。病院都合で差額のあるベッドになる場合もあるので、初期段階で合意しておいたほうがいいです。高額療養費制度や差額ベッド代など、費用面について、病院側は信じられないくらい無頓着で、数万円は誤差くらいの勢いでこられます。一般庶民の我々が積極的に情報を収集し、交渉していく必要があります。

入院〜手術

お昼ごろに入院し、その日は手続きや検査などを済ませるのみでした。 手術はオンコールといって、呼ばれたら手術室に行くというスタイルのため、ソワソワしながら待っていました。結局14時ごろに呼ばれ、看護師さんに案内されて歩いて手術室に行きました。道中の風景はまさにドラマや映画でみるやつで、楽しかったです。

手術自体は全身麻酔なので、気づいたら寝ていて起きたらすべてが終わって自分のベッド、というかんじです。 点滴をつけたまま、喉の痛み止めを飲んで就寝です。

術後〜退院

手術翌朝、起きたらベッドとパジャマが血で染まっていました。ありがとうございました。

喉が痛すぎかつ発熱もあり、世界を恨みました。看護師さんはクールです。

入院後半は出血もなく食事も平常にとれる状態になったため、退院を希望したのですが、「術後一週間時点に出血リスクがある」とのことで、入院を継続しました。このあたりはおそらく以下の論文が論拠になっていると思われます。統計的に有意なのかという疑問はありますが、専門外すぎてわかりません。

ヒマはヒマなのですが、喉の痛みと微熱はあり、活字をよむのはなかなかしんどかったので、主にマンガを読んだりラジオを聴いて寝落ちしたりしていました。キングダムとバクマンを全巻読みました。最高におもしろかったです。ファルファル。

退院日は片づけと手続きを済ませ、午前中には自宅に帰っていました。翌日からは喉をいたわりつつも普通に過ごしました。

その後

退院から一週間後に経過観察のための受診をし、特に問題ないとのことで、運動やお酒も解禁されました。 退院直後は喉の違和感が残っていましたが、退院から10日が経ち、もうほぼ通常どおりに生活しています。

まとめ

体調が改善したかはまだわかりません。がんばっていきたいです。 なお、最近は扁桃切除せずに凝固術や抗生剤で治療するのが主流らしいです。 それらを試して改善が見られなければ、切除を検討されるといいかもしれません。

参考

*1:正確には「口蓋扁桃摘出術」

*2:マッケンジーの分類によりⅡ°以上

*3:掌蹠膿疱症・IgA腎症・胸肋鎖骨過形成症

*4:大学病院などの「特定機能病院」と、病床数500以上の「地域医療支援病院」

*5:収入に応じて決まる