感想:Site Reliability Engineering

Site Reliability Engineeringを読んだので、感想と個人的な各章のサマリ(備忘メモ)です。

shop.oreilly.com
Google - Site Reliability Engineering

なお私はソーシャルゲーム会社のインフラエンジニアで、CM対策などを含めた大規模インフラの経験はありますが、ビッグデータ的な分散処理基盤はあまり経験していません。

[感想]

SLO大事

SLO(Service Level Objectivesの略。稼働時間や遅延などのサービスの健全性を示す指標に対する目標値)の策定と自動化を進めて、インフラの雑務(toil)を削減することが大事だなと思いました。

「Error Budget」の概念に注目されている本ですが、「Error Budget」に基づいた運用をする/しないに関わらず、SLOを策定して自動化を進めることがSREチームのスタート地点になるのかなと思いました。
SLOを定めることで、サービスにとっての障害が何かを定義できて、作業スコープを明確にできます。その上で自動化を進めると、作業時間の短縮ができ、「Error Budget」に基づいたチーム運営もできるようになり、またインフラエンジニアの燃え尽きも防げるという主張は、その通りだなと思いました。

Error Budgetについて

私は賛否両方あると考えています。エンジニアの観点では賛成で、サービスの運用者としては悩ましいと感じました。

「Error Budget」としてクォーター毎にSLOを下回ってよい時間を定義しておき、「Error Budget」が残っているうちはアグレッシブなリリースをしたり、障害対応をon-call担当に任せて非on-call担当のエンジニアが開発に専念できるのは、エンジニアとしてはとても仕事がしやすいなと思いました。

一方でサービスの責任者から見ると、インフラは動いていてナンボなので、イノベーションのために多少のSLO違反は許容してというのは心理的に受け入れづらそう。極論すると、必死に盛り上げているサービスが、アグレッシブなリリースで水を差されたら、企画している人たちは嫌だろうなと。
そうしたデメリットをイノベーションが上回ると説得できればよいけど・・。「Error Budget」を低めに設定するとあまり意味がないので、まだいい落としどころが自分の中でついていないです。*1


[各章サマリ]

本を買わなくていいじゃん!とならない範囲で書きます。
「Chapter 1 : Introduction」もインパクトありましたが、「Chapter 21 : Handling Overload」と「Chapter 26 : Data Integrity: What You Read Is What You Wrote」が私のこれまでの業務・興味との重なりが大きく、特に面白かったです。

Chapter 1 : Introduction

SREとは?Error Budgetとは?という内容。

Chapter 2 : The Production Environment at Google, from the Viewpoint of an SRE

Googleのシステム構成について。Borgやストレージスタック、SpannerやChubbyなどの概要。

Chapter 3 : Embracing Risk

稼働率100%を目指すと超高コストになるので、コストやサービスの内容(売上や人命に関わるとかの特性など)を踏まえて、妥当なSLOを策定しようという話。

Chapter 4 : Service Level Objectives

SLOやSLIなどの用語説明と、いい感じのSLOの運用方針について。

Chapter 5 : Eliminating Toil

タイトルそのまま

Chapter 6 : Monitoring Distributed Systems

Googleの監視システムの思想について。

Chapter 7 : The Evolution of Automation at Google

Googleの自動化の変遷。

Chapter 8 : Release Engineering

Googleのコードのデプロイと、設定変更の本番反映についてちょっと具体的な話。

Chapter 9 : Simplicity

コードはシンプルなほうがいいとのこと。

Chapter 10 : Practical Alerting from Time-Series Data

Googleがどのように監視用のデータを時系列で作成・保持し、アラートを飛ばしていたかの詳細。
現監視システムでなく、過去に10年使っていたborgmonについての話。

Chapter 11 : Being On-Call

Googleが障害対応担当をどうローテーションしているかについて。
かなりエンジニアの燃え尽き(burn out)に気を配っている感じ。

Chapter 12 : Effective Troubleshooting

Googleの障害解析のアプローチについて。
全体的には普通のアプローチだけど、根本原因の追究が徹底的なのと、解決が自動化 or ツール化なのがGoogleらしい気がしました。

Chapter 13 : Emergency Response

Googleで起きた障害とその対応のケーススタディ

Chapter 14 : Managing Incidents

障害対応時の役割分担について。
Googleは簡単な障害は自動復旧の仕組みがあるので、人手が必要な大規模/複雑な障害の時の話。

Chapter 15 : Postmortem Culture: Learning from Failure

Postmortem(障害報告書)とは何か、どう運用しているかの話。
postmortem自体は、google以外の会社でも結構よく聞きます。

Chapter 16 : Tracking Outages

Googleが使っている障害管理システムのご紹介。

Chapter 17 : Testing for Reliability

Googleが実践しているテストの話。

Chapter 18 : Software Engineering in SRE

Auxonというキャパシティプランニングツールの構築を例にした、SREのソフトウェア開発について。
新ツールをどう社内で普及させるかという泥臭い話もあり。

Chapter 19 : Load Balancing at the Frontend

Google流のDNS Load Balancingの話。

Chapter 20 : Load Balancing in the Datacenter

GoogleのDC内のロードバランサについての話。
Googleはユーザのリクエストを、Chapter19のDNSで地理的に最適なDCに割り振ってから、本ChapterのとおりDC内でL7レイヤの振る舞いをするLBで負荷分散しているそうです。

Chapter 21 : Handling Overload

高負荷時の望ましい振る舞いと、その実装について。
個人的にあまり考えてこなかった内容なので、とても勉強になった章でした。

Chapter 22 : Addressing Cascading Failures

障害の連鎖をどう防ぐかという話。Chapter21から繋がっている内容で、具体的な対策もあり。

Chapter 23 : Managing Critical State: Distributed Consensus for Reliability

分散合意システムでどう信頼性を担保するかという話・・と思いますが、この分野の経験が足りなくてうまく読めなかった章です。

Chapter 24 : Distributed Periodic Scheduling with Cron

Googleの超大規模・分散システムでcron的な処理をどうやって実現しているかの話。

Chapter 25 : Data Processing Pipelines

GoogleはWorkFlowというツールを作って、いい具合にデータ処理を並列処理しているとのこと。あまりこの賞は深読みしなかったので違うかも・・

Chapter 26 : Data Integrity: What You Read Is What You Wrote

バックアップ/リストアの話と、データ不整合の対策について。
優秀なエンジニア達が、多くのコストをかけて、本気でデータ保全を検討して実装した内容が書いてある章で、すごく学びが多かったです。

Chapter 27 : Reliable Product Launches at Scale

Launch Coordination Engineering(LCE)という、サービスローンチを専門にしたエンジニアの紹介。

Chapter 28 : Accelerating SREs to On-Call and Beyond

新しくSREに加わったメンバーをOn-Callを担当できるまで育てるプロセス。

Chapter 29 : Dealing with Interrupts

SREチームはサービス横断的な組織で、また障害対応を受け持っているため、作業の割り込みが増えがちなので、それをどう処理するかという話。個人的には作業の割り込みは苦にならないけど、チームで苦手にしている人が多かったので、うまく配慮できるようになりたい。

Chapter 30 : Embedding an SRE to Recover from Operational Overload

運用負荷が高まっているチームにはSREを1名派遣して、根本原因を取り除いて立て直すhow to気味な章。
SRE的な仕事が根付いていないチームを、SRE文化に変えていくときにも参照できそうな内容。

Chapter 31 : Communication and Collaboration in SRE

SREとプロダクトチーム、または複数拠点に分散しているSREチーム同士が、どのように連携すると良いかのGoogleケーススタディとベストプラクティス。

Chapter 32 : The Evolving SRE Engagement Model

SREがどうプロダクトに貢献するかという話。プロダクトのリリース後にチームに参加するパターン、開発中に参加するパターンを経て、フレームワークを提供するパターンに変遷してきたとのこと。

Chapter 33 : Lessons Learned from Other Industries

医療や軍隊、ライフガードなど別産業から、参考にしたアイデア集。
Chapter 26の実装の背景にもなっていそうで、読み物としても面白い。

Chapter 34 : Conclusion

全体のまとめ。

以上です。
英語版を買ったものの、積読しているうちに英語版が無償公開され、日本語版まで発行されそうになったので、焦って読みました。

*1:私がソシャゲというコンテンツの力が大きい業界のエンジニアで、接続エラーで離脱したユーザはインフラのイノベーションでは帰ってこないと思えるため、こうした考えになっているのかもしれない。

specファイルのRecommendsタグ

OpenSUSE用のタグ。CentOS等での扱い方は、OpenSUSEガイドラインにあった。
openSUSE:Specfile guidelines - openSUSE

Recommends

SUSE's RPM supports the Recommends tag, but it was not available upstream until very recently. As a result, packages with Recommends will fail to build for OBS targets Fedora_18, RHEL_6 and CentOS_6, for example. A workaround is to exclude the Recommends tag from non-SUSE targets:

%if 0%{?suse_version}
Recommends: foo
%endif
Alternatively the Recommends tag could become a Requires tag so that a recommended package (foo) gets installed in any case on systems that do not support RPM Recommends:

%if 0%{?suse_version}
Recommends: foo
%else
Requires: foo
%endif
See also openSUSE:Build Service cross distribution howto

httpdをreloadしてログを切り替える際、ロギングに漏れが出るか

表題の内容を試したが、結果やはり漏れは出なそう。
そもそもは、logrotate時の動作が知りたかった。設定はデフォルトだと以下のような感じ。
$ less /etc/logrotate.d/httpd
/var/log/httpd/*log {
daily
rotate 30
missingok
notifempty
sharedscripts
postrotate
/sbin/service httpd reload > /dev/null 2>/dev/null || true
endscript
}

これを見て、reloadしたときにログに漏れが出ないのかがふと気になった。本当はapacheのreloadがどういった挙動をするか調べるべきだが、軽く調べてわからなかったため、手っ取り早く実証することにした。
手順としては連番の公開ファイルを作成し、順番にアクセスしている途中でアクセスログをリネーム(mv /var/log/httpd/access_log var/log/httpd/access_log.1)→httpdのリロード(sudo /etce/init.d/httpd reload)を行う。

アクセスするシェルは以下。
#!/bin/bash
for i in `seq 1 100000`
do
curl http://localhost/index$i.html
done

アクセスした結果は以下。
access_log.1(リネームした旧ファイル)
127.0.0.1 - - [19/Jun/2014:08:04:24 +0900] "GET /index10.html HTTP/1.1" 200 14 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
127.0.0.1 - - [19/Jun/2014:08:04:24 +0900] "GET /index11.html HTTP/1.1" 200 14 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
(中略)
127.0.0.1 - - [19/Jun/2014:08:05:42 +0900] "GET /index10972.html HTTP/1.1" 200 14 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
127.0.0.1 - - [19/Jun/2014:08:05:42 +0900] "GET /index10973.html HTTP/1.1" 200 14 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2"

access_log(reloadで生成された新ファイル)
127.0.0.1 - - [19/Jun/2014:08:05:42 +0900] "GET /index10974.html HTTP/1.1" 200 14 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2"
127.0.0.1 - - [19/Jun/2014:08:05:42 +0900] "GET /index10975.html HTTP/1.1" 200 14 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2"


当然の結果だけど、安心した。

EC2(micro)上でのmysqlパフォーマンス設定と測定

最低限の設定のみで動かしていた、EC2にインストールしたmysqlですが、エキスパートのためのMySQL[運用+管理]トラブルシューティングガイド(http://www.amazon.co.jp/dp/4774142948)を買ったので、チューニングしてみました。性能改善よりもmicroインスタンスでメモリが少ないため、メモリ消費量を抑える観点で設定を実施しています。学習用の個人サイトでアクセスがほぼなく、レスポンスが遅い訳ではないため。

[mysqld]
max_connections = 1024
table_open_cache = 2048
table_definition_cache = 1100
key_buffer_size = 16M
read_buffer_size = 128K
read_rnd_buffer_size = 256K
join_buffer_size = 128K
slow_query_log
long_query_time = 3
log_queries_not_using_indexes
open_files_limit = 5500
innodb_buffer_pool_size = 100M
innodb_additional_mem_pool_size = 1MB
innodb_autoextend_increment = 64M
innodb_log_files_in_group = 2
innodb_log_buffer_size = 1M
innodb_flush_log_at_trx_commit = 1
innodb_flush_method = O_DIRECT
innodb_thread_concurrency = 8
innodb_commit_concurrency = 5

書籍から変更しているパラメータと理由は下記のとおり。
・table_open_cache
 →テーブルが2つのため
・read_buffer_size、read_rnd_buffer_size、join_buffer_size
 →デフォルト値(未設定状態)でも性能が出ていたため
innodb_buffer_pool_size
 →扱うデータ量が少ないため(1ヶ月稼働で現在8MB程度。1年間もてばよい。)
innodb_additional_mem_pool_size
 →テーブルが2つで少なく、またデフォルト値(未設定状態)でも性能が出ていたため
innodb_thread_concurrency 、innodb_commit_concurrency
 →更新量も参照量も少ないため、書籍より数値を減らして様子見

設定前後の性能は、Chromeの開発者機能でサイト(http://ysfj.net/umakatter)のhtml(mysqlでの参照結果を含む)が戻ってくる時間を20回計って確かめました。キャッシュは無効化して、毎回別のページに遷移しています。
設定前→0.225秒
設定後→0.201秒

性能が落ちてなかったので、しばらくこれで様子見します。
mysqlslapも試したかったですが、実際のWebアクセスにおけるクエリで測定したほうが妥当と考え、この計測方法としました。

Webサービス即日開発の実践4(Perl)〜文字コードの扱い

この開発で文字コードを気にしたのは、以下の2箇所です。
Perlのutf8フラグ
MySQLの4バイト文字対応

【utf8フラグ】
Perlで文字を文字列として扱う場合(表現が微妙ですが)、コード内ではutf8フラグをOnにする必要があり、コードの外部(例えばDBや標準入力/出力など)へ文字列を渡す場合はutf8フラグをOffにする必要があります。
今回のプログラムでの文字情報は
1.TwitterAPI→Perl
2.PerlMySQL
3.MySQLPerl
4.PerI→html
の四ヶ所で移動します。

1はNet::Twitterでツイート情報を取得した場合、値はutf8フラグがOnされている状態でしたので、追加でエンコードはしていません。
2はMySQLに書き込む際、DBIx::Class::Schema::Loaderを使用しており、そこでUTF8Columnsを用いてutf8フラグをOffにしました。
3,4はMySQLからutf8フラグOffで取得した文字列をそのままhtml に出力しています。(のつもりです。)

MySQLの4バイト文字対応】
ツイートやユーザ名に?といった記号など、4バイト文字が含まれるケースがあります。こうした文字をMySQLで扱う場合、データベースの文字コードをutf8mb4にする必要があります。実装はこちらhttp://d.hatena.ne.jp/kaze-kaoru/touch/20110829/1314598374を参考にして
・データベースの文字コードをutf8mb4で作成

Perlでデータベースに書き込む際、mysql_enable_utf8を指定しない

・my.cnfのclientでdefault-character-set=utf8mb4と設定(リンク先の通りにperlグループを作る方がよいですが、デフォルトではclientグループの値が参照されるので、clientに設定しても動きます。)

DBIx::Class〜で接続する際に、mysql_read_default_file=でmy.cnfを指定

することで、無事に絵文字などもMySQLに格納出来ました。MySQLのバージョンが5.5以上でないといけないことも、要注意です。

Webサービス即日開発の実践3(Perl)〜Twitter API 1.1対応とhttps対応

しばらく更新が滞ってしまいました。
投稿する時間も惜しんでコーディングしていて、なんとか無事に動くコードが完成しました。

詰まった箇所は、主に以下の2つです。
Twitter APIの仕様変更
②4バイト文字(絵文字や非常用漢字)の取り扱い

①は、サンプルコードが2010年のものでTwitter API 1.0を利用していたため、Twitter API 1.1に対応させる必要がありました。また開発中の1/14にhttpsも必須(http://news.mynavi.jp/news/2014/01/16/459/)になったため、その対応も実施し、以下のように書きました。

package Umakatter::CLI::FeedReader;
use Moose;
use Carp;
use URI;
use XML::Feed;
use Umakatter::CLI::FeedReader::Tweet;
use strict;
use Net::Twitter;
use utf8;

has 'consumer_key' => (
is => 'ro',
isa => 'Str',
default => 'xxxx'
);

has 'consumer_secret' => (
is => 'ro',
isa => 'Str',
default => 'xxxx'
);

has 'access_token' => (
is => 'ro',
isa => 'Str',
default => 'xxxx'
);

has 'access_token_secret' => (
is => 'ro',
isa => 'Str',
default => 'xxxx'
);

no Moose;

sub entries {
my $self = shift;
my $nt = Net::Twitter->new({
traits => [qw/API::RESTv1_1/],
ssl => 1,
consumer_key => $self->consumer_key,
consumer_secret => $self->consumer_secret,
access_token => $self->access_token,
access_token_secret => $self->access_token_secret,
});
my $r = $nt->search({q=>'美味い店', lang=>"ja", count=>100});
return @{$r->{statuses}};
}

sub tweets {
my $self = shift;
my @tweets;
my @entries = $self->entries;
for my $entry (@entries) {
push( @tweets,
Umakatter::CLI::FeedReader::Tweet->new($entry)
);
}
return \@tweets;
}


初期はssl => 1ではなく、api_urlにhttpsのURLを指定して実装していたのですが、https接続でエラーになったため、http://za.toypark.in/html/2009/10-21.htmlを参照して、IO::Socket::SSLMozilla::CAを導入しています。


②は、また次の記事にします。

Webサービス即日開発の実践2(Perl)〜テストスクリプト作成

Catalystが入ったので、Twitterに検索語を投げて検索結果を取得するテストスクリプトを作成し、フィジビリティを確認します。

こちらもまずは本の通りに書いてみましたが、エラーに。本が2010年のものであり、前提としているTwitterAPIが1.0のため、認証周りがだめでした。

そこで以下のサイトを参照にトライ&エラーを繰り返し、なんとか検索語に対する結果を取得できました。
Twitter API
https://dev.twitter.com/docs/api/1.1/get/search/tweets
Net::Twitter
http://search.cpan.org/~mmims/Net-Twitter-4.01000/lib/Net/Twitter.pod
ブログ類
http://www.fukudat.com/wiki/ja/PerlによるTwitter_botの作り方
http://ktz.sblo.jp/article/66724679.html
http://tech.voyagegroup.com/archives/465806.html

Perl(というかスクリプト言語)自体が初めてであり、コーディングの学習も兼ねた実践のため、文字コードにも苦戦しました。なので丁寧に解説してあった、Voyage Groupさんのブログ記事はとても参考となりました。
書いたコードは下記の通りで、実行時に引数で指定した文字列を検索し、結果を標準出力します。まだ「binmode STDOUT, ":utf8";」はおまじないで書いてしまっているので、調べて不要なら消したい。


=======================
#!/usr/bin/perl
use strict;
use Net::Twitter;
use utf8;
binmode STDOUT, ":utf8";
use Encode qw(decode_utf8);

my $consumer_key = "xxx";
my $consumer_secret = "xxx";
my $access_token = "xxx";
my $access_token_secret = "xxx";

my $nt = Net::Twitter->new({
traits => [qw/API::RESTv1_1/],
consumer_key => $consumer_key,
consumer_secret => $consumer_secret,
access_token => $access_token,
access_token_secret => $access_token_secret
});

do_search($_) foreach @ARGV;

sub do_search {
my $term = shift;
my $r = $nt->search({q=>decode_utf8($term), lang=>"ja", count=>100});
for my $status ( @{$r->{statuses}} ) {
print "$status->{text}\n";
}
}
=======================