読者です 読者をやめる 読者になる 読者になる

下町柚子黄昏記 by @yuzutas0

したまち発・ゆずたそ作・個人開発の瓦礫の記録

サービスレベル:設計と運用のプラクティス

概要

サービスレベルをいかに設計し、いかに運用するか。自分なりの考えの整理です。 尋常ではない長さになりました。随時アップデートします。たぶん。

ウェブオペレーション ―サイト運用管理の実践テクニック (THEORY/IN/PRACTICE)

ウェブオペレーション ―サイト運用管理の実践テクニック (THEORY/IN/PRACTICE)

もくじ

SLAとは何か

関係者が同じ目線を持つためのもの

例えば、システムエラーで真夜中にアラート通知が来たとする。そのエラーはどの程度のレベルなのか。 今すぐ対応しなければいけないレベルなのか。翌朝に出社してから対応すれば問題ないレベルなのか。

関係者全員が同じ目線を持てるようになると、運用が少しだけ楽になる。 過剰な期待に振り回されなくなる。未熟な対応に甘んじなくなる。

システムの保守運用だけに限った話ではない。「明日までにお願い」とか「大事だから緊急でやってくれ」とか。 そんな依頼を受けて違和感を覚えた経験があるなら、サービスレベルについて考えることで便益を得られるかもしれない。

火の一ヶ月間を経て……

私自身が体験したのはこんな事例1だ。

1営業日あたり2件の障害が発生する日々が1ヶ月間続いた。 ふと気が付いたら朝の5時だったり、土日が丸々潰れたり、そんな日々だった。 そのせいでヒューマンエラーや二次災害も起きた。まさに火の一ヶ月間だった。

炎が沈静化してから、真っ先にサービスレベルを定義して、関係者の合意を取った。 すると、死に物狂いで対応した内容の約半分が、必ずしも障害とは言えないことが分かった。 最初から品質に関する共通指標があれば、無理をせず安定したトラブル対応ができただろう。

SLAは契約ではなく、目標の合意に過ぎない

「受注側が満たせなかったら、発注側に賠償金を支払う」といった契約条件としてSLAを盛り込むケースがあるらしい。 そうではない。SLAは、サービスとして目指したい品質を、関係者内で合意するためのものだ。

未達成だと損をするような枠組みでは、高い目標に挑戦できるわけがない。 OKRという目標管理手法では「実績が達成度70%くらいになる高い目標」を定めるのが良いと言われている。 目標を合意するというのはそういうことだ。

SLAと聞くと、国内大手SIerがお堅い契約をするイメージがあるが、それは誤った認識だ。 GoogleFlickerでもSLA(もしくは類似する概念)にもとづいてシステムを運用しているらしい。 本格的にITサービスを開発・運用するなら、規模や形態を問わず適用できるのがSLAだ。

SLA:設計のプラクティス

いかにSLAを設計するか。

サービスのレベルを設計する

機能観点でのレベル分け

3〜4段階でレベルを分けることができる。

レベル 内容 例)音楽共有SNSを運営していた場合
1 セキュリティや法律を守れているか 演奏者の個人情報が本人以外から見えてしまうとレベル1の違反となる。
2 相対的に重要な機能が使える状態になっているか ログインや音楽演奏(重要な機能)ができないとレベル2の違反となる。
3 相対的に重要でない機能が使える状態になっているか 楽曲再生数(おまけ機能)が正しくカウントされないとレベル3の違反となる。

※ちょっとした画面の表示崩れなどを「軽微な不具合」として4段階目に入れることもできる。

※機能観点とは言えないが、カスタマーリレーションを広義のサービス運用に含めると、 社会的配慮(LGBT・宗教・趣味・職種への言及)についても、セキュリティや法律と同レベルで扱う必要がある。

コア機能を定義する

機能の優先度をつけるのは難しい。関係者の中で合意を得るのはさらに難しい。 「8割の機能は使われない」「顧客は2割の機能にお金を払っている」という言葉を踏まえて、優先度を判定することになる。

例えば、Flickerでは 写真を閲覧する機能 > 写真を新たに投稿する機能 の優先順位となっているようだ。 『ウェブオペレーション - サイト運用管理の実践テクニック』にて、Paul Hammond(元Flickerグループリーダー)が述べている。

サイトを提供する上でコアとなる機能を真剣に考えるとよいだろう。それがなくてもサイトを継続できる機能は何だろうか。

例えば、Flickerの場合は、写真を閲覧する機能がそのままであれば、アップロード機能がなくても構わない。

(この書き方をどこまで解釈できるかは怪しいが)少なくとも、コア機能については考えた方が良い。

まずは、自分のサービスがどんな機能を提供しているのか一覧化すると会話しやすくなる。 そのリストをもとに「コアとなる機能・サブとなる機能を振り分けましょう」と言って議論を始められる。

「全部重要」は「全部重要ではない」と同じ意味になってしまうことに注意したい。 チームのキャパシティを超える事態に陥った時には、1つ1つ優先順位を付けて行動せざるを得ないからだ。 やるべきこと・やりたいことは、大抵の場合、チームのキャパシティを超えている。

そのサービスにとって、何が本当に重要なのか。何が提供価値なのか。定義することが重要。

非機能観点でのレベル分け

システムとしての信頼性と性能がメインの対象となる。

信頼性

観点 内容
サーバサイド サイト停止の頻度・時間
クライアントサイド モバイルアプリやフロント画面でのクラッシュ
プラットフォーム 規約違反でAppStoreからBANされないか、自己証明書を使ってFirefoxからアクセス不可になっていないか

性能

観点 内容
レスポンス状況 最終指標:応答時間
キャパシティ 中間指標:CPU、メモリ、ディスク容量

後述する「目標設定」が重要になる。パフォーマンス目標として、どの程度の水準を目指すのか。 同じく後述する「定期的な振り返り」も重要だ。キャパシティ利用の推移を把握して、問題が起きる前にストレージを追加できると安心だ。

オペレーションのレベルを設計する

対応速度のレベル分け

サービス品質が脅かされた際のオペレーションは、以下のようにレベル分けできる。

レベル 概要 詳細
1 即時対応 夜中や休日であってもベストエフォートで対応する。
2 優先対応 営業日・営業時間内に対応。その間は機能追加の開発をストップする。
3 通常対応 開発要望リストに起票して、優先順位を判断する。次以降のタイムボックスに申し送る。

サービスレベルと組み合わせて考える。 どのレベルの問題だったら、どのレベルの対応速度なのか。

3つのフェーズ:調査→暫定対応→恒久対応

トラブル発生時、「調査」と「課題解決」で要求されるスピードは異なる。 アラートメッセージだけでは何が起きているのか分からない。そんな状況だと、サービスレベルを判定しようがない。 最悪の場合を考えて、まずは即時で事象の解明に当たる。そして問題が明らかになった段階で、再度レベルを判定する。

「課題解決」は、「暫定的な解決」と「恒久的な解決」に分けることができる。 ITILではインシデント管理と問題管理と呼び、これらは時にトレードオフの関係になる。 調査するためには現状を残した方が良いが、現状を残していてはユーザーに迷惑が掛かる、といった具合だ。

原則的にはインシデント対応を優先するのが望ましいとされている。 カスタマーケアの担当者は、利用者の不満を解消することに注力しなければいけない。 WebAPIサーバが落ちてスマホアプリが使えなくなったら、迷惑を掛けてしまったことを謝った上で、WEBサイトの利用を案内する。 その間にシステム担当はサーバ復旧に全力を尽くす。サーバが落ちた真因追求と予防策を考えるのは、落ち着いた後だ。

※大量・同時に問題が発生したときは、サービスレベルをもとに対応の優先順位を決めることになる。 Flickerの例を再掲すると、写真投稿と写真閲覧が両方できなくなったら、まずは写真を閲覧できるようにするのだ。

※恒久対応については、機能追加と同じようにプロダクトバックログ(あるいは変更要求一覧)で管理するのが望ましい。 プロダクトオーナーはサービスレベル目標をもとに、他案件との優先順位を決めることになる。

ベストエフォート

ベストエフォートへの過剰な期待

金曜の夜中にサーバが落ちたまま、月曜の朝までアクセスできなかったとしたら「ありえない」と思うだろう。 ベストエフォートで対応することになる。

ただし、あくまでもベストエフォートは、努力に過ぎない。約束ではない。 月曜の朝までアクセスできない状態が普通だ。そこに努力を上乗せするのだ。 深夜・休日の対応が続くと、そのオペレーションレベルが当たり前になり、チームは疲弊し、いずれ瓦解する。

試算すると、24時間365日で対応できるシフトを組むには、毎月1500万円ほど追加費用が発生することになる。

  • 前提1:アプリ(iOSAndroid・WEB)、インフラ(OS・MW)、ディレクションごとに各担当がいること。
  • 前提2:トラック係数を保つために&連続勤務にならないように各担当それぞれ3人以上であること。
  • 前提3:1人あたり人件費は月100万円。平日昼間勤務より合計稼働時間は減るが、夜間休日シフトで時間当たり金額は割高になる。

仮に全部のシステムモジュールを扱えるエンジニアを集めたとしても、雇用人数は減るが1人当たり単価は上がるだろう。 どのみち上記を満たす人材を探すのは困難だ。障害対応のためだけのシフト勤務というのは、身体的にも精神的にも好ましくない。

言いたかったのは「稼働率100%に近づけるためには、それくらいのコストが掛かる」ということ。

明示的に人件費として負担するのか。ベストエフォートの名の下で労働者に負担を移転するのか。 本来はコストとして積まれるべきものだ。 医療従事者には、宿直制度があり、オンコール体制を巡って何度も裁判が起きている。それと同じ。

ベストエフォートへの期待を満たす

とはいえ、月曜の夜中にサーバが落ちたまま、月曜の朝までアクセスできなかったとしたら、ステークホルダーの大多数は「ありえない」と思うだろう。 少なくとも、顧客はそう思うはずだ。顧客志向な従業員もそう思うはずだ。

いかにダメージを最小化するか。以下のような方針が考えられる。

  1. シフト制を導入してトラブルに対応する。宿直とまではいかなくても、オンコール体制にして、待機時間手当を支払う形でコストを積む。
  2. 人材や働き方の多様性を確保する。従業員全員が世界中でリモートワークをしていれば、誰かが夜に眠っている最中でも、地球の裏側の誰かが昼間にトラブル対応できる。

これらは理想の話だ。将来的にはそうありたい。ただ、すぐに理想の運用を開始するのは難しい。 大多数の組織において、結局はシステム担当者のガッツに委ねられることになるだろう。

確実にできることがあるとすれば予防活動だ。 対処のコストが大きい=予防が大事、という共通認識を醸成したい。 予防活動を仕組み化することはできる。予防の取り組みについては後述する。

影響範囲

アクセスの多い時間帯と少ない時間帯

夜間・休日の対応はベストエフォートであって約束ではないと述べた。

では夜間や休日にアクセスが集中するようなサービスのSLA・OLAはどうなるだろうか。 相対的には平日の昼間よりも重要なはずだ。 同じように、テレビCMを打ち始めたサービスであれば、CMの放送時間帯にアクセスできることが重要となる。

特定時間帯にオペレーションのレベルが高くなる場合、シフト制の導入を本格的に検討せざるを得ない。 ベストエフォート対応は安定しない。サービス特性とビジネス価値を守るために、払うべきコストは支払うしかない。

一方で、深夜・休日のシフト出勤をトップダウンで決めるのは危険かもしれない。 「従業員第一主義」(サウスウェスト航空)という言葉がある。 まずは従業員が安心とゆとりを持って働けるような環境を作る。 するとメンバー1人1人が工夫してサービスの信頼性を高めるようになり、深夜や休日でもユーザーが快適に利用できるようになる。 結果として利益に繋がり、株主が喜ぶ。あくまでも起点は従業員となる。 生活リズムを崩すような仕組みを明示的に導入するのとは正反対のロジックだ。

誰に何を約束するのか。どんなコストを掛けて、どんな価値を守るのか。 ユーザーとサービスだけでなく、ステークホルダーや業務についても、守るべき品質を定義できると素晴らしい。

影響範囲の閾値を決める

時間帯の話は、大きな括りでは「影響範囲」と呼ぶことができる。 ユーザー全体の1%だけが影響を受ける時間帯と、70%以上が影響を受ける時間帯だと、サービスレベルは変わるだろう。 同じように、ユーザー全員がログインできない障害と、1人だけがログインできない障害だと、前者の方が重く捉えられる。

誤解がないように補足すると、1人がログインできない障害についても、軽視すべきではない。全力で対応すべきだ。 ただ、1人分のデータにパッチを当てたり、新アカウントを手動で用意する、といった回避策を実施しやすい。 サポートスタッフの1人が、その1人のユーザーにつきっきりで対応することもできる。 10,000人がログインできない場合に比べると代替案や選択肢は多い。サービスレベルは相対的に低くなる。

影響範囲を考慮すると「全アクティブユーザー数の5%を超えるかどうかでレベルを上下させる」といったOLA設計ができる。 例えば、1人がログインできない場合(重要機能) = 全員のアクセスカウンターが誤表示する場合(おまけ機能) で同じくらいのレベルになるだろう。

例示に違和感があるようなら、以下のようなレベル設定はどうだろうか。

レベル 要約 内容
1 超ヤバい 全員がログインできないときのレベル
2 結構ヤバい 1人がログインできないときのレベル
3 少しヤバい 全員がおまけ機能を使えないときのレベル
4 まぁまぁ 1人がおまけ機能を使えないときのレベル

このSLA設計をするチームは、重要機能の軸がベースにあり、そこに影響範囲の軸を加えて判断していることになる。 それも1つの決め方だ。正解は1つではない。大事なのはレベル感について関係者が同じ認識を持つことだ。

サポート対象:OS・端末・ブラウザ・アプリバージョン

影響範囲の延長上にあるのが、サポート環境の話だ。 どのOS・端末・ブラウザ・アプリバージョンまでサポートするかを決める必要がある。

サポートの具体的なレベルとしては、3つに分けることができる。

レベル 顧客対応 テスト 内容
1 しない しない 「このブラウザで閲覧できないのですが」と問い合わせが来たら、別のブラウザでの利用を案内する。
2 する しない マイナーなブラウザの細かい挙動まではテストせず、「画面が崩れます」と問い合わせを受けたら直す。
3 する する 常にテストまで実施する。

「該当ブラウザを利用するUU数 / 全体のアクティブUU数」といった形でシェア率を測定する。 5%以上のシェアであれば対応する、10%以上のシェアであればテストする、といった設定ができる。

重要機能かつサポート対象であれば、品質を担保し続けることになる。 リリースごとに手動で回帰テストを実施するのも辛いので、E2Eテストを自動化する話に繋がる。

SLA:運用のプラクティス

設計したSLAをいかに運用するか。

各項目について目標を設定する

機能観点

機能観点のサービスレベルは以下のような目標設定ができる。

レベル 内容 目標値の例
1 セキュリティや法律を守れているか ベストエフォート。そのサービスの歴史上で障害0件を目指す。
2 相対的に重要な機能が使える状態になっているか 重要機能が使えなくなる障害は月に1件以内に抑える。
3 相対的に重要でない機能が使える状態になっているか 月に10件まで許容する。

相対的に重要ではない機能は、品質目標を定めないという選択肢もありうる。 小さなバグで問い合わせが多発すれば(Dev/Opsが一体となっている前提のもとで)機能追加の開発にも影響が出る。 ベロシティが不安定になったり、リードタイムが伸びるといった形で可視化されるはずだ。

非機能観点

非機能観点のサービスレベルは以下のような目標設定ができる。

信頼性

観点 内容 目標値の例・補足
サーバサイド サイト停止の頻度・時間 月に2回まで。1回あたり3時間以内。
クライアントサイド モバイルアプリやフロント画面でのクラッシュ UU数やPVに対するクラッシュが3%以下。他アプリがメモリを食う、といったクライアント都合も影響するので、0件にはならない。
プラットフォーム 規約違反でAppStoreからBANされないか etc… ベストエフォート。そのサービスの歴史上で0件を目指す。

性能

観点 内容 目標値の例・補足
レスポンス状況 最終指標:応答時間 ピークタイムの中央値が0.5秒以下。危機を検知するために、負荷が大きい時間を見る。コネクションエラーなどで外れ値が出るので、中央値を見る。
キャパシティ 中間指標:CPU、メモリ、ディスク容量 40%以下。跳ね具合を確認したいので、ピークタイムの最大値を見る。

目標と実績

目標を満たせなかったときはどうするか。

例えば、GoogleのSREではErrorBudgetの概念が出てくる。 ある程度までは障害を許容するが、閾値を超えるようであれば、機能追加の開発を停止せよ、という内容だ。 実際にはまだ開発停止になったことはないようだが、目標を意識することで動き方が変わったらしい。

定期的に目標と実績を比べながら、振り返りを実施していくのが良いだろう。

月次で振り返りを実施する

会のアジェンダ

アジェンダ1:結果の振り返り

その月に発生した障害や対応が、どのサービスレベル・オペレーションレベルに該当するのかを記録していく。 その上で、各目標値を満たせたかのか確認する。これを毎月繰り返すことで関係者の目線が徐々に揃うようになる。

アジェンダ2:原因・対策の振り返り

その月に発生した障害や対応について、問題があったならば真因分析と改善施策を打つ。 例えば、証明書の期限が切れてブラウザからアクセスできない障害が起きたとしよう。 同じようなミスがないように、証明書の期限切れをリマインドする仕組みを作るのがTryになる。

アジェンダ3:傾向・予防の振り返り

その月の実績を見て、これから問題が起きそうだったら事前に手を打つ。 例えば、徐々にパフォーマンスが劣化し始めているとしよう。 クエリや設定をチューニングしたり、サーバを増築するのがTryになる。

アジェンダ4:SLA自体の見直し

ソースコードリファクタリングするように、SLAの設計自体を少しずつ進化させる。

例1:その時のシェア率を見てサポートブラウザを更新する。 「IEが減ってChromeが増えた!」のであれば「Chromeだけテストしよう!」という判断に繋がる。

例2:シェアの5%以上だったら問い合わせに対応するルールだった。 だけど「潜在的なユーザーを取り逃がしすぎているように感じる」とカスタマーサポートチーム。 「基準値を3%に下げて、この端末のトラブル対応にコストを割けないだろうか」という提案に繋がる。

振り返りの位置付け

定期的な会話を通して、品質に対するチームの共通認識を得ることが目的だ。

ロールによって認識が違うなら、その違いをぶつけ合って、最適なサービスレベルに軌道修正しよう。 機械的に振り返り会を実施するだけでは意味がない。対話が生まれて初めて意味がある。

ちなみにトラブル対応と振り返りは別なので注意したい。 キャパシティが急激に悪化するようだったら、すぐさまプロセスを再起動するといった対応が必要だ。 わざわざ振り返り会を待つ必要はない。

プロアクティブに予防する

トラブル対応や振り返りといった受け身の動きだけでなく、いかに予防するかも大切だ。

技術的負債を予防する仕組み作り

以下のエントリーにまとめた。

yuzutas0.hatenablog.com

SREの例:稼働の50%以上を信頼性向上のために使う

予防策の1つは、GoogleのSREを参考にして、稼働ポートフォリオを組む方法だ。 運用担当は業務時間の50%以上を割いてシステム品質を向上させる。 トラブル対応や定常作業に使う時間 < 予防活動に使う時間 を目指す。

リアクティブな活動の時間(=問題が入ってくる量)よりも、 プロアクティブな活動の時間(=問題が出て行く量)が多ければ、品質は継続的に向上できる。 逆だと品質は悪化し続け、地雷を踏む日が来るのを待つだけの辛い運用になる。

理想はDev/Opsの両方を1つのチームが担うこと

開発と運用を同じ人が担当する。この体制は品質担保のためのベストプラクティスと言える。

課題を可視化する=品質を担保する

運用観点を嫌でも意識できるようになるからだ。 企画・開発の際に運用観点を考慮するようになり、品質を維持しやすくなる。

また、計画外の運用作業が増えると、その分だけ機能追加に割けるパワーが減る。 結果として全体のボトルネックが可視化されるようになる。 可視化されることで、課題の解消・予防アクションに自然と繋がるようになる。

ウェブサイトの開発と運用は、別々のチームに分かれている。

この配置は一般的なのかもしれない。しかし、サイトの安定性を確保し、新しい機能を届けるには、おそらく最悪の方法である。

成功したインターネット企業(AmazonFacebookGoogleFlickerなど)の多くは、チームを分割せずに違ったやり方を試みている。

チームを分ける狙いは、開発の邪魔になるものを排除し、機能の実装が遅れないようにするためだが、ここから24時間稼働し続けるコードが生まれることはない。

日曜の午前3時にポケベルを鳴らされるくらいなら、システムを壊れにくくしたり、もし壊れてもツールで自動的に回復できるようにしたりするはずだ。

『ウェブオペレーション - サイト運用管理の実践テクニック』から引用)

開発・運用を一体化する → 課題が可視化される → 品質が担保される → 開発デリバリーに好ましい影響を与える、ということだ。

品質を担保する=デリバリーを加速する

よく言われる「QCDはトレードオフの関係」という考え方は無視しよう。

品質が悪ければ、スピードは落ちる。調査・改修・テストに余計な時間が掛かるからだ。 トレードオフが発生するのは、作るだけ作って1回リリースした後は放り投げるプロジェクトの場合だけだ。

継続的なデリバリーを実施する場合、クオリティとアジリティは同時に改善される。2 品質を下げてスピードを優先するという選択肢は存在しない。

3つの変数とは速度、品質、価格である。

品質を下げても、作業がなくなるわけではない。自分の責任だと分からないように、作業を後回しにしているだけだ。

多くの欠陥のコストは、それを防止するコストを上回る。

欠陥を発見するのが早ければ、それだけ修正するコストは安くなるのである。たとえば、欠陥の発見が開発の10年後だとしたら、(中略)解明するために、膨大な歴史や文脈を再構築する必要がある。同じ欠陥であっても、作成直後に補足すれば、その修正コストは最小限になる。

『エクストリームプログラミング』から引用)

障害対応はマッチポンプ

OLAの項目でベストエフォートについて述べた。夜間・休日の対応についてだ。

貴重な時間を費やしてトラブルを解決したメンバーには、好意を抱きやすくなる(シグナリング効果)。 しかし、予防の観点からすると、夜間・休日に対応した個人を評価してはいけない。アンチパターンだ。3

火を消して最高の報酬を得るよい機会だと考えるようではいけないのだ。そうなると、プロジェクトはほぼ間違いなく危機駆動になる。

もし組織がプロダクトを作り出すことを仕事にしているなら、プロダクトのデリバリーをサポートしたことに対して報酬を出すべきだ。

『組織パターン』から引用)

DevとOpsが一体化されているチームの場合、自分でバグを作って自分でバグを直すことになる。 マッチで起こした火をポンプで火を消すようなものだ。

深夜・休日の対応はネガティブなものだ。担当者は評価もされずに、嫌な思いをする。 そんな思いをしたくないから、品質を前工程で作り込む。 品質の安定に伴って、デリバリーが安定・加速する。

結果としてプロダクトの価値をムダ・ムラ・ムリなく顧客に届けられるようになる。

サービス保守運用の枠を超えて

ユーザーへの約束

利用規約やプライバシーポリシーを考える

法務担当以外は関心を持ちにくいかもしれないが、大切なドキュメントだ。 この文書を通してサービス提供者と利用者は約束を交わしている。

当然ながらサービスレベルにも連動する。約款の内容が守られていないと法令違反、最悪の場合は営業停止となる。 そうでなくとも社会的な信頼を損なう。同意なしに個人情報を販売するようなサイトを使いたいだろうか。

ユーザーに約束している機能は何か

同様に、サービス内で約束している機能はサービスレベルが高くなる。 お金を払ったのに漫画アプリで漫画が読めなかったら大問題だ。

どの画面でユーザーにどんな約束をしているのか、チーム全員が把握できるように一覧表をまとめよう。 開発・運用の担当者が青ざめるような約束があるかもしれない。

「24時間365日いつでもどこでもオンラインで映画を視聴できます!」というトップ画面があったらどうか。 稼働率100%だ。トラブルが起きたときには、24時間365日いつでもどこでも対応しなければいけない!

※かといって「週5日9:00-17:00でのみ映画を視聴できます!」というサイトに登録するだろうか。 ユーザーとしては難しいところだ。できれば終業後や休日にお酒を飲みながら映画を見たい。 そのため、障害発生時には割引クーポンを配布するといった工夫が必要になる。 何を約束し、どのレベルで約束を破ったら、どのレベルの補償をするか、SLA設計が必要になる。

約束と実態を合わせる

守ることができない内容を約束していたら、利用者も経営者も技術者も、全員が不幸になる。

過剰すぎない表現に変える工夫が必要だ。 ユーザーが気付かないような小さな注意書きを添えるだけではダメだ。 問い合わせやトラブルの削減には繋がりにくい。

もしきは、ユーザーへの約束に合わせて、サービスレベルの実態を上げる。 そのためのコストはきちんと支払うのが筋だろう。 サーバの増築だったり、機能追加を止めてバグシューティングに専念することだったり。

財務計画との連動

サービスレベルを財務観点で可視化する

財務計画とサービスレベルが連動すると、お金の流れを通して事業運営の実態を把握できるようになる。

ここで言いたいのは「経営をしよう」ということだ。 サービス運営のリスクは財務諸表(もしくは財務に連なるKPIツリー)に織り込もう。

稼働率100%・障害0件を前提にした計画は破綻する

営業活動を例に上げよう。訪問数と受注数は一致しない。 20社訪問して1社成約(5%)といった仮説を置くことになる。 そこから売上予想を立てていく。システムも同じだ。

稼働が100%ではないことを前提にして、(文字通り)Error BudgetをP/Lに反映するのが良いだろう。 「障害は月1件まで」のサービスレベルで目標設定するなら、障害が月1件起きる想定でコストを積もう。 少なくともKPIツリーのどこかで計測できるようにはしよう。

ネガティブサプライズを織り込む

システムは生き物のようなものだ。 障害は必ず起きる。障害が起きれば、利益を毀損する。

サイトを訪問するはずだった、広告を見るはずだった、課金をするはずだったユーザーを失うことになるかもしれない。 にも関わらず計測指標に組み込まれていないと、ある日突然、計画外のコストが乗ることになる。

ネガティブサプライズが起きると分かっているのに、財務計画に反映されていないのでは、健全なサービス経営はできない。

おわりに

サービスレベルを考えるということは「コストを払ってでも守るべき品質は何か」を考えるということです。

サービスにとって何が大切なのかを考えることです。 カスタマーに対して何を約束できるのかを考えることです。 ビジネスに対して何を約束できるのかを考えることです。

守れない約束のために疲弊するのはやめましょう。 守るべき約束を全力で守れるようになりましょう。

騙し騙しの企画や開発を続けていると、ある日突然、下らないことで足元を掬われることになるでしょう。 あらゆるチームがその罠に陥ってしまう可能性を持っています。100点のサービスは存在しないからです。

それでも健全な運用を目指し続けた先に、安定的な価値の提供があるのだと思っています。

強い会社はこうして作られる! ITIL実践の鉄則

強い会社はこうして作られる! ITIL実践の鉄則


  1. ちょっぴり盛ったけど、気にしないでほしい。

  2. ちなみにコストは人件費・サーバ費用が大きくなりやすいが、安易に増減できる変数とは言えない。

  3. 既にレガシーなシステムで、10年前に埋められた地雷が爆発したときには、英雄を褒め称えたい。

技術的負債:予防と返済のプラクティス

概要

技術的負債をいかに予防し、いかに返済し続けるか。自分なりの考えの整理です。 「原理原則をプラクティスとして運用する」という観点にフォーカスしています。 随時アップデートします。たぶん。

エクストリームプログラミング

エクストリームプログラミング

もくじ

技術的負債とは何か

Wikipediaより具体例の引用

文書化、テストコードの記述、ソースコード中の積み残し(TODO)項目の解決やコンパイラの警告、静的コード解析ツールの解析結果への対応

組織で共有されない知識や、複雑すぎて変更が難しいコード

なお、ヒューマンエラーや運用ミスであっても、その失敗を誘発するような仕組みが問題だと考える。

また、明確に期限性があるEOSL(OSやライブラリ、APIのサポート期限切れ)対応は別のエントリーにまとめる。

リードタイムや品質に悪影響を与える

例えば、クラス設計に一貫性がないアプリケーションを改修する場合、影響範囲の洗い出しが手間となる。 すると、調査・実装・テストに時間が掛かり(リードタイムに影響)、考慮漏れで不具合が発生するリスクもある(品質に影響)。 結果としてビジネスやカスタマー体験にも悪影響を与えることになる。

3つの特徴がある

アラート通知のノイズ除去を例にする。 キャパシティやエラーログを監視してSlackに通知するように設定したが、対処する必要のない通知(ノイズ)が混じったとしよう。

1. ビジネス効果の見立て・計測が難しい

アラート通知はシステム運用者向けのものだ。 どれだけノイズがあろうと、サービス利用者に直接的な影響はない。

かといって、除去しないままだと、致命的な障害の検知が遅れる(漏れる)可能性がある。 また、大量の通知に目を通す負担や、高頻度の通知による集中の阻害は、実務担当者にとって辛い。 ヒューマンエラーの温床だ。

ビジネス観点の機能追加か、アラート通知のノイズ除去か、どちらを先に実施すべきか。 優先順位を判断するのは難しい。ビジネス効果の見立て・計測が困難だからだ。

ロジックと数字を組み立てることはできるかもしれないが、技術課題1つ1つに多種多様で複雑怪奇な算出式を並べる羽目になる。 そのため、ビジネス観点とは別のトピックとして対処することが望ましい。

2. 新しい課題が常に発生し続ける

除去すべきノイズはどんどん増えていく。

アプリに機能を追加するにつれて、コードベースは複雑になり、アラートの発生箇所は増えることになる。 アプリに機能を追加しなくても、依存ライブラリや連携システムのEOSL、利用者増加に伴う高負荷で新たなアラートが投げられる。 単純に同じノイズが大量に通知されることだってあるだろう。

機能追加をストップしてノイズ除去のためのプロジェクトを実施することは根本的な解決策にならない。 1度除去しても終わりではないからだ。 そのため、継続的に対応し続けることが望ましい。 したがって、予防・解消のプラクティスは、開発フローの中に組み込まれることになる。

3. 時間経過と共に修正が困難になる

どれが必要な通知でどれが除去すべきノイズか、切り分けが対処の第一歩だ。 システムが稼働を始めたばかりでアラートの種類も少なければ、専任者が1週間で切り分けできるだろう。

しかし、改修しないままだと悲惨だ。時間経過と共に大量・多様なアラート通知が飛ぶようになる。 仕様やコードベースは複雑になっており、特定パターンのアラートを1つ再現するために1週間掛かる可能性もある。

成功するようなプロダクトは特に、ビジネス・チーム・システムが急激に拡大・複雑化しうる。 借金が雪だるま式に積み上がる前に、なるべく早い段階から予防・返済していくことが望ましい。 継続的な予防・返済を仕組み化できずにレガシー化した場合は、機能追加を停止して書き直すことになる。

技術的負債をいかに予防するか

上記の特徴を踏まえると、なるべく早い段階から開発フロー内に予防のプラクティスを組み込むことになる。

スクラムセレモニー

スクラム開発を行う。

メンバがT字型人材の集まりで、誰もがチケットを上から(優先順位の通りに)取れること。 少なくとも、透明性・検査・適応の原則を理解し、セレモニーを通して、理想に近付くための継続的改善ができていること。

すると、以下のような予防が可能となる。

  • ドメイン言語が確立しやすくなる。
    • AさんとBさんが別々の言葉を使っていたら議論が混乱するからだ。
    • 辞書やWikiを作ることが悪いとは言わないが、定期的なセレモニーで言葉を交わすことで定着しやすくなる。
  • 筋の悪い設計を回避しやすくなる。
    • スプリントプランニング第2部にて、全員で設計を実施するからだ。
    • 良い設計を維持するための観点や価値観がチーム全体に浸透する。
    • 1人が設計して、後から他の人がレビューする場合に比べて、前工程で品質を作り込める。後工程になると手戻りしにくいので、前工程での品質担保が重要。
  • 早すぎる最適化を防ぐことができる。
    • 要件や作業チケットを最小限に分割することで、YAGNIの原則に従うからだ。
    • Pull Requestとチケットが連動していれば、余計な実装は入り込まない。
    • 計画外の実装を入れ込んだ場合、施策効果やベロシティの計測で槍玉に挙げられる可能性がある。矯正されやすい。
  • コードの統合リスクを抑制できる。
    • トーリー分割によって継続的インテグレーションが可能となるからだ。
    • マージ待ちの在庫を積み上げないようにすれば、コンフリクト解消の手間を減らし、予期せぬ影響を最小化できる。
    • 段階リリースや機能トグルなど、利用者から変更が見えない場合でも、負債予防の観点では有益となる。
  • インフラのコード化やCI/CD基盤の整備をアクション化しやすい。
    • インフラの設定が可視化されにくい・スケーラブルではない場合、あるいはリリース作業に時間が掛かる場合、属人性排除やベロシティ安定化のブロッカーとして問題提起することができる。

なお、チームの技術スキルが未熟な場合は、負債を蓄積せざるをえない。

中途半端にアーキテクトを引っ張ってきて、チームが理解できない設計をしても、その設計自体が負債となる可能性がある。 チームへの装着まで徹底できる人がいると理想だが、そうでなければ可能な範囲でベストを尽くして、あとは前に進むしかない。

技術的負債とは別に、個人学習、勉強会、ペアプロ、負債解消、問い合わせ対応、障害対応など、スキル向上の機会を活かすことが大切。

静的解析

静的解析ツール(RubocopやSonarQube)で自動的に指摘が入るようにする。 コーディング規約を見ながらの目視レビューは現実的ではない。

  • 指摘項目を0件にする。
    • 一般的に非推奨とされるコーディングを防ぐため。
    • レガシーシステムの場合は、悪化させないことを目標にする。いきなり0件は無理なので徐々に改善。
  • 複雑度や負債スコアを悪化させないようにする。
    • 保守性の低いコーディングを防ぐため。
    • クラス・メソッドを適切に分離・結合することになる。結果としてKISSの原則が保たれる。
    • まだ若いシステムの場合は悪化せざるを得ないので、閾値を超えないか・推移が緩やか(異常値でない)かを確認する。
  • テストカバレッジを悪化させないようにする。
    • 自動テストのメリットを享受し続けるため。

悪化させない≒(ぴったり同じスコアにはならないはずなので)僅かに改善となる。 結果としてボーイスカウトの原則が保たれる。

内容によってはコミットやプルリクを分割することになる。 チケットはリファクタリング(前)→主改修→リファクタリング(後)として型化できる。

  • 最初のリファクタリングでは既存の非推奨な設計・実装を修正する。
    • そうしないとテストコードが書けない場合。
    • 既存構成に流されて低品質となる場合。
    • まだレガシー化していないようであれば不要。
  • 後のリファクタリングでは適切な分離・結合を実現する。
    • コーディング時に気付いた問題があれば、早期にその発見をコードに反映する。
    • 事前に内部設計まで見通せていれば不要。ホワイトボードコーディングが活発なら最小化できる。

なお、静的解析ツールを自動反映するに当たって

  • ローカルのコミット時検査が理想だが、設定が難しければプルリクで最終担保することになる。
  • デフォルトのルールだと過不足があることが多いので、必要に応じてカスタマイズする。

また、Lint系ツールやコードフォーマッターも同様に運用に乗せるのが望ましい。

自動テスト

外部品質・内部品質を維持し続けるためにテストコードを書く。 なお、機能追加に伴う動作確認・手動検査(特に探索的テスト)は技術的負債とは別トピックなので言及しない。

内部品質

単体テストRSpecJUnitを想定。

Pull Requestごとに担保する。 テストしやすいコードとなるので内部品質が担保される。

静的解析でのカバレッジ担保と合わせてKISSの原則が保たれる。 コードレビューを待たずして、実装者自身が自工程で品質を担保するようになる。

実装・テストを細かく刻み慣れることで、スプリント計画会議でのチケット分割も細かくなる。 1つの案件に全員で群がることができるまでチケットを小さく分割すると、互いの作業内容が見えるようになり、相互レビューの精度が上がる。 また、小さなバッチサイズでの一個流しが実現できると、リーン原則に基づくリードタイム・品質の改善に繋がる。

外部品質(機能)

E2Eテスト。SeleniumやAppiumを想定。

リリースごとに担保する。理想は毎晩回って毎朝チェックできること。 重要機能が正常に動作することを担保する。 デグレ観点。その時々のリリースの改修内容とは無関係で毎回実施するテスト。

主要導線や重要機能、なおかつ主要ケースがメンテナンス対象となる。 相対的に重要度が低い機能、レアケースでの挙動、動作保証外環境は対応しない。 テストコードの保守コストが過剰になるため。

コストを掛けて守るべき品質とは何か、担保すべき重要機能とは何か。 関係者全員がサービスレベルの認識を揃えていることが前提となる。 SLAの設計・運用プラクティスは別のエントリーにまとめる。

とはいえ(特にネイティブアプリの)E2Eテストは技術的にまだまだ難しいところもあるように感じている。

外部品質(脆弱性

セキュリティテスト。OWASP ZAPを想定。

リリースごとに担保する。理想は毎晩回って毎朝チェックできること。 SLAを担保する。脆弱性はベストエフォートで0件を目指すことになるはず。

まだ私自身がセキュリティテストのツールを使いこなせていないので、見通せるようになったら追記する。

外部品質(パフォーマンス)

負荷テスト。Gatlingを想定。

定期的に実施する。理想は毎週末に回って週明けにチェックできること。 SLAを担保する。キャパシティが中間指標、レスポンスタイムが最終指標となる。

アクセス増加のシナリオを元にテストを設計することになる。

  • 過去のピーク時期のアクセス傾向に基づく今後のピーク予想。
  • 今後の事業計画を踏まえての増加。
  • ニュースに取り上げられたりバズったときの増加。

価格体系によってはサーバ費用が跳ね上がるので、負荷試験の実施頻度は財務計画とのご相談。 週次が難しくても月次。それがダメなら、せめて四半期に一度は実行したいところ。

技術的負債をいかに返済するか

予防をしても多少の負債は溜まる。小さく早く返済する仕組みを作る。

課題を一覧管理する

プロダクトバックログにアイデアを起票するように、技術的負債のトピックを起票して管理する。 所与のリソース内で継続的に返済し続けるためにはスクラムのような進め方が望ましい。

ポートフォリオを組む

専任の担当者をつけるか、タイムボックス内の一定時間でチーム全員が対応するか。

担当者をつけてしまうと、担当者以外のメンバの学習機会が奪われることになる。 負債解消を通して、予防動機と知識が身につく。同じような負債を生み出さないように注意できる。 それでも専任者を設けるなら、定期的に当番をシャッフルするのが良いかもしれない。 理想はチーム全員だ。各々が学習した上でスプリント計画会議に臨むことで、観点を洗い出しやすくなる。

チーム全員で一定時間を設ける場合、週X時間ではなく、特定の曜日・時間で固定する。 結局いつやるかを決めないと、他の作業に追われてスキップしがちになってしまうため。 「毎週金曜日の午後3時間は技術的負債を返済する」といった形で設けると話が早い。 差し込み業務でその時間に着手できない時は、レトロスペクティブで解決策を話し合う。

優先順位を付ける

ビジネス効果を明示できなくても、何かしら優先順位を付けて上から着手するのが正攻法。 「リードタイムや品質への悪影響」が問題なのだから、その影響度合いを比較する。 大中小かフィボナッチ数列で相対比較すれば十分だ。改修の規模感も見積もっておきたい。

とはいえ、この方法には限界がある。 そもそも日々のリファクタリングで解決できないような課題が残るので事前に見通しを立てにくい。 また、プロダクトオーナーのように優先順位を最終判断するロールが必要となる。

  • 1人のPOが持てるストーリーは50個までと言われている。が、技術課題リストはシステムの複雑化に伴って50個を超えるだろう。
  • PO配下にエリアPOを設けるか。定期的に課題リストをリセットするか。意思のあるメンバがPOに働き掛けて特定課題の優先順位を上げてもらうか。いずれにせよ管理コストが過剰に生じうる。
  • システムの複雑化に伴ってMicroservicesになるのが理想ではある。チームや課題リストも一緒に分割されるのであれば。ただ、リストが50個を超えるのが先か、リアーキテクティングすべき規模になるのが先か、やってみないと分からない。

もしくは……

もしくは、各チームメンバが心のままにチケットを選ぶという手もある。各々がやるべきだと判断したものを選ぶ。 日々の開発・運用の中で負に感じているもの、成果を出しやすいものが優先的に選ばれることを期待している。 少なくとも「リードタイムや品質への悪影響」を誰よりも肌で感じるのは、管理者ではなくチームメンバ1人1人のはずだ。

管理コストをゼロにして、価値の創出にエネルギーを費やすのが理想のあり方。 何よりも「自分の意思で選び、自分の手で改善する」成功体験の獲得こそが、技術的負債の予防と返済を継続するための、最高のプラクティスだと思う。

最後に

本エントリーは、某所でまとめた箇条書き草案に、殴り書きの解説を加えたものです。 1年後には、草案から大きく離れて、全く違うプラクティスに辿り着いているかもしれません。 この手のトピックはチームごとに試行錯誤しながら道筋を作っていくものだと思っています。 機会があれば、異論反論を含めて、ぜひ他の方の事例や考えを伺ってみたいです。

長くなってしまいましたが、最後まで読んでいただき、本当にありがとうございます。

リーン開発の本質

リーン開発の本質

各ツールのチケットを全部JIRAにぶっこむスクリプトを作りました

概要

Confluence、Github、JIRA、Redmineに散在しているTodoチケットを1つ残らずJIRAにインポートするスクリプトを作りました。 リポジトリhttps://github.com/yuzutas0/AnyToJira

f:id:yuzutas0:20170412211034p:plain

もくじ

  • 解決したい課題
  • 経緯・背景
  • やったこと
  • 構成
  • コード(一部抜粋)
  • 感想・学び

解決したい課題

某開発チームにてTodoを一元管理できていなかったことが課題となります。

ビジネス観点でやりたいことは施策案件として一覧化されていたのですが、バグ・技術課題については、 過去の開発・運用担当者がそれぞれのツールに起票し、申し送り事項として放置されていました。

経緯・背景

引き継ぎ前の各部隊で使っているツールが異なっていました。

  • 前の製造チーム: Confluenceの表に申し送り事項をメモ。
  • 前の検品チーム: Redmineに不具合を起票。未解決課題はそのまま申し送りに。
  • 前の運用チーム: GithubのIssueに不具合やTodoを起票。未解決課題はそのまま申し送りに。
  • 前の保守チーム: JIRAのチケットとして申し送り事項を起票。

職能別組織として考えると、知識を一元管理するのは自然なので、やむをえないと思います。 分野によって蓄積すべき情報・項目が違うので利用ツールは分かれるでしょう。まさに以下の記事が良い例かと。

tech.mercari.com

ただ、最初は良いのですが、知識の成熟やコモディティ化に伴って、リードタイム短縮を志向した職能横断の動きに変わることがあります。 Dev/Opsを一体化する際に申し送り事項が散在していると、可視化・運用の妨げになってしまいます。 (最初から一元管理できているのが理想ですが実際はなかなか難しい!)

やったこと

申し送り事項をスクリプトで全部JIRAにぶっこみました。

取得元 送り先 データ取得方法
旧JIRA 新JIRA WebAPIへのリクエスト(専用ライブラリ利用)
Confluence 新JIRA 擬似ログイン → 画面情報をスクレイピング
Github 新JIRA WebAPIへのリクエスト(専用ライブラリ利用)
Redmine 新JIRA WebAPIへのリクエスト

構成

シンプルにしています。

  • main : 処理全体を記述したもの
  • lib : main からコールされる
    • 各ツールからのデータ取得処理( main とのI/Oは全て共通)
    • JIRAへの送信処理

f:id:yuzutas0:20170412211049p:plain

コード(一部抜粋)

JIRAに送るところ

require 'jira-ruby'

class JiraSender
  def initialize
    @client = JIRA::Client.new(JIRA_COMMON.options)
    @project_id = @client.Project.find(ENV['JIRA_PROJECT_NAME']).id
  end

  def send_jira(summary, description)
    issue = @client.Issue.build
    response = issue.save(
      fields: {
        summary: summary,
        description: DESC_PREFIX + description
      }.merge(fields)
    )
    puts response unless response
  end
end

JIRAから取得するところ

require 'jira-ruby'

class JiraReceiver
  URL_PREFIX = "#{ENV['JIRA_HOST']}#{ENV['JIRA_CONTEXT_PATH']}/browse/"

  class << self
    def issues
      client = JIRA::Client.new(JIRA_COMMON.options)
      issues = {}
      client.Issue.jql(ENV['JIRA_JQL']).each do |issue|
        issues[issue.summary] = URL_PREFIX + issue.key
      end
      issues
    end
  end
end

Confluenceから取得するところ

require 'mechanize'
require 'open-uri'
require 'kconv'
require 'oauth'

class Confluence
  CONFLUENCE_XPATH_PREFIX = '//*[@id="main-content"]/div/table/tbody/tr['
  CONFLUENCE_XPATH_SUFFIX = "]/td[#{ENV['CONFLUENCE_TABLE_COLUMN']}]"
  CONFLUENCE_PAGE = "#{ENV['CONFLUENCE_URL']}pages/viewpage.action?pageId=#{ENV['CONFLUENCE_PAGE']}"

  class << self
    def issues
      issues = {}
      titles.each { |title| issues[title] = CONFLUENCE::CONFLUENCE_PAGE }
      issues
    end

    private

    def titles
      result, agent = io_variables
      agent.get(ENV['CONFLUENCE_URL'] + 'login.action') do |page|
        login(page)
        contents, start_row, end_row = crawl_variables(agent)
        start_row.upto(end_row) do |index|
          scraped = scrape(contents, index)
          result << scraped unless scraped.empty?
        end
      end
      result.compact.reject(&:empty?)
    end

    def login(page)
      page.form_with(name: 'loginform') do |form|
        form.os_username = ENV['JIRA_MAILADDRESS']
        form.os_password = ENV['JIRA_PASSWORD']
      end.submit
    end

    def scrape(contents = '', index = 0)
      confluence_xpath = CONFLUENCE_XPATH_PREFIX + index.to_s + CONFLUENCE_XPATH_SUFFIX
      return '' if contents.xpath(confluence_xpath).empty?
      unless contents.xpath(confluence_xpath).xpath('.//li').empty?
        return scrape_content_with_list(contents, confluence_xpath)
      end
      contents.xpath(confluence_xpath).text.to_s
    end
  end
end

Githubから取得するところ

require 'octokit'

class Github
  URL = 'https://github.com/'
  REPOSITORIES = ENV['GITHUB_REPOSITORIES'].split(',')

  class << self
    def issues
      issues = {}
      client = Octokit::Client.new(access_token: ENV['GITHUB_TOKEN'])
      REPOSITORIES.each do |repo|
        responses(client, repo).each do |issue|
          issues[issue.title] = issue.html_url
        end
      end
      issues
    end

    private

    def responses(client, repository)
      responses, next_response = call_api(client, repository)
      paging(responses, next_response)
      pr_prefix = "#{URL}#{ENV['GITHUB_ORGANIZATION']}/#{repository}/pull/"
      responses.reject do |response|
        response.html_url.start_with? pr_prefix
      end
    end

    def call_api(client, repository)
      responses = []
      repository_path = "#{ENV['GITHUB_ORGANIZATION']}/#{repository}"
      responses.concat client.issues(repository_path)
      next_response = client.last_response.rels[:next]
      [responses, next_response]
    end
  end
end

Redmineから取得するところ

require 'open-uri'
require 'kconv'
require 'json'

class Redmine
  URL_PREFIX = "#{ENV['REDMINE_URL']}/projects/#{ENV['REDMINE_PROJECT_NAME']}/issues.json?key=#{ENV['REDMINE_API_KEY']}&query_id=#{ENV['REDMINE_QUERY_ID']}"
  LIMIT_SIZE = 100

  class << self
    def issues
      pages = request['total_count'].to_i / LIMIT_SIZE + 1
      issues = {}
      pages.times do |page|
        this_page = page + 1
        items = request(LIMIT_SIZE, this_page)['issues']
        items.each { |issue| issues[issue['subject']] = url_of(issue) }
      end
      issues
    end

    private

    def request(limit = 1, page = 1)
      request = "#{URL_PREFIX}&limit=#{limit}&page=#{page}"
      response = open(request, &:read).toutf8
      JSON.parse(response)
    end

    def url_of(issue)
      "#{ENV['REDMINE_URL']}issues/#{issue['id']}"
    end
  end
end

感想・学び

  • 散在していようが、データさえあれば後で一元化できます。後に引き継ぐ人のために、少しでも気になる課題はどこかに書き出すことが大事だと思いました。
  • 利用者の多いグループウェアなら、データをぶっこぬくライブラリやサンプルコードを誰かしら公開しています。ツール採用に悩んだときは、とりあえず有名どころを使えば引き継ぎ観点では問題なさそうです。
  • この手の情報管理を考えると、プロマネディレクション業務でも、プログラミングスキルがあるに越したことはないですね。

そんな感じです。最後まで読んでいただき本当にありがとうございます。

ゴミ出し通知サービスを作ったけど供養します

概要

「明日はゴミ出しの日だよ」を教えてくれる「AsGomy」(アスゴミー)なるサービスを作ったのですが供養します。 かなり昔の話です。

f:id:yuzutas0:20170330004630p:plain

もくじ

  1. どんなサービスか
  2. 行動設計
  3. なぜ作ったのか
  4. どうやって作ったのか
  5. なぜ供養するのか
  6. 思ったこと

1. どんなサービスか

市区町村と帰宅時間を入力すると、ゴミ出し前日にリマインドメールを送るサービスです。 ユーザーのフィードバックを受けて配信時間を微妙に学習します。

名前の由来はそのままで、AsGomy=明日ゴミです。 HileSearchと同じ語感決めとなります。

yuzutas0.hatenablog.com

2. 行動設計

前日にゴミをまとめて、当日の朝は出すだけの状況を想定しています。

  • 当日の朝にゴミをまとめようとしても急いでいて間に合わない!
  • 玄関にゴミ袋が置いてあれば、あとはそれを持って集積所に行くだけ!何とかなる!

なので、当日ではなく前日に準備することがポイントとなります。 また、帰宅直前を狙って配信するように設計しています。

  • 帰宅より大幅に前だと忘れてしまうから
  • 帰宅して一息ついてしまうとゴミをまとめる気力がないから

帰路→メールで通知→思い出す「明日はゴミ出しの日だ!」→帰宅→すぐにゴミをまとめる→玄関に置く→ほっと一息、という行動設計です。

3. なぜ作ったのか

自分の中で盛り上がっていた人工無脳・電脳秘書の企画のためです。

  • 日常生活の特定課題を解決するエキスパートシステムまがいのものを作ろうとした
  • その時に困っていたことが生ゴミの捨て忘れだったので試しに作ってみた

yuzutas0.hatenablog.com

また、当時はシステム開発の勉強を始めたばかりだったので、学習目的も兼ねていました。

  • 課題設定を絞り込んだ上で、ユーザーの行動に影響を与える設計にした
  • 単純なCRUD処理だけでなく、クローリング、簡易な学習機能、メール配信を試した

4. どうやって作ったのか

システム構成

以下の通りです。記憶が正しければ。

開発工程

理解できていなかったので、心のままに作っていました。

  • 概念検証
    • インタビュー:問題意識の共感者がどれくらいいるのか、共感者にはどのような特徴があるのかを確認(→顧客・課題の検証)
    • プロトタイプ:Googleカレンダーでゴミ出し前日の夜にリマインドするよう設定して効果を測定(→解決策の検証)
  • 要件定義
    • 概念検証の結果をもとに、カスタマーの行動設計を詰めて、最終的にはジャーニーマップに沿う形でデザイン
  • 設計
    • まともにやっていなかった
  • 実装
    • クローラー:大量にエラーを吐きまくる傍らでバグシューティングして、首都圏のゴミ日程データ(6,000地域・20万件)を引っ張ってきた
    • アプリ:ユーザー情報のCRUDをお手軽実装
    • ジョブ:メール配信・学習のジョブをお手軽実装
  • テスト
    • 軽く正常系を通した
    • そのあとは友人・知人を強制召喚して利用&バグ報告してもらった

上流工程に比べて下流工程が貧弱すぎますね。 この反省を受けてEnokilogでは少し真面目に意識するようになります。

yuzutas0.hatenablog.com

5. なぜ供養するのか

良くも悪くも初学用サイトにしかならなかったからです。 色々と学ぶべきことはありましたが、役目は果たしたのかなと思っています。

ただ、アイデア自体は別に悪くないのではないかと今でも思っています。 今の実力なら丸3日集中すれば作れるはずなので、ハッカソンか何かで機会があればぜひ作り直したいです。

6. 思ったこと

イデアは悪くないのにシステムがダメなせいで崩れる企画は、今回に限らず山ほどあるのかなと思います。 二度とそういった残念な目に合わないように、知識を付けようと思った瞬間でもありました。

yuzutas0.hatenablog.com

当時と比べると、知識はそこそこ付いてきたと思うので、もっと色々な企画を実現したいなぁと改めて思います。 大きなこと・多くのことを実現するために、技術スキル以上に巻き込み力を発揮していかねばと思いつつあります。

長くなってしまいましたが最後まで読んでいただき本当にありがとうございます。 (※手元にjarファイルが残っているだけなので今回はGithubリポジトリなしです)

ひたすら肯定してくれるチャットボットを作ったけど供養します

概要

何を言っても頷いてくれる人工無脳「Unazukin-Chan」(ウナズキンちゃん)を作ったのですが供養します。だいぶ昔の話です。

f:id:yuzutas0:20170320234822p:plain

※注意:この記事は個人的な糞アプリの供養であって、データサイエンティストやAI開発者に向けたものではありません。

もくじ

  1. どんなサービスか
  2. なぜ作ったのか
  3. どうやって作ったのか
  4. なぜ供養するのか
  5. 思ったこと

1. どんなサービスか

  • LINEのようなチャットUIでメッセージを投稿できます。
  • 「なるほど」「すごいな」「悪いのは君じゃない」みたいな感じで励ましてくれます。

iOSのネイティブアプリですが、IPAファイルを知り合いにばらまいただけです。

2. なぜ作ったのか

  1. 誰かに頷いてほしかったからです。
  2. 某所でチャット機能の開発を検討していたのでプロトタイピングがてら作ってみました。
    • 「吹き出し」というUI自体が興味深いと感じました。
    • スマートデバイスの普及に応じて広く親しまれるようになったように思えたからです。
  3. 人工知能ではなく)人工無脳に関心があったので試しに動くものを作ってみたかったからです。
    • 開発時はちょうど第3次AIブームが加熱する手前で、Siriのようにプラットフォーマーにロックするための客寄せパンダが主流でした。
    • より窓口の広いデジタルパートナー(サマーウォーズロックマンEXEみたいな世界)を作ったらカスタマー行動の入口を抑えられるのではという仮説がありました。

3. どうやって作ったのか

以下のサイトで掲載されていたサンプルコードを参考にしました。

[iOS] JSQMessagesViewController でチャットアプリを実装する | Developers.IO

上記の内容をコピーしてペーストして、対話相手が自動返答するように書き換えました。 そんだけ!

- (void)viewDidLoad {
    [super viewDidLoad];
    // チャットUI生成
    [self initNewChat];
    // メッセージデータの配列を初期化
    self.messages = [NSMutableArray array];
    [self makeDictionary];
    // 擬似的に自動でメッセージを受信
    [self startChat];
}

// Sendボタンが押下されたときに呼ばれる
- (void)didPressSendButton:(UIButton *)button
           withMessageText:(NSString *)text
                  senderId:(NSString *)senderId
         senderDisplayName:(NSString *)senderDisplayName
                      date:(NSDate *)date
{
    // 新しいメッセージデータを追加する
    JSQMessage *message = [JSQMessage messageWithSenderId:senderId
                                              displayName:senderDisplayName
                                                     text:text];
    [self.messages addObject:message];
    // メッセージの送信処理を完了する (画面上にメッセージが表示される)
    [self finishSendingMessageAnimated:YES];
    // 擬似的に自動でメッセージを受信
    [self response];
}

// 初回挨拶
- (void)startChat
{
    [NSThread sleepForTimeInterval:1.0];
    self.content = @"やぁ\n何かありましたか?\nよかったら聞かせてください";
    [self receiveAutoMessage];
}

// 肯定
- (void)response
{
    [NSThread sleepForTimeInterval:1.0];
    self.content = self.getDictionary;
    [self receiveAutoMessage];
}

// 辞書作成
-(void)makeDictionary
{
    NSMutableArray *words = [NSMutableArray array];
    [words addObject:@"うんうん"];
    [words addObject:@"わかるわかる"];
    [words addObject:@"わかるよ"];
    [words addObject:@"そっかー"];
    [words addObject:@"そうなんだ"];
    [words addObject:@"なるほど"];
    [words addObject:@"すごいね"];
    [words addObject:@"きみは悪くない"];
    // [words addObject:@"お前がそう思うんならそうなんだろう\nお前ん中ではな"];
    self.terms = words;
}

// 辞書設定
- (NSString *)getDictionary
{
    int n = arc4random() % self.terms.count;
    return [self.terms objectAtIndex:(NSUInteger)n];
}

このときがiOS開発(これを開発とは呼ばないか)デビューだったのでセットアップに手間取りましたが1日で完成しました。

4. なぜ供養するのか

適当な顔アイコンに頷かれても虚しいだけだったからです。

5. 思ったこと

  • UIの重要性!!!
  • 糞アプリは作った直後は面白いですけど、保守する気にならないという問題。
    • 糞アプリだからこそ本気で保守し続けたら、無駄なこだわりがかえって面白いかも、とは一瞬思いました。
    • 辞書を充実させたり、いい感じの応答をできるようにしたり、きちんとした機械学習を挟んだり。
  • また、個人開発でネイティブアプリの保守コストを張るのは辛そうだと思うようになりました。
    • (この程度の規模ならどうとでもなりますが)AppleGoogleの進化に追従しなければいけないので。
    • そこで発想をWEBサービスに絞ってみる→ToCではなくToBの製品が適しているのではと考える→グループウェアに関心を持つようになる→自作Qiita:Teamクローンに繋がります。

yuzutas0.hatenablog.com

でもやっぱり、こうして振り返ると楽しかったです。 面白おかしくやっていきたいですなぁ。

最後に

  • 長くなってしまいましたが最後まで読んでいただき本当にありがとうございます。
  • 一応Githubリポジトリは公開しておきます。最新のXcodeでビルドできるとは思えないですが……。

データ可視化なWEBサービスを作ったけど供養します

概要

MashBoard(マッシュボード)というサービスを半年ほど動かしていたのですが供養します。

f:id:yuzutas0:20170315214609p:plain

もくじ

  1. どんなサービスか
  2. なぜ作ったのか
  3. どうやって作ったのか
  4. なぜ供養するのか
  5. 思ったこと

1. どんなサービスか

統計データをヒートマップで表示するサービスです。

名前の由来は忘れました。ダッシュボード(DashBoard)と何か(Mではじまる言葉)を掛けていたのは覚えているのですが。

1-1. 対象・用途

かなりニッチで

  • IT勉強会などのイベントが対象
  • どの曜日・時間帯に
  • どのくらい多くのイベントが開催されるか

を描画するだけです。

この時点で廃棄まっしぐらな気がしますね。

1-2. ささやかな機能

フリーワードでの絞り込み!

例えば、"iOS"の分野で絞り込むと「iOSの勉強会はこの曜日のこの時間帯が多い」と分かるので

  • iOS開発チームに対してはその曜日・時間に予定を入れないようにしよう
  • 有志でiOS勉強会を開くときはバッティングしないようにしよう

といったデータドリブンな意思決定ができるようになります。

2. なぜ作ったのか

某所でエンジニア組織の運営に携わることになり、会議体を設計する過程で作りました。

2-1. 経緯

メンバー1人1人にヒアリングしたところ「週次ミーティングが外部の定期勉強会とバッティングするので、時間を早めるか曜日を変えてほしい」なる意見がちらほらと聞こえました。

*その週次ミーティングは、プロダクトや担当領域が異なる複数の開発チームが横断的に情報交換する場で、偉い人のスケジュールに合わせて水曜日の夜に実施していたのです。

メンバーの意見を聞いた私の最初の感想は「勉強会の開催スケジュールが変わったらどうするねん」でした。 バラバラなチームが集まるミーティングなので、スケジュール調整を何度もやり直すのは面倒だからです。

とはいえ、否定から入るのは良くないということで、ひとまずデータを見ることにしました。 また、仮にスケジュールを変更するにしてもビジュアルで示した方が関係者を説得しやすいと考え、可視化ツールを作ることにしました。

2-2. 結果

  • 本当に水曜の夜は勉強会が多かった!(データを見た事実)
  • 某エンジニアいわく「定時帰宅の会社が多いからでは」(定性的な考察)

これらを添えて、「外部勉強会とかぶりやすいスケジュールでミーティングを設定するのはエンジニア組織としていかがなものか」と偉い人に進言。

無事に別の曜日・時間に変更することができました。

めでたしめでたし。

3. どうやって作ったのか

3-1. クローラージョブ

以前作ったものを流用しており、ATND・connpass・DoorKeeper・Zusaarが取得対象となります。

交流会やパーティーを除外するために「勉強会」でフリーワード検索したところ(ミーティングの設計をしていた当時では)4ヶ月分・約540件の有効データを得られました。

yuzutas0.hatenablog.com

3-2. オンラインアプリ

フロントエンドはReactを用いてペラ1の画面を描画しており、バックエンドのRails製WebAPIをコールしています。

ちょうどReactが話題になり始めた時期だったので、お試しがてらチュートリアルのサンプルコードを見ながら実装しました。

  • APIでHashを受け渡す
    • Key:それぞれの曜日・時間
    • Value:イベント開催数が上位何パーセントなのか
  • JSで表組みを描いて、イベント開催数に応じてclassを指定する
  • CSSが各classに応じた着色をすることで、結果としてヒートマップが描ける

4. なぜ供養するのか

Jupyter Notebook があるから
          ↘
         Jupyter Notebook があるから
          ↙
Jupyter Notebook があるから
          ↘
         Jupyter Notebook があるから
          ↙
_人人人人人人人人人人人人人人人_
> Jupyter Notebook があるから <
 ̄^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^ ̄

わざわざWebサービスにする必要がない、という一言に尽きます。

データ分析をするだけなら、既存の優秀なツールを活用するのがベストだと思います。 というかJupyter Notebookが強すぎる。

分析結果をもとに何らかのアクションを自動化する、といったプラスアルファがあって初めてWebサービスとして輝くのかなと。 それこそ、会議の自動スケジューリングのような価値提供ができれば魅力的……かも?

5. 思ったこと

5-1. 反省点

説得材料として自分で使ってみて、分かりやすいビジュアルとは言えませんでした。結局は雰囲気でゴリ押しました。いわゆる “Data Visualization” なる分野ですが、単純に面白いです。プレゼン・分析のスキルとしてある程度確立されているはずなので、勘所を抑えられるようになりたいです。

5-2. 良かった点

  • 組織運営の課題を技術で解くことの面白さを感じました。自分の組織だけではなく、世界中の組織の抱える課題を解決できるようになるとビジネス化できるのかなぁとワクワクします。
  • また、フロントエンドの技術検証として、jQueryでやりたきことは十分できるから「最新ツールに振り回されなくても良いのでは説」が私の中で浮上し始めます。

これらの学びが自作Qiita:Teamクローンに繋がります。

yuzutas0.hatenablog.com

最後に

  • 長くなってしまいましたが最後まで読んでいただき本当にありがとうございます。
  • 一応Githubリポジトリは公開しておきます。

IT系勉強会まとめ:Webサービスを作ったけど供養します

概要

Monomy(モノミー)というサービスを半年ほど動かしていたのですが供養します。

f:id:yuzutas0:20170307234328j:plain

もくじ

  1. どんなサービスか
  2. どうやって作ったのか
  3. なぜ作ったのか
  4. なぜ供養するのか
  5. 思ったこと

1. どんなサービスか

1-1. ざっくり説明

勉強会・イベントサイトを横断で検索できるサービスです。

APIを公開している下記4サイトが対象となります。

  • ATND
  • connpass
  • DoorKeeper
  • Zusaar

1-2. 画面イメージ

  • 検索画面
  • カード型の一覧表示画面

f:id:yuzutas0:20170307234328j:plain

  • 詳細画面
  • サイドバーにて類似レコメンド

f:id:yuzutas0:20170307234348j:plain

  • メモ代わりのメール送信

f:id:yuzutas0:20170307234409j:plain

1-3. 由来・コンセプト

名前の由来は「物見遊山」(ものみゆさん)です。和風シリーズ。

移動に制限のあった江戸時代の人は、お参りに行くという名目で、外の世界を旅行したそうです。 同じように、勉強会・イベントでは、スキルアップだけでなく、視野を広げたり、新しい人と出会う楽しみもあります。 そういう前向きな気持ちで一歩踏み出せるような意味を持たせる名前にしました。

このようなコンセプトで作ったサービスとなります。 実際にいくつか勉強会を主催・参加してみて、もっと自分の世界を広げたいなぁと思いました。

2. どうやって作ったのか

過去の個人開発サービスと同じような流れなので細部は省略します。

yuzutas0.hatenablog.com

2-1. システム構成

構成図・技術要素は以下の通りです。

f:id:yuzutas0:20170307234556j:plain

  • APFW:Rails
  • DB:MySQL
  • 検索エンジン:Elasticsearch (provided by Compose)
  • メール配信:SendGrid
  • HW/NW/OS/MW:CloudFoundry (wrapped by Bluemix)

2-2. クローラージョブ

f:id:yuzutas0:20170307234617j:plain

WorkloadSchedulerにてcron相当の設定を行い、Railsバッチ処理を叩きます。 実際にはHTTPリクエスト経由となるのでRails側でAPIを用意しました。

Railsバッチからは各勉強会サイトのAPIをコールしてDB・検索エンジンに保存します。 APIコールで時系列を指定し、過去に同じページを保存していればクローリングを打ち切ります。

2-3. オンラインアプリ

f:id:yuzutas0:20170307234631j:plain

エンジニアがIT勉強会を探すためにサイトを訪問するとRailsレンダリング結果を返します。 Bluemix標準ランタイムだと最新バージョンに対応しないため、CloudFoundryの情報を漁ってカスタム設定にしました。

フリーワード検索と類似文書レコメンドではElasticsearch、それ以外ではMySQLからイベントを探します。 類似文書レコメンドは「More Like This」でお手軽実装となっています。 また、メール送信の要求を受けたらSendGridに送信リクエストを送ります。

2-4. その他開発環境

f:id:yuzutas0:20170307234647j:plain

せっかくのBluemixなのでサービス開発に使えそうなものをいくつか試しました。

  • Auto-Scaling
  • キャパシティ計測ダッシュボード
  • Todo管理
  • 脆弱性診断ツール
    • クローリングしたHTML・画像をそのまま出力している点を指摘されました!

2. なぜ作ったのか

2-1. PaaSの検証のため

サーバ構築・メンテナンスの手間を省くためにPaaSを使えないか検証したかったからです。 インフラ観点だけでなくアプリ観点で使い勝手を検証するためにサンプルアプリとして作成しました。

2-2. UIの学習のため

HileSearchというWEBサービスGigazineに掲載いただいた際、使い勝手の悪さをあちこちでご指摘いただきました。

yuzutas0.hatenablog.com

そこで、検索>一覧>詳細>アクション、という一般的なToC向けサイトの構成を学びたいと考えました。 自分で1度作ったあとに、世のサービスを見ると「こういう工夫が必要だったのか!」と気付けるだろう、という作戦です。

f:id:yuzutas0:20170307234429j:plain

ここでの学びが後述のQiita:Teamクローンに繋がります。

2-3. フィードバックサイクルのため

磨き込み・学習のためには、継続的にユーザーからのフィードバックを得ることが重要だと考えました。 IT勉強会というテーマを選んだのはそのためです。

  1. IT勉強会のためのサイトを作ったよ!という発表をする
  2. 勉強会に参加する人は「勉強会」自体にもある程度関心がある(はず) → 意見を出してもらえる(はず)
  3. 意見をもとに改修 → その旨を次の勉強会で発表する → 2に戻る

こういった正のサイクルを回しやすいのではないかと、という仮説です。

2-4. 本音

というのは半分言い訳で、実際には色々な勉強会に顔を出して、色々な人と話をしてみたかった! そのために話す題材が欲しかった!できたら一緒に盛り上がりたかった!それだけです!

4. なぜ供養するのか

4-1. 機能

勉強会でフィードバックを得るためには1つ目玉機能が欲しいと考えました。

  • Tinderのように好き嫌いを回答して機械学習してくれたら面白いやん!
  • だとしたらWebよりもiOS/Androidネイティブアプリの方が良いのではないか!

ここで機械学習スキルの壁に当たり、迷走もとい勉強することになります。

yuzutas0.hatenablog.com

4-2. 保守

Courseraを終えて見積もり・実装ができるようになると別の課題に思い至りました。 Web/iOS/Android/レコメンドと、システムを広げてしまうと、そのあとの保守が辛くなります。 ToB向けサービスで、Webに絞った方が磨き込みが容易ではないか、と考え始めるようになりました。

4-3. 頻度・速度

加えて、勉強会よりも業務の中でドッグフードできるグループウェアに魅力を覚えるようになります。 というのも、同僚からフィードバックを得るほうが圧倒的に学習の頻度が多いだろうと考えたからです。

この考えがQiita:Teamクローンの開発につながります。

yuzutas0.hatenablog.com

5. 思ったこと

供養する理由を文字に書き出した感想。

「まだ供養しなくてよいのでは?」

やる前から否定してばかりで格好悪い。

  • iOS,Android,レコメンドは実装してしまおう。
  • 保守が大変なら周囲を巻き込もう。協力したくなるような絵を描こう。
  • 勉強会に参加してフィードバックを貰おう。

というわけで、そのうち再開するかもです。 アイデアは捨てるより実現する方が楽しいよね。

ここまで書いてブログ投稿を取りやめようか迷いましたが、せっかくなので投稿します。

最後に

  • 長くなってしまいましたが最後まで読んでいただき本当にありがとうございます。
  • 一応Githubリポジトリは公開しておきます。いつかまたpushする日が来るかなぁ。