大規模サービスでプログラマが注意すること(基礎)

大規模サービスでプログラマが注意すること(基礎)blog_20130517

みなさん、こんにちは。

GeNERACEのピンキリエンジニアのひろゆきです。

いきなりですが、少しだけ自己紹介をしますと、前職やら何やらで比較的、大規模とよばれるサービスの開発、運用していました。

今回はその経験から得たノウハウ的なものをご紹介させて頂きたいと思います。

既にそういったサービスを経験されている方には当たり前のことかもしれませんが、これから参加される方、または、参加したばかりの方のお役に少しでもなれば幸いです。

 

データベースとの関わり方に気をつける

これは基本でありながら、かなり重要なため、関わり方(今回はRDB)について書きます。
(MongoDBなどのNoSQLと呼ばれるものは別のノウハウになるかと思います)

大規模サービスとなりますと、どうしても高負荷なものになりがちです。

単純にサーバースペックの問題もありますが、プログラムの実装方法で解決できる場合もあります。

それにはデータベースへのアクセスが大量に発生するといったことを意識していることが必要です。

 

データベースにアクセスしない

高負荷時には大抵の場合、まず、データベースのCPU利用率、IOの問題が発生します。

これを解決する方法として、そもそもデータベースにアクセスさせる必要があるのかを考えます。

例えば、ほとんど更新されないマスタデータがあるとします。

更新されないので、アクセスする毎に同じデータが返ってくることになります。

ということは、わざわざ毎回毎回、高負荷なデータベースにアクセスせずとも良いはずです。

初回はどういったデータなのか分かりませんが、2回目以降は初回に取得したデータを別途保存しておいたものを使用させれば良いのです。

具体的にはMemcached、Redisなどのキャッシュを利用する方法があります。

またはWEBサーバー自身のメモリに保存しておく方法もあります。

ただし、キャッシュを利用する場合は、データの更新時にキャッシュを削除し、新しいデータを反映させる必要があるので、その方法についても考えておく必要があります。

こういった方法でプログラミングをすると、データベースへのアクセスはかなり減るため、負荷を軽減することができます。

// サンプル
public function getData() {
    $data = $this->getFromCache();
    if (!empty($data)) {
        // キャッシュが存在する
        return $data;
    }
    $data = $this->getFromDB();
    $this->setCache($data);
    return $data;

}

 

INDEXを使用する

データベースにはマスタデータだけがあるわけではありません。

それ以上のトランザクションデータが存在します。

トランザクションデータは即時性が求められることが非常に多く、キャッシュを利用できる可能性が低いです。

あえてキャッシュするとすれば、表示のみ行われるカラムだけをキャッシュするくらいかもしれません。

処理で使用される数値などをキャッシュしてしまうと、データが変更された場合に本来の処理結果と違ってしまうので注意が必要です。

では、トランザクションデータにアクセスする場合に気をつけることと言えば、どういったクエリを書いているのかになります。

気にする項目は、そのテーブルにはデータが何件あるのか(どれくらい増えていくのか)、クエリはINDEXが使用されているかです。

WEBサービスのトランザクションデータは高速に増えていきます。

そのため、サービスリリース時には全く負荷とならなかったクエリが、ある時、全く返ってこなくなることがあります。

何も考えずにJOINを繰り返すと、こういった罠にハマりがちです。

そうならないためにも、テーブルに対して適切なINDEXを付けて行きます。

データベースにもよりますが、データが少ない時にはINDEXがなくても十分なパフォーマンスを発揮してくれたり、INDEXを付けるより高速に結果を返すこともあります。

ただし、前述したとおり、データは高速に、永続的に増えていきますので、その準備を行なっておくべきです。

具体的には、プライマリキー以外での検索クエリを投げていた場合は対象になります。

[userテーブル]

user_id name birthday group_id area_id

上記のuserテーブルのプライマリキーがuser_idカラムだとします。

このテーブルに対してgroup_idで検索したいとすると、group_idにINDEXを付けます。

それによりgroup_id順にINDEXが作成され、検索が高速に行えます。

つまり、基本的には検索キーにINDEXを付けることによって検索が高速に行えます。

ただし、検索キーになるからといって全てのパターンに対してINDEXを付ける必要はありません。

SELECT * FROM user WHERE group_id = 1 AND area_id = 1

上記のクエリを使用するとしてgroup_idで既にINDEXが存在するのであれば、group_id順での検索が行えるため、必要ないケースがあります。

逆に必要あるケースはgroup_idの種類が少ない場合で、ソートされても同じ順位に並ぶ数が多く、対象データが絞りきれないためです。

例えば、100万件データが存在し、group_idが10件しか存在しない場合は、単純計算だと10万件の中から対象となる「area_id = 1」のデータを検索しなくてはならないため、あまり効率的ではありません。

この場合は、そもそもgroup_idのみでのINDEXがあまり意味をなしていない可能性が高いです。
(要件によると思いますが、負荷を考慮して仕様を変更する必要があるかもしません)

適切なINDEXを付けることによってパフォーマンスは確実に上がります。

「適切」を簡単にいうと、発行されるクエリに対してINDEXが使用されること、になります。

発行しているクエリにINDEXが使用されているかは各DBに分析用の機能があると思いますので、そちらを実行してみてください。

結果をみれば、INDEXの有無でパフォーマンスに影響することが分かると思います。

 

上記を踏まえて処理順を決める

データベースになるべくアクセスしない、効率的なアクセス方法を記述しましたが、処理順でも負荷を減らすことは可能です。

例えば、ある注文フォームからデータを受け取りチェックを行うとします。

受け取るデータは顧客ID、商品IDとします。

効率の良いチェックの処理順として考えられるのが

1.データが空でないか

2.商品IDが商品テーブル(マスタ)に存在するか

3.顧客IDが顧客テーブルに存在し、ステータスが問題ないか

となります。

理由としては1のチェックはデータ検索を行う必要がないため、負荷はほぼありません。

2のデータはマスタデータになるため、キャッシュを利用が可能です。

3はトランザクションテーブルにアクセスするため、それなりの負荷が予想されます。

といったことになります。

これを3から行うようにすれば、チェックで必ずデータベースがアクセスされ、データベースの負荷に貢献してしまいます。

といったことで、処理順でも負荷を抑えることが可能です。

 

 

 

DBへのアクセスについて常に意識してプログラムを書く必要がありますが、この辺りを理解していれば、それなりに負荷を避けられると思います。

夜中にデータベースサーバーが落ちて、叩き起こされないようにしたいものですね。

マジで。。。

 

 

最後まで読んで頂きありがとうございました。

また何かあれば書かせて頂こうと思います。

2 thoughts on “大規模サービスでプログラマが注意すること(基礎)

  1. Pingback: PHP初心者向けのコードの最適化 | GeNERACE labo

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です