2016/12/16

ビジネスデータ発生間隔を統計的指標でチェックする

これは、NewRelic Advent Calendar 2016 の16日目の記事で、New Relic Insights で「想定外」に備えるセーフティネットを作るの続編になります。

ビジネスプロセス監視 — 外形監視、症状監視に続く第三の監視

ここでやろうとしていることは、外形監視、症状監視に続く三つ目の種類の監視です。外形監視、症状監視という言葉は下記の記事から取りました。

このあたりの用語はまだ整理されていないと思いますが、システム全体を監視する「外形監視」と個々のコンポーネントを監視する「症状監視」という分類は監視システム設計で最も重要な分類だと思います。

私は、監視システムには、この分類には入らない別の種類の監視が必要だと考えています。ここでは仮にそれを「ビジネスプロセス監視」と呼びたいと思います。

「外形監視」はトランザクションを受け付けられる状態にあることを保証します。しかし、厨房が準備完了でも、開店中のレストランにお客さんが一人もいなかったら、それは正常な状態とは言えません。つまり、システムが実際に動いているかどうかを監視することが必要ではないかと思うのです。

そこで、受注、売上、支払い、出荷といったビジネス上重要なトランザクションの発生間隔をチェックするような監視を行っています。つまり、一定時間以上、注文がなかったらアラートを上げるというような監視です。

具体的には、受注を部門別に見て次のような処理を定周期で行っています。

  1. その部門の過去のデータから、「期待されるデータ発生間隔」を計算する(詳細は後述)
  2. 最後に対象データが発生した時刻からの経過時間を得る
  3. 1と2の比率が一定以上になる状態が連続したらアラートを上げる

例えば、「期待されるデータ発生間隔」が1時間で比率を5倍に設定したとしたら、最後のデータ発生から5時間経過した時点で「来るはずのデータが来てないよ!」というアラートをあげます。

このエントリでは、これを数ヶ月やってみた感想と、今後工夫すべき点について書きたいと思います。

ビジネスプロセス監視の目的と効用

これには以下のような目的があります。

  1. サービスの相互作用から生じるトラブルをチェックする
  2. ビジネス上の問題や依存しているサービスの問題を把握する
  3. システム無事稼働しているという「安心感」を得る

今は、一つのアプリケーションが複数のサービスから構成され、API、イベント、コールバックなどの相互作用によって動作することが多くなっています。そして、通常「外形監視」の対象は、アプリケーションではなくて、サービスのURLになります。

そうすると、個々のサービスは正常に稼働しても、全体のアプリケーションとしては、正常に処理できない、それに気がつかない、という状況が発生しやすくなります。

例えば、バージョンアップに伴う内部的な構成変更が意図せず暗黙のAPIの動作の変更につながってしまうというケースです。あるいは、移行時の特殊な運用や急激な負荷の増大から処理のタイミングが違って、今まで経験してない処理パターンが発生するというようなケースです。

トラブルが実際に発生した後なら、そういう例外的なケースをテストケースや監視に含めることもできますが、事前に全てを見通すことは不可能です。「ビジネスプロセス監視」はこういう例外的な異常を検出できる可能性が高い監視方法です。

また、SEOの環境変更とか(ビジネス上の)上流、源流となるサービスの異常などの原因で、自社システムには問題がなくても、処理すべきデータがこない、お客様が自分のサイトまでたどり着けないというケースもあります。

これは、システム運用担当としては考える必要がないことかもしれませんが、自社のビジネスが動いてないという状況が無関係とは言えないと思います。

そして、一番大きい効果としては「安心感」があります。

そもそも、システム運用担当は、本当に気が休まらない仕事です。異常は休日でも夜間でも起こる時は容赦なく起こり、そこからは逃げることができません。

ですから、せめて何もない時はゆっくりしたいと思うのですが、今本当に「何もない」と言えるのか?ということです。

私は、会社から帰る前に必ず一度、この「ビジネスプロセス監視」の状態をチェックします。これがグリーンであるのを確認すると、「ああ、これで今日も終わった」という感じがします。また、外形監視や症状監視のアラートが上がった時も、最初にこれを見ます。そうすると、「とりあえずシステムは動いている」という確認が取れるので、落ち着いて問題に取り組むことができます。

「期待されるデータ発生間隔」を求める困難さ

さて、これをどのように行うかは前の記事に書きましたが、データ発生間隔の計算とその調整にはとても苦労しています。

まず、時間帯や休日の変動があって、これにセールなどのビジネス上のイベントによる変動が加わります。だから、単純に「5時間注文がない」とか「昨日(先週)とデータ件数が全然違う」というようなロジックにしてしまうと、誤検出が多くて意味のないアラートになってしまいます。

これに関しては、以下のように対策しています。

  • 「ビジネスプロセス監視」専用のSlackチャンネルを用意する
  • アラートまでの検出回数、連続検出回数などを多めにする
  • そもそも誤検出があっても気にしない

アラートの通知は、当社では Slack と Pagerduty を主に使っているのですが、こういう通常の通知からは切り離しました。そして、Slackに #minor-alert というチャンネルを作り、症状監視の軽微なワーニングと「ビジネスプロセス監視」のアラートをここに出すようにしました。

そうすると、ここは「普段は見ないけど問題が起きた時に見る」ためのチャンネルになります。

別の深刻な問題がある状態で、その原因や状態を探るために、ここに出ている過去数時間のアラートは状況の素早い把握のために非常に役に立ちます。また、前の章に書いたように、「ここに何もないことを確認して安心する」ためのチャンネルにもなります。

統計処理による発生間隔のチェック

とは言っても、誤検出は少ないにこしたことはありません。この精度を高めるための方法としては、次の三つが考えられます。

  1. アドホックにビジネスに対応するロジックを追加していく
  2. 統計処理的な考え方を導入する
  3. 機械学習を導入する

最近はやりのディープラーニングなどの機械学習は、このような要件に有効かもしれません。もし機会があれば試してみたいと思っていますが、当社では今のところ1が中心で、最近、2を部分的に取り入れました。

1は具体的には、このようにしました。

  • 過去の同じ時間帯のデータを比較対象にすることで、時間帯変動に対応する
  • 一週間前の同じ時間帯のデータを比較対象にすることで、曜日の変動に対応する
  • 1日前、2日前、1週間前、2週間前などのデータを集め、それらの最大値と比較することで、セールと休日に変動に対応する

これによって、常時大量の注文を受けている部門については、かなり正確に「期待されるデータ発生間隔」を計算できるようになりました。

しかし、データ量が少ない部門については、このような調整を行っても、どうしても「注文のある日」と「注文のない日」の差が大きく、誤検出が止まりませんでした。そこで、上記のアドホックなロジックでデータが集まらなかった場合には、この監視の対象外とするようにしました。

しかし、そのような例外的なビジネスの方が、処理ロジックにも例外が多く、そこだけで使う外部サービスなどもあります。「ビジネスプロセス監視」の趣旨からいうと、このようなデータ量が少ない部門に注目すべきです。

そこで、今回、ここに統計的な処理を加えてみました。

アドホックなロジックで「異常」と判定された場合に、問題のデータの発生間隔を偏差値に変換して、その偏差値が90以上だった場合にアラートを上げる、という処理方法に変換しました。

以下にそのコードの一部を引用します。


# http://web-salad.hateblo.jp/entry/2015/02/05/110557 から流用して一部修正
module ArrayExtensionStddev
  def sum
    reduce(:+)
  end

  def mean
    sum.to_f / size
  end

  def var
    m = mean
    reduce(0) { |a,b| a + (b - m) ** 2 } / (size - 1)
  end

  def stddev
    Math.sqrt(var)
  end

  def to_deviation_array
    sd = self.stddev
    m = self.mean
    map do |n|
      (n - m)*10.0/sd + 50
    end
  end

  def deviation_of(n)
    sd = self.stddev
    m = self.mean
    (n - m)*10.0/sd + 50
  end
end

class Array
  include ArrayExtensionStddev
end

class NewRelicApi
  def send_query(nrql)
    conn = Faraday.new(:url => API_ENDPOINT) do |faraday|
      faraday.adapter  Faraday.default_adapter  # make requests with Net::HTTP
    end

    response = conn.get do |req|
      req.url "query?nrql=#{nrql}"
      req.headers['Accept'] = 'application/json'
      req.headers['X-Query-Key'] = NEWRELIC_QUERY_KEY
      req.options.timeout = 60
    end

    JSON.parse(response.body)
  end

  # Queryの結果からデータ到着時間の配列を作成する。統計関数のない Query の形式にのみに対応
  def timestamp_array_from_result(r)
    return [] unless r["results"]
    r["results"].first["events"].map {|h| h["timestamp"]/1000 }
  end

  # 到着時間の配列から到着間隔の配列を作成
  def get_interval_array(a)
    a.each_cons(2).map do |a, b|
      a - b
    end
  end
end

# 中略
#   (アドホックなロジックで異常と判定された部門の情報をabnormal_sectionsに入れる)

$api = NewRelicApi.new

abnormal_sections.each do |sec, max, message|
  ia = [] # データの到着間隔を入れる配列
  # 少しづつ過去に遡って、十分な件数が集まるまで繰り返す
  [4,8,24,72,24*7*4].each do |hours_ago|
    nrql = %Q[SELECT * from Sales where section = '#{section}' limit 100 since #{hours_ago} hours ago ]
    r = $api.send_query(nrql)
    ta = $api.timestamp_array_from_result(r)
    ia = $api.get_interval_array(ta)
    break if ia.size > 90
  end

  # last_interval: 最終データ到着からの経過時間
  last_interval = Time.now.to_i -  max/1000.0
  # deviation_value: 過去約100件分の到着間隔に対する経過時間の偏差値
  deviation_value = ia.deviation_of(last_interval)
  if deviation_value > 90.0 # 偏差値90以上は異常と判定する
    status_of_abnormal_sections << "*#{message} deviation value:#{'%2.2f' % deviation_value}"
    error_level = [1, error_level].max
  else
    status_of_normal_sections << "#{message} deviation value:#{'%2.2f' % deviation_value}"
  end
end

この処理はまだ試行している段階ですが、データ量の少ない部門についても、これによってより適切なチェックができるようになったようです。

終わりに

幸か不幸か、ここ数ヶ月は大きなトラブルはなく、当社の「ビジネスプロセス監視」はまだ本物のアラートを上げたことはありません。

ですから、今使っているロジックや手法が本当に有効かどうか(問題がある時にそれを検出できるか)はわかりません。

しかし、運用担当者としての「安心感」の観点からは非常に大きな効果があったと思います。

アプリケーションでもインフラでも大きな変更があった時には、私もいつも、各種監視画面をチェックする合間に、注文の一覧など直近のトランザクションの一覧を見るということを習慣にしてきました。監視画面や監視項目は常時改良して不足しているものを追加したりしていますが、どうしてもそれだけでは安心できず、実データが入ってきているのを見て初めて安心するという状態でした。

今は、自分がいつもやっていたことをこの「ビジネスプロセス監視」が代わりにやってくれます。当然、機械ですから、私がやるよりずっと網羅的で二十四時間いつでもやってくれます。

NewRelic Insights のカスタムイベントのAPIは非常に簡単で、timestampも自動的に付加されますので、このような用途には非常に適しています。ここでやっていることはまだ試行錯誤の段階で、スクリプトで書くべき部分が多いなど課題も多いのですが、多くの運用担当者にお勧めしたいと自分の実感から思っています。

おまけ:論理と非論理の間あるいは分割統治の限界

余談ですが、この問題を少し広げて(主語を大きくして)考えると、「論理的なパーツを集めると非論理の問題が起こる」という風に言えるかもしれません。つまり、「分割統治の限界」という問題です。

コンピュータの歴史上、サブルーチンの発明から常に人間は分割統治によって問題を克服してきました。問題を分割して、個々の解決手段を論理的に構築して、最後に、そのパーツを論理的に組み合わせればバグのないシステムができるはずだ、ということです。

設計手法はみんなそうだし、最近では、マイクロサービスやTDDや関数型プログラミング言語、さらには、これを並行処理に適用したfunctional reactive programmingも同じです。

監視においては、症状監視で全てのコンポーネントが正常に動作していることを確認できれば、本来は、外形監視やここで取り上げた「ビジネスプロセス監視」などはいらないはずです。

しかし、どれも小規模の問題ではうまくいっても、問題の複雑さが大きくなると、どこかで限界にぶつかります。

人間が扱える複雑さには、常に上限があることを意識すべきではないでしょうか?

ですから、どんなに画期的な手法に見えても、それが有効であればあるほど「複雑さの限界」にあらかじめ備えておくことが必要なのではないかと私は考えます。

今は、クラウドコンピューティングと、広義のマイクロサービス化が、その「複雑さの限界」に近づいているように感じます。有効性が証明されていることでその時期が早くなるということです。

Qiita で New Relic Advent Calendar 2017 いろいろ書きました。特に、New Relic APM の入門的な連載を書きましたので、是非、ご覧ください。

New Relic 公式の日本 New Relic ユーザー会を立ち上げました。ワークショップの情報など日本のお客様向けに情報を発信していきますので、是非、参加ください。

過去記事

2018/09/13

翻訳: FutureStack18: New Relic 開発者向けプログラム-オープン化、シンプル化、活発化への道

今年も始まりました。New Relic の年次カンファレンス FutureStack 18。
この記事では、Elixir 用の New Relic APM エージェントの発表とデベロッパープログラムの発表がされています。

続きを読む

2018/08/27

翻訳: New Relic APM 新機能: 分散トレーシング

New Relic APM にDistributed Tracing (分散トレーシング)機能が追加されました。メニュー単位で機能が追加されたのはだいぶなかったのではないかと思います。マイクロサービスにおけるサービスをまたがったデータの流れを可視化できる機能のようです。是非、チェックしてみてください。

続きを読む

2018/05/18

SREcon18 と Rails デベロッパー向けアンケート結果の紹介

SRECon America カンファレンスにおけるアンケートの記事と Rails デベロッパーに対するアンケートの記事という2つの異なったレイヤーのアンケートに関する記事を見つけたので、ざっくり紹介します。違った視点での傾向が見れてなかなか面白いです。

続きを読む

2018/05/11

AWS Summit Tokyo を中心に直近の New Relic 関連イベントのご紹介

5/30 から始まる AWS summit Tokyo に参加するということで、海外から New Relic スタッフが来日し、イベント等を行います。是非、この機会に New Relic に興味のある人は参加してみてはいかがでしょうか。(基本、日本人スタッフいるので、日本語でも大丈夫なはず)

続きを読む

2018/03/17

翻訳: New Relic Browser JavaScript Error Analytics ベータ版 – エラーの早期発見、修正に役立つ

New Relic Browser の JS エラー機能が新しくなるようです (現在ベータ版)。APM で採用されているエラープロファイルが JS エラーにも対応したようです。これによって、エラーが起きている傾向が分析できるようになり、今後の JS のエラーが起きる前に対策が取りやすくなります。既存の PRO ユーザーはベータ版が使えるようなので、是非、使ってみてください。

続きを読む

 もっと見る