好きなこととMySQLの実行計画の話
この記事は 🎅GMOペパボエンジニア Advent Calendar 2022 の 13 日目の記事です。
はじめに
この1年くらい、新しく入ったチームメンバにOJTで MySQL の実行計画の話をすることが多いなぁというのを思い出し、数人に説明してたらある程度まとめてみようという気持ちになり今回は実行計画を元に改善した話を書きます。
なぜ「新しく入ったチームメンバに MySQL の実行計画の話をすることが多い」かというと、Webアプリケーションを作る上で例えば Rails なら MySQL, PostgreSQL, SQLite あたりを RDBMS として採用することが多いと思われますが、パフォーマンス向上のためにアプリケーションで実行している SQL のチューニングは不可欠でチューニングには各 RDB の実行計画を読み解くことが必要になってくるけれど、それを知る機会って(いざ自分がやる立場にならないと)無いよなぁと思い、明日から使える tips の一つになればいいな〜ということで話すネタにしています。
実際に自分自身 minne という大規模なユーザー数・出品数を抱えるサービスに入ったことでそのように考えるようになったし、現在処理上のバグ・オペミスで発生した不整合なデータを復旧させるだとかそういった対応も多いのでよりパフォーマンスを意識して対応するようになりました。
では以下 MySQL での改善事例をベースに話を進めます。※ MySQL 5.6 で確認しています。
実行計画について
MySQL は実行したい SQL を解釈し最適にテーブル操作する手順を作り出すオプティマイザという機能を搭載しています。オプティマイザがどう解釈するかによって同じ SQL でも実行時間は変わることがあります(オプティマイザの中身は統計情報やその時のテーブルの状態によって変化している)。
このオプティマイザがどのようにクエリを実行していくか、を示したものが実行計画です。EXPLAIN
をSELECT
, DELETE
, INSERT
, REPLACE
, UPDATE
ステートメントの前に追加して実行することで、ユーザーも実行計画を参照することができます。
MySQL :: MySQL 5.6 リファレンスマニュアル :: B.5.6 オプティマイザ関連の問題
オプティマイザは上であげた MySQL, PostgreSQL, SQLite いずれにも搭載されている機能で、実行計画参照は MySQL なら EXPLAIN
、PostgreSQL も EXPLAIN
で, SQLite なら EXPLAIN QUERY PLAN
をつける模様。
また MySQL ではオプティマイザトレースというものが 5.6 から導入されていて、オプティマイザが計算しているコストやインデックス使用の判断理由などを EXPLAIN 句より詳細に出力することができます。
(それは別で事例作って書いてみたい...統計情報も...)
実行計画を読んでみる
運営メンバーが使用する管理画面で、ある注文情報を集計するページが重すぎて場合によってはタイムアウトする、という問題が発生。 以下のようなクエリを実行していました。
その①
SELECT `tbl2`.`col1` FROM `tbl2` WHERE `tbl2`.`state` = 'deposited' AND `tbl2`.`created_at` BETWEEN '2022-08-01 00:00:00' AND '2022-08-31 23:59:59'
その②
SELECT DATE(tbl2.created_at) AS date, COUNT(tbl1.id) AS count FROM `tbl1` INNER JOIN `tbl2` ON `tbl2`.`col1` = `tbl1`.`id` WHERE tbl1.type = X AND (tbl1.id IN (XXXXXX)) AND (tbl2.state = 'deposited') AND (tbl2.created_at BETWEEN '2022-08-01 00:00:00' and '2022-08-31 23:59:59') GROUP BY DATE(tbl2.created_at)
それぞれのテーブルは以下の情報を持っているとします。
- tbl1: 注文情報のテーブル
- tbl2: 注文に関する入金済み/キャンセル済みなどの状態の履歴を保存しているテーブル。tbl1 とは1対多の関係。
1つ目のクエリの実行計画
MySQL > EXPLAIN SELECT `tbl2`.`col1` FROM `tbl2` WHERE `tbl2`.`state` = 'deposited' AND `tbl2`.`created_at` BETWEEN '2022-08-01 00:00:00' AND '2022-08-31 23:59:59'\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: tbl2 partitions: NULL type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 553831 filtered: 1.11 Extra: Using where 1 row in set, 1 warning (0.006 sec)
検証環境にて実行計画をとってみた結果こうなりました。MySQLのリファレンスに詳細に記載されていますが、各出力結果について抜粋して見てみます。
MySQL :: MySQL 5.6 リファレンスマニュアル :: 8.8.2 EXPLAIN 出力フォーマット
type
結合方法を示しており、ざっくり以下のようなものがある。
- const
PRIMARY KEY
またはUNIQUE
キーを参照して検索、一番速い
- range
- インデックスを使用し範囲検索
- index
- フルインデックススキャン
- インデックスのみのスキャンのため
ALL
よりは速い
- ALL
- フルテーブルスキャン
テーブルからの行の取得を可能にするインデックスを追加することで、ALL を回避できます。
とある通りインデックスが貼られていないので一番遅い
possible_keys
行検索に使用するために選択できるインデックスを示す。カラムが NULL の場合は参照するインデックスがない。
key
MySQL (オプティマイザ)が実際に使用することを決定したキー (インデックス) を示す。key が NULL の場合、MySQL はクエリをより効率的に実行するために使用するインデックスを見つけられていない。
また、possible_keys は候補を挙げている(NOT NULL)だが key は NULL になる場合もあり、それは表全体のレコード数が少ないなどインデックスを使用せずフルスキャンした方が効率的であるとオプティマイザが判断したためと思われる。
ref
テーブルから行を選択するために、key に指定されたインデックスに対して比較されるカラムまたは定数を示す。
rows
MySQL がクエリ実行のために調査する必要があると考える行数(見積もった行数)を示す。
filtered
rows は調査される推定の行数を示すのに対し、こちらはテーブル条件によってフィルタ処理されるテーブル行の推定の割合を示す。
Extra
MySQL がクエリを解決する方法に関する追加情報。(一部のみ)
- Using where
- 頻繁に出力される。WHERE句に検索条件が指定されており、なおかつインデックスを見ただけではWHERE句の条件を全て適用することが出来ない場合に出力。
- Using index
- クエリがインデックスだけを用いて解決できることを示す。Covering Index (複合インデックスに対し単一インデックス/一つのインデックスだけで検索が完結するインデックス)を使用している場合などに表示される
- Using where とも示されている場合、キー値のルックアップを実行するためにインデックスが使用されていることを意味する
- Using filesort
- filesort(クイックソート)でソートを行っていることを示す
- 検索条件とソートの条件の両方に利用できるインデックスが見つからない
- 検索とソートに適した複合インデックスを作成する必要があるが、条件に最適なインデックスとは別のインデックスが検索またはソートに使われてしまっている、という可能性も考えられる
- Using temporary
- JOIN の結果をソートしたり、DISTINCT による重複の排除を行う場合など、クエリの実行にテンポラリテーブル(CREATE TEMPORARY TABLEを使って一時テーブルを作成)が必要なことを示す
これらの出力結果と、1つ目のクエリの実行計画内容と照らし合わせてみると以下の問題点があります。
- フルテーブルスキャンされており、インデックスの利用は無いので遅い🙅
- Using where なのですでにあるインデックス + 更に WHERE 句の条件を参照しなければいけない状態 🙅
- 条件によってフィルタ処理されるテーブル行の推定の割合がたったの 1% である🙅
改善してみる
上記の結果からこれらをやってみます。
- 既存の別の検索条件&インデックスを使うでは得たい結果が得られないので、tbl2 テーブルに state, created_at で複合インデックスを貼ってみる
- 更に1のインデックス(index_on_state_created_atとする)を USE INDEX 構文により強制使用させる ※ MySQL で possible_keys カラムに示されたインデックスを強制的に使用させる or 無視させるには、クエリで FORCE INDEX, USE INDEX, または IGNORE INDEX を使用する
実行計画は以下のようになりました。
MySQL > EXPLAIN SELECT `tbl2`.`col1` FROM `tbl2` USE INDEX (index_on_state_created_at) WHERE `tbl2`.`state` = 'deposited' AND `tbl2`.`created_at` BETWEEN '2022-08-01 00:00:00' AND '2022-08-31 23:59:59'\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: tbl2 partitions: NULL type: range possible_keys: index_on_state_created_at key: index_on_state_created_at key_len: 1027 ref: NULL rows: 19 filtered: 100.00 Extra: Using index condition 1 row in set, 1 warning (0.003 sec)
- type ALL -> range に変わり、key も index_on_state_created_at になったことからちゃんとインデックス利用して検索されてる🙆♀️
- rows 553831 -> 19 で大きく減っており、行見積もりが減った = 実際に検査するレコード数もそれだけ少なくなったので高速化が期待できそう🙆♀️
- filered 1.11 -> 100 で 100% がテーブル条件によってフィルタ処理される🙆♀️
本番環境でのパフォーマンスも大幅改善され、注文情報を集計するページが重すぎて場合によってはタイムアウトする
という事態も発生しなくなりました。
2つ目のクエリも改善してみる
その②のクエリも改善してみます。
改善前
MySQL > EXPLAIN SELECT DATE(tbl2.created_at) AS date, COUNT(tbl1.id) AS count FROM `tbl1` INNER JOIN `tbl2` ON `tbl2`.`col1` = `tbl1`.`id` WHERE (tbl1.type = X) AND (tbl1.id IN (XXXXXX)) AND (tbl2.state = 'deposited') AND (tbl2.created_at BETWEEN '2022-08-01 00:00:00' and '2022-08-31 23:59:59') GROUP BY DATE(tbl2.created_at)\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: tbl1 partitions: NULL type: range possible_keys: PRIMARY,index_tbl1_on_type key: PRIMARY key_len: 4 ref: NULL rows: 3 filtered: 50.00 Extra: Using where; Using temporary; Using filesort *************************** 2. row *************************** id: 1 select_type: SIMPLE table: tbl2 partitions: NULL type: ref possible_keys: index_tbl2_hoge,index_tbl2_fuga key: index_tbl2_hoge key_len: 4 ref: tbl1.id rows: 1 filtered: 5.00 Extra: Using index condition; Using where 2 rows in set, 1 warning (0.004 sec)
tbl1, tbl2 テーブルそれぞれの結果が出力されているが、ともに id: 1 で select_typeは SIMPLE になっており、これは複数テーブルであっても1回の処理でデータを取得する実行計画であることを意味します。
(select_type は JOIN 含む単純な SELECT では SIMPLE
だが、UNION やサブクエリ使用時はそれに応じて変わる)
実行計画からこれらのことがわかります。
- 両方、利用可能なインデックス(possible_keys)の候補が複数ある
- Using filesort があり、検索条件とソートの条件の両方に利用できるインデックスが見つからない
- 2個目は特に filered (条件によってフィルタされる行の推定割合)が低い
改善後
こちらは以下をやってみました。
WHERE句には以下の条件を指定している。オプティマイザが最適と解釈した順番で実行されるのでWHERE句の順番によって速度には影響しない。と解釈しているが、
tbl1.type = X
での絞り込みによりある程度取得するデータ量を少なくできると見込んでいるので tbl1 テーブルに type, idc で複合インデックスを貼ってみるWHERE tbl1.type = X AND (tbl1.id IN (XXXXXX)) AND (tbl2.state = 'deposited') AND (tbl2.created_at BETWEEN '2022-08-01 00:00:00' and '2022-08-31 23:59:59')
これも更に1のインデックス(index_on_type_id とする)を USE INDEX 構文により強制使用させる
結果はこちら
MySQL > EXPLAIN SELECT DATE(tbl2.created_at) AS date, COUNT(tbl1.id) AS count FROM `tbl1` USE INDEX (index_on_type_id) INNER JOIN `tbl2` ON `tbl2`.`col1` = `tbl1`.`id` WHERE (tbl1.type = X) AND (tbl1.id IN (XXXXXX)) AND (tbl2.state = 'deposited') AND (tbl2.created_at BETWEEN '2022-08-01 00:00:00' and '2022-08-31 23:59:59.999999') GROUP BY DATE(tbl2.created_at)\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: tbl1 partitions: NULL type: range possible_keys: index_on_type_id key: index_on_type_id key_len: 8 ref: NULL rows: 3 filtered: 100.00 Extra: Using index condition; Using temporary; Using filesort *************************** 2. row *************************** id: 1 select_type: SIMPLE table: tbl2 partitions: NULL type: ref possible_keys: index_tbl2_hoge,index_tbl2_fuga,index_on_state_created_at key: index_tbl2_hoge key_len: 4 ref: tbl1.id rows: 1 filtered: 5.00 Extra: Using index condition; Using where 2 rows in set, 1 warning (0.003 sec)
- 1個目の方の key が PRIMARY -> index_on_type_id になったが、filtered は 50 -> 100 になった🙆♀️
- 1個目の Using filesort が変わってないが、これは JOIN した結果に対してソートするときは常にfilesortである、というのが調べていて判明。そのため filesort は消せなかったのだが、
Using index condition
も含まれているので作成したインデックスはちゃんと使えている🙆♀️
2個目に関しては正直ほぼ差がないけれど、それでも改善前よりは高速化が見込めると検証環境で判断したので本番環境にも適用し、ある程度は速くなったかな〜という所感でした。
好きなことの話
改善できた事例を挙げてみたものの、数万〜数十万単位になってくるレコード数(データ量)で実行計画からクエリチューニングを行いバーーンと爆速になったぜ!という成果を出すのはなかなかに難しいです。ちまちま手を入れてちまちま改善しています。
私は現在 minne というサービスで Customer Reliability Engineering(CRE) という活動をやっています。社のテックブログ「minne CREを紹介します」でも触れていますが、「カスタマーサポートの力の向上・拡大」も重要なミッションのひとつであり、サポートチームに成果を最大限発揮してもらうために顧客管理・注文情報管理システムの性能向上は不可欠なので、意欲的に上記のような改善をやるようになりました。
この記事タイトルに立ち返りますが、自分がエンジニアをしてて好きなことってなんだろうとふと考えてみて、私は「RDBMSのデータに触れる」が好きなことの一つです。こういう改善やるのも単純にデータに触れて改善するのが好きだからというのもあります。
どんな大規模なサービスでも根っこのデータは表形式でデータを管理しているって、こんなにシンプルな思想で実現できているなんて RDB って本当にすごい代物だよなぁと年々愛着が高まって(?)います。(NoSQL も使っていますが)
どうデータを持たせるかテーブル設計するのも好きにクエリ書いて取りたいデータを取れるのもマジで楽しいし、大規模なサービスに携わっているからこそ感じられるようになったことですがデータの傾向一つで今は何が起きているかを察知できるのも楽しいです。人の行動で生まれるデータって正直で尊い。宝。精通度はともかく、RDBを触るのが楽しいからこそサーバサイドのエンジニアをやっているのかもしれません。
ということで、アドベントカレンダーだし実行計画の他にもパーソナリティがわかる何かがあると良いかな?と思いつつ、冒頭で離脱されないように好きなこと語りは最後に入れました。来年ももっと開発して楽しい瞬間を増やしたい。読んでいただきありがとうございました!
(リンク集にペパボの過去数年分のアドベントカレンダーがありますが、地味に 2018 年の入社から皆勤賞でした。5回...5年.....め...だと....????)
🎄GMOペパボエンジニア Advent Calendar 2022 もありますのでそちらもぜひ!
RubyKaigi2022 覚書と感想
RubyKaigi をリモート視聴!Day3は移動しながらであまり聴けなかったのだけどDay2まで聴いたセッションでいくつか覚書。
- Make RuboCop super fast
- How fast really is Ruby 3.x?
- Types teaches success, what will we do?
- TRICK 2022 (Returns)
- 雑感
Make RuboCop super fast
Ruby 3x3(Ruby2の3倍の実行速度を目指すという構想)のようにRuboCop 2x2をキャッチフレーズに、RuboCop 2.0を開発しているという話。RuboCopには開発者体験の向上、またエディタやIDEとの連携が求められている。速さの向上は言うまでもなく。というところで、以下の手法で高速化しているとのこと。
- キャッシング
- .cache/rubocop_cache に分析済みの情報を残す
- マルチコア
- 1.19 からデフォルトでマルチコアで動くようになっているが、さらにparallelでの実行でボトルネックになっている箇所を並列に実行されるようにした
- デーモンモード
- server モードでしか使わないrequireを外すことで850倍速くなった
- 4つ目の手法も紹介されていたがメモしきれず….
また Rubocop の過去と未来の話で、以前は RuboCop を実行するごとに Rubocop::CLI のプロセスを起動しているため重いという課題があったが、これは rubocop-daemon というライブラリの登場である程度解決された。デーモンとしてプロセスに常駐し、実際のCLIはデーモンへのコマンド送信と結果受信のみ行うので早い。なるほど。
そして RuboCop は server モードが 1.31 から導入されたが、 server では重いモジュールの読み込みを解決しておいて、client は server に繋ぐだけにすることで高速化に成功したというもの。
rubocop --server
でオプション指定することで実行できるようになっている。
rubocop-server コマンドの提案もあったが利用者は RuboCop がそのまま速くなるのを期待しているはずなので別コマンドよりはオプション化が望ましいだろうということで「コードにしたものと、コードにしなかったことがプログラミングである 」の指針で検討したというのは、ソフトウェア設計で大事な考え方だなぁと頷きながら聴けた。
How fast really is Ruby 3.x?
Ruby 3 では YJIT や Ractor などの改良があるが実際どのくらい高速化されたのか?というのを Ruby のアプリケーションである Fluentd から評価した、という内容。(問題解決する機会が少ないのでそうか Fluentd って Ruby 製だ!っていうのを忘れていた….)
Fluentd は息の長いプロジェクトであり、Ruby のランタイムに依存しているプロジェクトでもある。
が、それぞれの過去の Ruby をスナップショットした td-agent が残ってるので、比較できるというユニークな強みがある。(td-agent は Fluentd を各OS用に rpm, yum パッケージで配布している)
Rails だとデータを引っ張ってくる処理が多いので、ボトルネックは MySQL や I/O になりがち
→つまり、Ruby3x3 で3倍速くなったとしても Rails が3倍速くなるというのは考えにくく影響は限定的では?となるが、Fluentdでは実際にRubyで重い処理をやっているため違う結論が得られるはずでは?と検証。
Ruby 1.9 と 3.2 を比較しその結果 Fluentd のベンチマークでは Ruby3x3 は達成された、YJIT は素晴らしい。という評価。
他言語(Python)と比較したりするとまだ差はあるというのも語られていたが、ベンチマークテストのプロセスをこうやって聴くのは楽しい。
Types teaches success, what will we do?
Ruby3 から導入された型定義のメリットを享受していこうというお話で、gem の RBS(型定義)ファイルを管理する gem_rbs_collection の紹介。
なぜRubyの型定義をプロジェクトに導入するのは難しいのか。RubyGems.org からどれだけの gem に型定義されているのか見ると、44個/17万 しかない。約0.03%。
gem_rbs_collection へのコントリビュートを行い、型定義された gem を増やすことで既存プロジェクトへの型定義導入障壁を取り除こう!ということでそのコントリビュート方法について
- すでに型定義がある場合、gem のアップデートに伴う型まわりの変更の追従
- steep check というコマンドがあるのでそれを実行しエラーが出て型定義に誤りがありそうなら PR を投げてみる
- また source_location というメソッドがありこれを使うと定義されてるファイルがわかって便利
- 新規に型定義を gem_rbs_collection に登録する場合は コントリビュートガイド に則って進めよう
既存プロジェクトへの型定義導入、確かにどこからやってみるのがいいのだろうというところだったので障壁を取り除くのに gem_rbs_collection へのコントリビュートから初めてみるというのは良い!
TRICK 2022 (Returns)
github.com に全公開されている!
面白かったー。eto award: "Most global"–Yusuke Endoh
こちらが特に好き🌍
https://github.com/tric/trick2022/blob/master/06-mame/entry.rb
雑感
追いつけたもの、追いつけなかったもの含めて10セッションほど聴講したが、全編通して難しかった〜というのはありつつ、知らない用語に触れて学ぶ(この割合が多い)だったり普段使ってる技術要素に対する自分の曖昧な認識を正すいい機会になるなと実感。
というか知らない→知る→やっぱ何もわかってない→今度こそ知る(なお)ということの繰り返しなので、本など読んでみて知った気になるのではなくその都度自分で触って知らない→知るを繰り返していかねば。久しぶりの RubyKaigi 楽しかった!
ActiveRecord includes, eager_load, preload の挙動の話
ActiveRecord の N+1 問題を解消するため関連するテーブルのデータを事前に読み込みキャッシュしておく、その方法に includes, eager_load, preload メソッドがある。
それぞれ以下の理解でいて、とりあえず includes を使ってそこからパフォーマンスに難があればメンテしていけば良いっぽい という雑な考えで includes を採用した処理があった。
まず、joins はデフォルトで INNER JOIN を行うが association のキャッシュはしない。
- includes
- キャッシュはする
- 実際に発行されるクエリが一つで済むか(指定した association を複数のクエリに分けるので)複数になるかは場合による、以下の通り
- eager_load
- 指定した association を外部結合(LEFT OUTER JOIN)しキャッシュする、そのため実際に発行されるクエリは一つで済む
- preload
- 指定した association を別クエリで取得してキャッシュするので、複数のクエリが発行される
その後パフォーマンスに懸念が出てきたので eager_load に変えたがその時見落としていたことがあり、
has_many :hoge, -> { order(position: :asc) }, dependent: :delete_all
のように中間テーブルを has_many で定義した際の order のスコープブロックが無視され ORDER BY hoge.position ASC
が付与されなかったというものだった。
挙動として preload が 指定したassociationを別クエリで取得
に対し eager_load は LEFT_OUTER_JOIN で指定したテーブルを結合してキャッシュする
のであれば、その際引数を渡して絞り込みやソートなどを指定していなかったので当然かも。と思い order を明示的に指定することで対処したのだが、改めてincludes, eager_load, preload についてちゃんとコードを追う。
顧客信頼性エンジニアをしていますと言い張って一年が経った
この記事はGMOペパボエンジニア Advent Calendar 2021 9日目の記事です。
現在、前回の冒頭でも話していた CRE をやっています。最近は以下のイベントで発表したようにこんな活動をしているというのを外に発信できる余裕も多少出てきました。
ということで表題について、CREをやりはじめて丸一年が経過した。
今書かないと今後書かない気がするので、顧客信頼性エンジニアをしていますと言い張って、自分に対しての気付きを振り返って書いてみることにする。
この記事を読んだ人にCREをやってもらいたいなという意図があって書くわけではなく、こういう人も居るんだなという感じで読んでいただければと思います。
続きを読むレビューを分析してみる2020
この記事は GMOペパボエンジニア Advent Calendar 2020 の 22 日目の記事です!
さて最近は CRE(Customer Reliability Engineer)として活動しているのですが、その件については会社のテックブログで執筆している最中なのでそちらで、今年何書こうかなとネタを探していた時。
2020年、1月末から在宅勤務をしており当初はうまく仕事進められるかなぁと不安に思っていたのも束の間、リモートワークのスタイルは結構自分に合っていたのかなんなのか、CREを始める転機にもなったし自分の成長を少しずつ感じた1年だったので、ふと思い返していて新しいことを書くよりこれに思い当たりました。
「このサービスにジョインして結構コード書いてきたけど、お前は今まで受けたレビューを活かせているのか?」と・・・
明らかにこういう指摘減ってるじゃんとかそういう成長できてるかと・・・
自分のやった特定の施策、特定の仕事に関して振り返る機会は多いと思うのですがそういう振り返りって実はあんまりちゃんとしてなくない?
ということで今携わっているサービス minne にジョインして約2年半、自分の立てた PR に対してのレビュー内容を振り返ってみる。
続きを読むGoogle Apps Script で issue 確認リマインドを作る
業務でメンバーへの依頼・共有がある時 issue を立て、そんな時コメント中でメンションをつけたり関係者を assign しておくということはどこでもやっていることだと思うのだけど、特に作業依頼の場合そんな issue が見逃されて放置されてしまうのはかなしい。
ということで例によって GAS & GitHub API を使い、issue が立って一定期間放置された(クローズされない)ままだと Slack にリマインドしてくれるやつを作ってみる。
Google Apps Script の作成方法としては以前の記事に書いてあるので割愛。
- 1. issue の一覧を取得
- 2. 作成からの経過日数を抽出
- 3. assign されている人がいたら通知内容に出力
- 4. webhookにリクエストを送信
- 5. 休日・祝日を判定
- 6. いざ実行
- おまけ - payload で attachments ではなく blocks を使ってみる
Google Apps Script はじめました
「GitHub Enterprise のリポジトリの、特定のラベルがついた issue にコメントがあった時に Slack に通知させたい」という目的で Google Apps Script をはじめたら思いの外楽しい。
上記を実現しようとしたとき GitHub integration for Slack であれば /github
の Slash コマンドで十分ではある。
subscribe で ave-hikari/tips
というリポジトリの issue のコメント(アクティビティ)を通知させる
またこの通知を bug
のラベルがついた場合のみにフィルタする
コメント時以下のように通知される
(通知解除時は /github unsubscribe
)
特定のリポジトリの全 issue に対してでは量が多すぎるので特定のラベルがついてるやつのみ通知させたい!というカスタムがしたいとき、
Google Apps Script でも GitHub API を使って、かつ通知を見た時テンションも上がるように Slack に通知させてみよ〜という感じに思い至った。
スクリプトは基本的な JavaScript + GAS 独自の関数、という感じなので新規のアプリ作成&導入手順やGCPでのログ参照周りを細かく残す ✍️
続きを読む