orangain flavor

じっくりコトコト煮込んだみかん2。知らないことを知りたい。

#isucon 2014予選の延長戦をやってみた

予選の時間内では足りてないことばかりだったので、もう少し試行錯誤することにしました。

#isucon 2014の予選をほぼ一人で戦うハメになった話 - orangain flavor

目標は50000点、できれば60000点出したい。

予選終了時

  • Python実装
  • DBはRedisのみを使う
  • Cookieがないときだけnginxで静的ファイルを返す
  • Gunicornを使ったマルチプロセスモデル
  • ワーカー数10、ワークロード10

最終提出スコア: 32710

細々とした改良

  • nginxの設定を追加。
  • redis-pyのパーサーをhiredisに置き換え。
  • テンプレートエンジンを使わないよう変更。
  • アプリを見なおして、RedisのRead/Write数を削減。

スコアはあまり上がらず 32912

Gunicornのワーカーをmeinheldに置き換え

前回のエントリのコメントで id:methane 氏に教えていただいたmeinheldに置き換え、nginx―Gunicorn間でKeepAliveを有効にしました。PyPI最新の0.5.6です。

スコアは5000ほど上がって、 37223

副作用として、nginxでCookieがないときだけ静的なindex.htmlを返すことができなくなりました。

nginxでの良い設定方法がわからず、こんな感じで無理矢理やっていたのですが、meinheldを使うと400 Bad Requestが返ってくるようになりました。

    location = / {
      if ($http_cookie !~* "session") {
        root /home/isucon/webapp/public/;
        rewrite ^.* index.html break;
      }
      proxy_pass http://app;
      proxy_set_header Host $http_host;
      proxy_http_version 1.1;
      proxy_set_header Connection "";
    }
    location / {
      proxy_pass http://app;
      proxy_set_header Host $host;
      proxy_http_version 1.1;
      proxy_set_header Connection "";
    }

原因不明ですが、Varnishを使うともっと簡単に設定できるようになると聞いていたので、この時点では深追いしませんでした。

Varnish導入

Varnishを導入しました。yumで4.0.xをインストールしたつもりが、Amazon Linuxだと3.0.xしかインストールできずしばらくハマりました。

また、daemonの起動に失敗してもどこにもログが吐かれないのでだいぶ悩みました。 varnishd -C -f FILEvclを検証すれば良いことを知りました。

バックエンドとしてUnix Domain Socketに対応していないようなので、元の通りGunicornをTCP 8080で動かすようにしました。

静的ファイルはnginx、アプリはgunicornと振り分けるよう設定しました。 

スコアはさほど変わらず 37632

エラーページを静的に返すように

Varnishで簡単にキャッシュできるようになったので、ログイン失敗時に /?err=*** のページにリダイレクトするようにしました。

これは結構効いて、スコアは 43715 に。Varnish便利。

データをインメモリに持つように

ユーザー情報とBAN/Lockの情報をメモリに保持するようにしました。マルチプロセスなので、結局Redisにもアクセスする必要があり、苦労した割に効果は薄かったです。

スコアは上がらず、というかむしろ下がり 42533

FlaskからBottleに書き換え

プロファイリング結果を見るとFlaskで時間を食っていたので、さらにmicroなフレームワーク Bottle に置き換えました。

これは結構効果があり、スコアは 50386

Sessionを暗号化されていないCookieに置き換え

正直どうなのかという話ですが、Session Cookieの暗号化と検証を捨て、ユーザー名と最終ログイン情報をCookieに直接格納しました。

このとき、Cookieの値に空白が含まれるとベンチマーカーが意図したとおりに返してくれない問題に若干苦しみました。

スコアは若干上がり 51076

RedisからのReadをパイプライン化

attempt_loginの条件分岐の中で、細切れでRedisからReadしていたのを、パイプラインで一括取得するようにしました。

これによりRedisへのアクセスは、1リクエストあたり最大Read1回、Write1回の計2回で済むようになりました。

スコア: 52705

1回休み

Redisの設定を見ると、いつの間にかRedisのappendonlyが無効になっており、永続化されてない状態になっていました。

有効にしてスコアを測り直すと 51246 でした。

PyPyにしてみる

PyPyを使ったら速くなったりしないかなーと思って試してみますが、スコアは 46607 に下がったので却下。

PyPyに単純に置き換えただけで、標準以外のライブラリも普通に動くのだなと感動しました。

2014-10-05 23:34追記: 後ほどちゃんとPyPyに合わせてチューニングしたらCPythonよりも高いスコアがでました。詳しくは文末の追記を参照。

Varnishのチューニング

VarnishのCPU利用率が高いのでチューニングしてみました。

  • VARNISH_MIN_THREADS=200 → 50
  • -p thread_pools=4
  • -p session_linger=100

いまいちよくわからないもののスコアは若干上がり 53016

最新のmeinheldに置き換え

GitHubの最新版を使うと良いと教わったので、置き換えてみました。

スコアは若干上がり、 53872

まとめ

最終的なスコアは 53872 となりました。これ以上はどうすればスコアが上がるか思いつきません。

とりあえずの目標としてた50000は超えましたが、60000には届きませんでした。Pythonのマルチプロセスモデルだとこの辺が限界なんでしょうか。。

ちなみにワークロードやGunicornのワーカー数はいじってもスコアが上がらなかったので、変えてません。 

ソースコードと設定はGitHubで公開しています。

クーポンのおかげで、あまりお金をかけずに勉強できました。AWS様ありがとうございます。

f:id:mi_kattun:20141005123906p:plain

参考サイト

おまけ

GOGC=offでベンチマーク実行

$ GOGC=off ~/benchmarker bench --workload 10

スコア: 79324

静的ファイルを返さないように

<link>タグと<img>タグをdocument.writeで書きだすようにしました。

$ GOGC=off ~/benchmarker bench --workload 10

スコア: 180084

orangain/isucon2014-qualifier-python at no-static

2014-10-05 23:34追記:PyPyをちゃんと導入してみた

例によって id:methane 氏に教えて頂きました。ほんとうに感謝です。

PyPyへの最適化

Tornado 3.2をインストールし、GunicornのワーカーをTornadoWorkerに置き換えました。またC拡張であるhiredisもアンインストールしました。

さらにinitスクリプトの中で、JITのウォームアップとして25秒間ベンチマークを走らせました。

ベンチマーク実行中は、GunicornのCPU消費がCPythonに比べて格段に減り、ワーカー数を減らすほどスコアが上がるようになりました。ワーカー数1、ワークロード14で 54736 まで上がりました。

1プロセスへの最適化

1プロセスを前提とすれば、メモリにRedisと同じ内容を保持し、RedisへのReadアクセスを完全に無くすことができます。Readしないように書き換えたところ、同じくワーカー数1、ワークロード14で 55955 まで上がりました。

orangain/isucon2014-qualifier-python at pypy

PyPyを使ってみて

PyPyは今回初めてまともに使いましたが、サードパーティのライブラリを含めてエラーなどは全く無く、単純に置き換えただけ*1で正常に動作し、正直驚きました。

Amazon Linuxへのインストールこそ非公式配布のバイナリを使いましたが、非常に安定している印象を受けました*2

PyPyを使えば、Pythonでも1プロセスで高速に処理するGo言語のような戦い方ができることを確認できて良かったです。それでも来年はGoで出られる程度にはGo言語力を高めたいですが。

*1:もちろんサードパーティライブラリのインストールは必要ですが。

*2:ちなみに使用したPyPyのバージョンはCPython 2.7互換のPyPy 2.4です。

#isucon 2014の予選をほぼ一人で戦うハメになった話

まとめ

  • リモートでタイムゾーンが違うと大変
  • Pythonを選んだ
  • Redisのみを使う実装に書き換えた
  • 提出スコアは32710

準備したこと

事前にチームで昨年の問題を解きました。プロファイリング方法を全然わかってなかったので、制限時間内にほとんどスコアが伸びませんでした。1週間かけて勉強し、MySQLのスロークエリログやApache/nginxのログを集計する方法を知りました。

また、継続的にプロファイリングできるようにするのが大事だと痛感したので、以下の様なツールを作りました。

  • ベンチマークを回すと同時に、以下の様なデータを収集する。
  • 収集したデータを集計し、Jenkins上で成果物として保存し、スコアのグラフをプロットする。

f:id:mi_kattun:20140930001731p:plain

やったこと

10:00-

開始時刻になっても他の2人が現れないので、一人で作業することに。最初のところで、案内された手順でRuby実装が終了しない問題や、isuconユーザーでSSH接続できない問題などでかなり手間取ってしまいました。

ブラウザでの挙動、DBの構造、稼働中のミドルウェアを確認をした上で、事前に準備したツールでリクエストログやスロークエリログの集計などを行いました。

Python実装での初期スコアは 1534 でした。

11:00-

アプリのソースコードを読み、データ構造を変えたらBAN/Lockの判別はO(1)にできるし、JOINとかないからKVS向きだと判断しました。が、上位陣はMySQLのままで高いスコアを出していたので読みが甘かったかもしれません。

インメモリで高速に処理できてかつ永続化もできるKVS、ということでRedisを選択しました。Redisはこれまで話を聞くだけで実際に使ったことなかったですが、Pythonから簡単に使えました。tagomorisさんがISUCONは新しいことに挑戦できる場だと仰っていたのはほんとそう思います。 *1

12:00-

ご飯を食べ、Redisをインストールしました。yumでインストール出来ないのは辛かったです。Redisの使い方を勉強しながらアプリを書き換えました。ちゃんと書き換えられるか自信がなかったので、少しづつ書き換えて確認する戦略をとりました。

とりあえずlogin_logの処理とBAN/Lockのチェックだけを書き換えて、Failしつつもスコアが 8864 に上がったのが13:30頃でした。

14:00-

14:00になってようやく2人目のメンバーが参加しました。が、すぐに状況を把握できるわけではありません。。

login_logに初期データがあることに気づき、reportが通るようになったのが14:20頃。レポートの出力順をチェックされていたらどうしようとドキドキしてましたが、無事SUCCESS。スコアは 9250

Redisのデータがちゃんと永続化されているか自信がなかったので、サーバーを再起動して問題ないことを確認。

nginxで静的ファイルを返すよう設定してもらいました。

15:00-

最後までMySQLで処理していたログイン時のユーザー情報取得もRedisに移し、ついでにパスワードは平文で比較するようにして、スコアは 9623 に。

初期化処理でMySQLからRedisにデータをロードする処理が遅くてイライラしてきたので、予め初期化済みのaofファイルを用意して置き換えるようにしました。これによって軽快にベンチを回すことができるようになりました。

nginxで静的ファイルを返すとContent-Typeが正しく設定されないという罠に引っかかって二人で直しました。

16:00-

この辺でworkloadを増やすとスコアが上がることに気づくものの、cannot assign requested addressというエラーが出るようになります。

カーネルパラメータを調整し、workload 10でfailしなくなったのが16:30で、スコアは 30694

RedisをUnix Domain Socketで繋いだら速くなるかなと設定してみてもそれほど変わらず、スコアは 31059。 

nginxのworker_processesを4に増やすと 31650 に。この辺からスコアが伸びなくなります。

17:00-

nginxとgunicornをUnix Domain Socketで繋ぐも、さほど変わらず。

セッションクッキーがない場合だけnginxで静的ファイルを返すよう設定を試みましたが、二人共詳しくなかったため、なかなかうまく設定できず。

そんな中3人目がようやく登場するも、なにができるわけでもなしw もう諦めようかと言いながらも、どうにかして設定したのが 17:40。スコアは 32710。これが最終スコアになりました。

この後、いろいろなworkload、gunicornのプロセス数を試すも、スコアは上がりませんでした。高速化できる場所も思いつかずに終了しました。

反省点

後半ちゃんとボトルネックを特定できておらず、ほとんどスコアが上がらなかったのは反省点です。サーバー起動時にオンメモリにデータを持つようにすればもっと上がった気がします。

ツールを用意したものの、肝心のnginxのレスポンスタイムを出力するのを忘れていたりと全然活用できませんでした>< 自分が直前に作ったコマンドは使い慣れてないし、微妙に使いやすくないし、心の何処かで自分を信用できてなくてほんとダメでした。

というか、Redisへの書き換えに3時間ほど集中したおかげで色々吹っ飛びました。こういう時に冷静な仲間が居るといいんだろうなと思います。

まとめ

今回はじめて参加しましたが、非常に楽しい一日を過ごすことができました。運営の皆様、チームのメンバー、ありがとうございました!

*1:supervisordも去年の問題を練習で解いた時に初めて使い方を知りました。

プロフェッショナルのための実践Heroku入門 の紹介

以前公開した、 The Twelve-Factor App日本語訳が書籍に収録されるということで、一冊頂けました。ありがとうございます。せっかくなので簡単に紹介します。

f:id:mi_kattun:20140917204846j:plain

Herokuの哲学

私が初めてHerokuを触ったとき*1に感動したのはそのオープンさです。Heroku独自の仕様は少なく、Git, Procfile, Buildpackなどのオープンな仕様に加え、各言語のデファクトスタンダードな方法(Pythonであればpipとrequirements.txt)で依存関係を管理すれば良いことにとても好感を抱きました。

そんなHerokuを支える哲学として気に入った部分を2つ書籍から引用します。

Herokuはアプリケーション開発者の生産性を最大化することに常に焦点を当てています。世界を変える新しいアイデアをソフトウェアという形で世に送り出し、実際に世界を変えていくことを可能にするために、アプリケーション開発者自身がその創造力、時間、コストを本当に価値のある活動だけに100%集中できるようなプラットフォームでありたいと願っています。
Herokuは、アプリケーション開発者にとって、生産性が高い開発環境とは、彼らの手に馴染んだツールや経験値を生かすことができる環境―すなわち各アプリケーション開発者が自分たちの手元に構築している自分のための開発環境―こそがアプリケーション開発者の生産性を最大化させるのだと判断しました。

プロフェッショナル向けのガイド

もちろん本書は哲学だけでなく、実践的な書籍です。

最初の一歩であるHerokuアカウントの作成から、Dynoの冗長化・カスタムドメインSSL・データベース運用などの本番環境での利用までを丁寧に解説しているため、これから本格的に触ってみるという人にはオススメです。

また、デプロイ時のSlugコンパイラの動作やDynoの動作環境など、Herokuの具体的なアーキテクチャについては知らなかったため、勉強になりました。

最初に書いたとおり、The Twelve-Factor Appの日本語訳も掲載されています。これはHeroku上で強制されるある種の制約が、スケーラビリティやポータビリティを生み出す方法論であることを言語化して教えてくれる文書です。

目次

最後に目次を載せておきます。 

はじめに
Herokuの哲学
プロフェッショナルなアプリケーション開発者
Herokuの歴史
本書の構成

第1章 Herokuの概要
1.1 本章の内容
1.2 Herokuとは
1.3 さまざまな種類のクラウドサービス
1.4 Herokuの特徴

第2章 Herokuの利用準備
2.1 本章の内容
2.2 Heroku利用準備
2.3 アプリケーションの作成からデプロイまで
2.4 Ruby開発環境の構築
2.5 Node.js開発環境の構築
2.6 Scala開発環境の構築
2.7 Java開発環境の構築

第3章 アプリケーション開発のポイント
3.1 本章の内容
3.2 データベースの選定
3.3 外部ストレージ
3.4 レスポンス時間制限
3.5 Slug制限
3.6 IPアドレス制限
3.7 Procfile
3.8 Foremanによるローカル環境デプロイ
3.9 ステージング環境構築
3.10 ステージング環境のBasic認証設定

第4章 アドオンによる機能追加
4.1 本章の内容
4.2 アドオンとは
4.3 アドオンの基本操作
4.4 代表的なアドオンの紹介

第5章 本番環境への移行
5.1 本章の内容
5.2 Production Check
5.3 Cederスタックの利用
5.4 Dynoの冗長化
5.5 プロダクションレベルのデータベースの利用
5.6 カスタムドメインの利用
5.7 SSLの導入
5.8 カスタムエラーページの設置

第6章 Heroku Postgres
6.1 本章の内容
6.2 Heroku Postgresのサービス
6.3 基本機能
6.4 Heroku Postgresの拡張機能

第7章 トラブルシューティング
7.1 本章の内容
7.2 Herokuのオフィシャルサポート
7.3 よくあるトラブル
7.4 Herokuのエラーコード

第8章 Herokuのアーキテクチャ
8.1 本章の内容
8.2 Herokuの仕組み

第9章 The Twelve Factor App
9.1 はじめに
9.2 背景
9.3 このドキュメントの対象者
9.4 The Twelve Factors

*1:私が触ったときには既にCederスタックでした。Herokuの歴史も書かれてて興味深いです。

PyCon JP 2014に参加しました

2014年12月23日 編集:資料・動画の埋め込みは重かったのでリンクにし、いくつかの資料へのリンクを追加しました。

毎回行きたいと思いながらも用事があって行けなかったPyConに初めて参加しました。

f:id:mi_kattun:20140915221906j:plain

感想

Kenneth Reitzさんによる1日目の基調講演は、言語、そしてインターネットによって人は他の人と交流できるようになったのに、Pythonは2と3の二つの言語に分かれてしまっているという話でした。

個人的にはUnicode周りが面倒なPython 2にはもう戻れないと感じていますが、利用状況は2のほうが圧倒的でした。確かに英語圏の人にはメリットが感じにくいので、メリットを享受しやすい非英語圏(含む日本)の人が積極的にPython 3に移行していくと幸せになれるのではないでしょうか。 

西尾泰和さんによる2日目の基調講演は、人間は人工物、言語、方法論、教育の4要素によって増強することができ、新しいものを発見するためには、盲点を知るための方法論(比較、歴史、経験、抽象化、会話)が大切という話でした。

今回PyConに参加したことで自分の知らない領域の話をいろいろ聴くことができ、自分の盲点を認識できました。資料を見返して復習するとともに、聴けなかったセッション資料・動画も観て勉強していきたいです。

以下、参加したセッションの資料・動画です。

基調講演1日目:Python 2.7 and Python 3: A Sacred Love Story

Python 2.7 and Python 3: A Sacred Love Story // Speaker Deck

CH01 Opening~Keynote: Kenneth Reitz - YouTube

Deep Learning for Image Recognition in Python

Deep Learning for Image Recognition in Python

MH01 Deep Learning for Image Recognition in Python (ja) - YouTube

Gunicorn, the thundering herd and other concurrency programming challenges

Gunicorn, the thundering herd and other concurrency programming challenges // Speaker Deck

CR02 Gunicorn, the thundering herd and other concurrency programming challenges (en) - YouTube

Pythonの実装系総ざらい

Pyconjp2014_implementations

CH04 Pythonの実装系総ざらい (ja) - YouTube

最新リリースCMSツール Plone 5 のモダンUIとテクノロジーの進化

PyCon JP 2014 plone terada

CH05 最新リリースCMSツール Plone 5 のモダンUIとテクノロジーの進化 (ja) - YouTube

XML-RPC : Pythonが「電池付属」と呼ばれる理由

XML-RPC : Pythonが「電池付属」と呼ばれる理由

CR05 XML-RPC : Pythonが「電池付属」と呼ばれる理由 (ja) - YouTube

Open Data for Taiwan's Roadways

Open Data for Taiwan's Roadways // Speaker Deck

MH06 Open Data for Taiwan's Roadways (en) - YouTube

Python を支える技術: ディスクリプタ

Python を支える技術 ディスクリプタ編 // Speaker Deck

CH08 Python を支える技術: ディスクリプタ編 (ja) - YouTube

Lightning Talks

CH09 Lightning Talks - YouTube

基調講演2日目:Rediscover with Python

PyConJP Keynote Speech (Japanese version)

CH10 Opening~Keynote: Hirokazu Nishio - YouTube

聴きたかったけど聴けなかったセッション

自分が後で観るために書いておきます。

Micro Python で組み込み Python

Micro Python で組み込み Python

CR04 Micro Python で組み込み Python (ja) - YouTube

PyCharm活用術

CH06 PyCharm活用術 (ja) - YouTube

PyCharm 活用術 (ja)

リファクタリングツールあれこれ (May the force be with you)

リファクタリングツールあれこれ — pyconjp2014 1 documentation

CH07 リファクタリングツールあれこれ (May the force be with you) (ja) - YouTube

パッケージングの今

パッケージングの今

MH12 パッケージングの今 (ja) - YouTube

その他の資料も含め、以下の場所でまとめられています。

PyCon JP 2014 - 資料一覧 - connpass

RequireJSを始めて戸惑ったこと

公式のGetting Started単純すぎでは?

自作のhelper/util.jsをAMD使って書いてるような意識高い人は、今さらGetting Startedを読まないと思うのです。。

Examplesからリンクされている以下のサンプルを眺めるのがオススメです。

 requireとrequirejsとrequire.configとrequirejs.configのどれ使えばいいの?

ドキュメントでは混在してて戸惑いますが、requirerequirejs は基本的に同じものなので、気にしなくて良いです。主観では requirejs より require を使っている人のほうが多いです。

require.config({})require({}) とも書けます。ただ、設定をする場合は require.config({}) 、モジュールを読み込むときは require と使い分けたほうがわかりやすいでしょう。

configの書き方いろいろありすぎでは?

気にしないで良いです。以下の書き方を使いましょう。

HTML

<script src="require.js" data-main="main.js">

main.js(エントリポイント)

require.config({
  // モジュールIDからモジュールのファイルを探すときにベースとなるパス
  baseUrl: '',
  paths: {
    // モジュールIDでは探せないモジュールのパス
  },
  shim: {
    // AMDに対応してないモジュールを読み込むための設定
  }
});


require(['jquery'], function($) {
     // $を使った処理
});

require.config の引数はConfiguration Optionsに解説があります。

そういえばモジュールって何?

公開用オブジェクトを、グローバルスコープを汚さずに定義するためのスクリプトです。公開用のオブジェクトとは、jQuery$や、Underscore.jsの_、Backbone.jsのBackboneのようなオブジェクトです。

jQueryプラグインのように、既存のオブジェクトに変更を加えるだけで何も公開しないモジュールもあります。1ファイル1モジュールのみを定義できます。

モジュールは jquery.jsmy/module.js のようなパスで管理することもできますが、 jquerymy/moduleのような モジュールID で管理する方が良いです。

baseUrlはどこにすべき?

最初はベースっぽいところを適当に選べばよいかと思います。モジュールをファイルパスから独立したモジュールIDで管理することを意識しておけば、後から変更してもそんなに困りません。

BackboneとUnderscoreってshim使う必要あるの?

もはや必要ありません。RequireJS公式を含む世の中のドキュメントには、Backbone.jsやUnderscore.jsがshimの使用例としてよく出てきますが、 2014年2月リリースの Backbone.js 1.1.1、Underscore.js 1.6.0以降はAMDに対応しています。

ちなみにjQuery UIも1.11(2014年6月リリース)からAMDに対応しています。

r.js難しくない?

はい。shimのライブラリが動かなくなったりするので頑張りましょう。 shimの解説 にいろいろ注意書きがあります。

最初は optimize: 'none' で様子を見るのが良いです。

r.js遅くない?

Rhino版は遅いので、Node版を使いましょう。

ちなみに私は django-require というライブラリを使って、Herokuにpushしたときにr.jsが走るようにしましたが、あまりに遅かったのでr.jsを使うのやめました。Python環境にはNodeが入っておらず、Javaしか入ってないので、簡単に使えるのはRhino版のみでした。

まとめ

始めるときに参考にさせていただいたサイト

Amazon S3のUS Standardリージョンって何?

EC2などのサービスでは US East (Northern Virginia) というリージョンが存在しますが、S3だけは US East の代わりに、US Standard というリージョンが存在します。何故なのか気になるのでちょっと調べてみました。

f:id:mi_kattun:20140607200533p:plain

AWS公式資料から

US Standardへのリクエストは自動的に Northern Virginia または Pacific Northwest *1 にルーティングされるとのことです。

この認識が正しければ、S3で自動的に作られるオブジェクトの3つ以上の複製が、これら2つのリージョンにまたがって保管されていると考えられます。

 This region automatically routes requests to facilities in Northern Virginia or the Pacific Northwest using network maps.

Buckets and Regions - Amazon Simple Storage Service

転送量課金の面では、US East (Northern Virginia) と同じリージョンという扱いです。

 リージョン間でコピーリクエストを通じて転送されるデータは、S3 詳細ページの料金表セクションに指定された料金で課金されます。同じリージョン内にある Amazon EC 2 と Amazon S3 間、もしくは米国東部の Amazon EC2 と米国スタンダードの Amazon S3 間でデータ転送を行った場合、請求は発生しません。

よくある質問 - Amazon S3 (クラウドストレージサービス Amazon Simple Storage Service) | アマゾン ウェブ サービス(AWS 日本語)

ちなみに、US Standardのみデータ整合性モデルが異なります。想像ですが、2つのリージョンにまたがって複製されるため、強い整合性を採用できなかったのかもしれません。

米国西部(オレゴン)、米国西部(北カリフォルニア)、欧州(アイルランド)、アジアパシフィック(シンガポール)、アジアパシフィック(東京)、アジアパシフィック(シドニー)、および南米(サンパウロ)リージョンの Amazon S3 バケットは、新しいオブジェクトの PUTS に対して、「書き込み後の読み込み」整合性を提供します。また、PUTS および DELETES の上書きについて、結果整合性を提供します。米国スタンダードリージョンの Amazon S3 バケットは、結果整合性を提供します。

よくある質問 - Amazon S3 (クラウドストレージサービス Amazon Simple Storage Service) | アマゾン ウェブ サービス(AWS 日本語)

中の人に聞いた方のブログ記事から

Shlomo Swidler氏が、AWSのJeff Barr氏にRead-After-Write ConsistencyがUS Standardで提供されない理由を聞いたというブログ記事です。

その理由は「US Standardが米両岸にまたがる論理リージョンだから」と書かれています。やはり上での想像は正しかったようです。

Read-after-write consistency for AWS S3 is was only available in the US-west and EU regions, not the US-Standard region. I asked Jeff Barr of AWS blogging fame why, and his answer makes a lot of sense:

This is a feature for EU and US-West. US Standard is bi-coastal and doesn’t have read-after-write consistency.

Aha! I had forgotten about the way Amazon defines its S3 regions. US-Standard has servers on both the east and west coasts (remember, this is S3 not EC2) in the same logical “region”. The engineering challenges in providing read-after-write consistency in a smaller geographical area are greatly magnified when that area is expanded. The fundamental physical limitation is the speed of light, which takes at least 16 milliseconds to cross the US coast-to-coast (that’s in a vacuum – it takes at least four times as long over the internet due to the latency introduced by routers and switches along the way).

Read-After-Write Consistency in Amazon S3

結論

Amazon S3のUS Standardリージョンは、アメリカ東西海岸にまたがる論理リージョンです。 ただ転送量課金の面では、US East (Northern Virginia)と同じと考えて問題ないでしょう。

もう少し明確なソースがありそうな気がしますが、見つけられませんでした。ご存じの方は教えて頂けるとありがたいです。

参考:

*1:Oregonのことかと思いましたが、Oregonは2011年に始まった比較的新しいリージョンなので違うかもしれません。わかりません。

Heroku上のDjangoアプリで静的ファイルをS3から配信する(後編)

前編 までで静的ファイルをS3から配信することができました。しかし、実際にHerokuにpushしてみると、毎回全ファイルがアップロードされてしまい、非常に時間がかかります。

原因はこうです。collectstaticはファイルのタイムスタンプを比較してファイルが変更されたかどうかを判定しています。しかし、Herokuではpushの度に全ファイルのタイムスタンプが更新されるので、全ファイルに変更があったと判断されてしまうのです。

参考: Django collectstatic from Heroku pushes to S3 everytime - Stack Overflow

この対策として、前編 のようにcollectstaticをローカルマシンで実行する方法もあります。しかしせっかくならHerokuへのpush時にやってくれたほうがエレガントです。

これを解決するのが collectfast です。collectfastはcollectstatic時にファイルの変更チェックをタイムスタンプではなく、ハッシュ値で行うライブラリです。

collectfastはS3上のファイルのハッシュ値Djangoのキャッシュに保存することで、高速に変更チェックを行います。キャッシュなしでも利用できますが、毎回全ファイルのハッシュ値をS3から取得するので導入効果が薄れます。手元では1分ほどかかりました。

ライブラリのインストール

$ pip install collectfast pylibmc django-pylibmc-sasl

pylibmcdjango-pylibmc-sasl は後述のmemcachierを使うのに必要です。

requirements.txtにも忘れずに書いておきます。(バージョンはご利用のものに合わせてください)

Collectfast==0.2.0
django-pylibmc-sasl==0.2.4
pylibmc==1.3.0
python-dateutil==2.2
pytz==2014.3
six==1.6.1

Herokuの設定

キャッシュで使うmemcachierアドオンを追加します。

$ heroku addons:add memcachier

また、AWSのクレデンシャルはHerokuの設定に追加しておきます。

$ heroku config:set AWS_ACCESS_KEY_ID=*** AWS_SECRET_ACCESS_KEY=***

Djangoの設定

settings.pyに以下の設定を追加します。

INSTALLED_APPS = (
    ...
    'collectfast',  # 行を追加
    ...
)

os.environ['MEMCACHE_SERVERS'] = os.environ.get('MEMCACHIER_SERVERS', '').replace(',', ';')
os.environ['MEMCACHE_USERNAME'] = os.environ.get('MEMCACHIER_USERNAME', '')
os.environ['MEMCACHE_PASSWORD'] = os.environ.get('MEMCACHIER_PASSWORD', '')

CACHES = {
     'default': {
          'BACKEND': 'django_pylibmc.memcached.PyLibMCCache',
          'TIMEOUT': 1000,
          'BINARY': True,
          'OPTIONS': {
               'tcp_nodelay': True,
               'remove_failed': 4
          }
     }
}

COLLECTFAST_CACHE = 'default'

ここでは説明を簡単にするため、defaultのキャッシュを使ってますが、他の用途で使っている場合は、collectfastなどのキャッシュを作るとよいでしょう。

参考:

デプロイ

あとは普通にHerokuにデプロイするだけです。

$ git push heroku master

1回目はある程度時間がかかりますが、2回目以降は変更のあったファイルだけがアップロードされるのですぐに終わります。

結果

Heroku単体で配信していた時は200ms程度かかっていた静的ファイルの配信が、S3を使うことで30〜100ms程度で終わっていることがわかります。

Heroku(US)で静的ファイル配信 f:id:mi_kattun:20140604002053p:plain Heroku+S3(Tokyo)で静的ファイル配信 f:id:mi_kattun:20140604002038p:plain

(参考)さくらVPS(大阪) f:id:mi_kattun:20140604002105p:plain

今回は日本のみをターゲットとしたのでS3のみを使いましたが、全世界をターゲットとする場合はCloudFrontなどのCDNを使うのが効果的でしょう。