技術の犬小屋

Webプログラミングを餌に生きる犬のメモ帳

Posts in the PHP category

仕事でPHPのユニットテストを書いている。普段はユニットテストの手法としてブラックボックステストを行っているが、他の方法も調べたのでメモを残しておく。
 

代表的なテスト手法

やみくもにテストを増やしても、費やした時間に対して効果的なテストをしているとは限らない。
ここで紹介する代表的なテスト手法に従ったテストを書くことで短時間で十分なテストを作ることが出来る。
 
ユニットテストには、主に大枠として以下の2つのテスト方針が存在する。

  • ホワイトボックステスト
  • ブラックボックステスト

 
以下より、それぞれのテスト方針の詳細とその中で行われるテスト手法を説明する。
 

ホワイトボックステスト

ホワイトボックステストとは、ソースコード中の文や分岐、条件などを実行してバグを見つけるテストである。実行してバグを見つけるので、どの文、分岐、条件も1回は実行する必要がある。つまり、ホワイトボックステストでは、テストで実行した文、分岐、条件の割合が、テストの進捗を計る指標となる。この割合を「カバレッジ率(網羅率)」という。
ホワイトボックステストでは、カバレッジ率が100%になるのに必要なだけテストケースを作成する。
また、テストで確認する対象(文、分岐、条件)により、ホワイトボックステストの指標は、ステートメントカバレッジ(命令網羅)、ブランチカバレッジ(分岐網羅)、コンディションカバレッジ(条件網羅)の3つに分類されている。
 

ステートメントカバレッジ(命令網羅)

ソースコード中にある全ステートメント(命令文)のうち、1回でもテストで実行されたステートメントの割合。ステートメントカバレッジの「ステートメント」とはコードの「行」のことである。ステートメントカバレッジでは「分岐」については考えず、if文がtrueであろうが、falseであろうが、とにかくその行が実行されれば良い。開発現場ではC0カバレッジと呼ばれる。
 

ブランチカバレッジ(分岐網羅)

ソースコード中にある全ブランチ(分岐)のうち、1回でもテストで実行された分岐の割合。分岐で条件全体が成立したとき(trueのとき)と不成立のとき(falseのとき)の両方が実行される必要がある。開発現場ではC1カバレッジと呼ばれ、ブランチカバレッジはステートメントカバレッジよりも強い評価基準となり,ブランチカバレッジが100%の場合は,必然的にステートメントカバレッジも100%になる。
 

コンディションカバレッジ(条件網羅)

分岐に設定されている1つ1つの条件について、成立したときと不成立のときの両方がテストで実行された割合。1つの分岐に複数の条件が設定されている場合、各条件ごとに実行の有無を確認する。一見するとブランチカバレッジと似ているが、定義は異なる。例えば、以下のようなテスト対象のコードがあるとする。

if(a == 0 && b < 0) {
……
}

この場合、a = 0b = 0のテストケースとa = 1b = -1のテストケースを作成してテストを行えば、コンディションカバレッジのカバレッジ率は100%になる。しかし、2つのテストケースともif文自体はtrueにならないので、ブランチカバレッジのカバレッジ率は100%にはならない。
 
これら以外にも更に強い評価基準を持ったホワイトボックステストの指標が存在する。しかし、強い評価基準であればあるほど、相対的にテストの作成には時間がかかるようになる。
 

ブラックボックステスト

ブラックボックステストとは、ある入力に対し、仕様通りの正しい結果が返されるかどうかを確認するテストである。ホワイトボックステストとは異なり、ブラックボックステストではソースコードを見ない。内部処理は考えず、入力とそれに対する結果(戻り値)を照らし合わせてテストの合否を判定する。
ブラックボックステストでは、次に挙げる「同値分割法」と「境界値分割法」を用いる。これらはどちらか一方を行えば良いのではなく、必ず両方とも行う必要がある。
 

同値分割法

プログラムが期待する入力値である「有効同値」とそれ以外の入力値である「無効同値」のそれぞれに代表値を用意し、それらを入力値としてプログラムを実行した結果を確認する方法。たとえば、有効同値が0以上かつ1000未満の場合、無効同値は0未満かつ1000以上になる。
 

境界値分割法

有効同値と無効同値の境界となる値を入力値としてプログラムを実行し、その結果を確認する方法。例えば、有効同値が0以上かつ1000未満の場合、無効同値は0未満かつ1000以上で、その境界値は-1、0、999、1000となる。
 

どのテスト手法を採用すればいいのか

ホワイトボックステスト、ブラックボックステストのどちらか片方だけ行っても十分なユニットテストとはいえない。テストをする観点がソースコードなのか(ホワイトボックステスト)、振る舞いなのか(ブラックボックステスト)で異なるためである。カバレッジ率が高くても振る舞いが間違っていたらバグとなり、同様に振る舞いが合っていてもカバレッジ率を意識しないとコードの中でテストをしていない箇所に気付くことが出来ず、バグを生んでしまう可能性がある。
 
とはいえ、我々がユニットテストにかけることが出来る時間は限られている。全てのテストケースをテストクラスで記述しよう、などと気負ったりせず、ある程度自分の納得するまでテストケースを記述したあとは、以降のテストでテストケースが必要になったときにテストケースを追加するように考えるほうが気が楽である。
 
 
以上
 
 
参考
書籍 プログラミング現場の単体テスト

PHP 5.4で調査や検証を行い,分かったことがあったのでまとめておく。
 

プリミティブ型と参照型

PHPの変数の型には,大きく分けて「プリミティブ型」と「参照型」の2つが存在する。ここでは説明を省きたいので,それぞれの違いについては,この記事をご閲覧いただきたい。
 

PHPにおける値渡しと参照渡し

上記の説明から,プリミティブ型の変数への代入では「値渡し」が行われ,参照型の変数への代入では「参照渡し」が行われていることが理解出来る。
 
PHPのコードを書いて実験してみる。

$a = 1;
$b = $a;
$a = $a + 1;
echo $b; //1 を出力する

 
プリミティブ型を変数に代入するときは,値渡しが行われるので,$bに$aを代入した後で,$aに対して操作を行っても$bには影響を与えない。
 

$a = new DateTime('2000-01-01');
$b = $a;
$a->add(new DateInterval('P1D')); //1日加算 2000-01-02
echo $b->format('Y-m-d');         //2000-01-02 を出力する

 
参照型を変数に代入するときは,参照渡しが行われるので,$bに$aを代入した後で,$aに対して操作を行うと$bに影響を与える。
 

僕の知ってる参照渡しと違うんだけど…

あなたの知っている参照渡しは,おそらく以下のようなコードではないだろうか。

$a = 1;
$b = &$a;
$a = $a + 1;
echo $b; //2 を出力する

 
プリミティブ型を変数に代入するときは,通常の場合は値渡しが行われるが,&を使うことで参照渡しを行うことが出来るので,$bに$aを代入した後で,$aに対して操作を行うと$bに影響を与える。
 
あまり聞かないが,これは「値の参照渡し」とでも呼べば覚えやすい気がする。
 
※ちなみにメソッドに対して,値の参照渡しを行うときは,「参照返し」も一緒に行わないと, return されたときに値のコピーが発生してしまうので注意が必要である。
 

参照型のコピーを作成する方法

では,PHPで参照型のコピーを作成するにはどうすれば良いのか。上記で示した参照型の代入の例のように,単純に参照型を変数に代入するだけでは,変数がお互いに影響を与えあってしまうので,これでは使い勝手がよくない。
 
そこで,PHPにはcloneという命令が用意されており,これを使うことで参照型が格納された変数のコピーを作成することが出来る。
 

<?php
$a = new DateTime('2000-01-01');
$b = clone $a;
$a->add(new DateInterval('P1D')); //1日加算 2000-01-02
echo $b->format('Y-m-d');         //2000-01-01 が出力される

 
参照型を変数に代入するときは,通常の場合は参照渡しが行われるが,cloneを使うことで値渡しを行うことが出来るので,$bに$aを代入した後で,$aに対して操作を行っても$bには影響を与えない。
 
これは「参照の値渡し」と呼ばれる。
 

まとめ

変数に何かを代入するときは,以下の4つのことを意識する。

  • 値渡し(値の値渡し)
  • 参照渡し(参照の参照渡し)
  • 値の参照渡し
  • 参照の値渡し

 
 
以上
 
 
参考
PHP: リファレンス渡し – Manual
オブジェクト変数の値渡し – PHPリファレンス
PHPが糞言語なのはどう考えても参照をポインタだと思っているお前らが悪い – なんたらノート第三期ベータ

Redisの使い方を書籍で勉強したので,まとめておく。
 

Redisとは

メモリ上にKey-Valueストア(KVS)を構築することができるソフトウェアの一つ。
KVSは任意の保存したいデータ(値:value)に対し,対応する一意の標識(キー:key)を設定し,これらをペアで保存するデータベースの一種で,Redisはコンピュータのメインメモリ上にKVSを構築し,外部のプログラムからデータの保存と読み出しができる。
保存する値(value)として様々なデータ構造を利用することができ,文字列,バイナリデータ,リスト,集合(セット),ハッシュなどを保存することができる。
 

Redisの5種類のデータ構造
型名 格納されている情報 データ構造の読み書き機能
STRING 文字列,整数,浮動小数点数 文字列全体,文字列の一部の操作,整数や浮動小数点数のインクリメント/デクリメント
LIST 文字列の連結リスト 両端の要素のプッシュ,ポップ,オフセットによるトリミング,個々/複数の要素の読み出し,値による要素の探索,削除
SET 一意な文字列の順序のないコレクション 個々の要素の追加,フェッチ,削除,メンバーかどうかのチェック,積集合,和集合,差集合,ランダムな要素のフェッチ
HASH 順序のないキーと値のハッシュテーブル 個々の要素の追加,フェッチ,削除,ハッシュ全体のフェッチ
ZSET 浮動小数点数のスコア順に並べられた文字列メンバーからスコアへのマッピング 個々の要素の追加,フェッチ,削除,スコアの範囲,メンバーの値に基づく要素のフェッチ

 

Redisの特徴と用途

Redisは,メモリ上で動作するキー・バリュー型の揮発性データベースである。揮発性と聞くと,Redis自身が停止したり,Redisが動作しているハードの電源が切れたりすると,Redisに保存されているデータは消失してしまうように思われるが,Redisはデータをディスクに書き込み,永続化させる機能も持ち合わせているのでそれを回避できる。特徴としては,メモリ上にデータを格納するので,非常に高速にデータの書き込み・読み込みを行うことができる。キャッシュ用のデータベースとしても優れているが,単体でもデータストアとして利用可能なように設計されており,100を超える様々なコマンドを備えている。
 

レプリケーションによる負荷分散

Redisには,他のコンピュータに複製を作る機能があり,更新が可能な1台の「マスター」と,その複製である読み出し専用の複数台の「スレーブ」という構成にすることで大規模化を図ることができる。この負荷分散の方法を「レプリケーション」と呼ぶ。
 

データの永続化

Redisには,データを永続化(ディスクへの書き込み)を行う仕組みとして,SnapshotとAppend Only Fileの2つの方法が用意されている。
 

スナップショット(Snapshot)

スナップショットでは,設定が有効な場合,Redisは定期的にデータベースの内容をディスクに出力する。Redisを再起動すると,このファイルからデータが読み込まれ復元される。一定回数の更新・一定間隔でディスクにファイルを出力する。ファイルの出力タイミングは設定ファイル,CONFIGコマンドで変更可能である。出力は非同期で行われるため,プロセスがクラッシュした場合には前回のスナップショット以降のデータが失われる可能性があるので,多少のデータロスは許容できる必要がある。手動でスナップショットを保存する場合は,SAVEコマンドまたはBGSAVEコマンドで行うことができる。出力されたファイルの形式はバイナリとなる。
 

スナップショットの設定項目(/etc/redis.conf)
設定項目 内容 書式
save データベースをディスクに保存するタイミングを設定することができる。与えられた秒数経過するか,指定された回数分,書き込み命令を受け付けるとデータベースを保存する。 save seconds changes
rdbcompression .rdb形式のデータベースにダンプするときに,圧縮するかどうかを設定することができる。デフォルトでは’yes’になっており,常に圧縮するようになっている。もし保存時にCPUパワーを節約したい場合は’no’を設定する。 rdbcompression yes
dbfilename DBをダンプするときのファイル名を指定する。 dbfilename dump.rdb
dir 作業ディレクトリを設定します。DBは,このディレクトリ内に,dbfilename設定ディレクティブで設定された名前で書き出される。追記専用ファイルモードもこのディレクトリ内に作成される。 dir ./

 

追記専用ファイル(Append Only File)

Append Only Fileは,書き込み操作を全てログファイルに記録する。スナップショットでのデータロスの可能性を許容できない場合にこれを使う。スナップショットと併用可能で,ドキュメントではAppend Only Fileの単体運用は推奨されていない。スナップショットと併用した場合,再起動時にはAppend Only Fileの方が使用される。ファイル出力タイミングは設定で変更可能で,デフォルトでは1秒毎となっている。出力されるファイルの形式はテキストで,ファイルサイズはどんどん肥大化していくので,定期的に再構成する必要がある。バージョン2.2までは手動でBGREWRITEAOFコマンドを実行し,再構成する必要があったが,バージョン2.4からは自動的に再構成を実行できるようになった。
 

Append Only Fileの設定項目(/etc/redis.conf)
設定項目 内容 書式
appendonly もしデータが大切で,データを失いたくないのであれば,これを有効にすべきである。このモードが設定されると,Redisはappendonly.aofに書き込み操作を受け取るたびにすべて記録していく。このファイルは起動時に全データセットをメモリ内に構築していくときに読み込まれる。 appendonly yes
appendfilename 追記専用ファイルの名前を設定する。デフォルトはappendonly.aofである。 appendfilename appendonly.aof
appendfsync オペレーティングシステムに対して,出力バッファにデータが貯まるのを待つのではなく,データをディスクに書き出すように指示することができる。OSによっては実際にデータをディスクに書き出したり,なるべく速く書き出すようにしたりする。Redisは次の3つのモードをサポートしている。no:fsyncしない。データの書き出しはOSに任せるので,高速に動作する。always:追記専用ログに書き込むたびにfsyncを行う。低速だが安全に動作する。everysec:最後のfsyncから1秒経過するとfsyncを行う。上記の2つの中間に位置する。 appendfsync everysec
no-appendfsync-on-rewrite Append Only Fileのfsyncポリシーがalwaysかeverysecに設定されており,バックグラウンドのセーブ用のプロセス(バックグラウンドのセーブか,Append Only Fileのログのバックグラウンド書き込み)がディスクに対して大量のI/Oを発生していたとすると,Linuxの設定によっては,fsync()呼び出し時にRedisが長時間ブロックしてしまう可能性がある。現在では修正方法がないため,別スレッドのfsync呼び出しは,同期的な書き込みの呼び出しまでブロックさせてしまう。このオプションを使うと, BGSAVEやBGREWRITEAOFが実行中は,メインのスレッドではfsync()呼び出しが行われないようになり,この問題を回避することができる。これはつまり,他の子スレッドが保存している間は,appendfsync noneが設定されているのと同じ動作をするようになるため,利用者の視点で説明するとすれば,クラッシュした場合に,最悪のシナリオを想定すると,最大で30秒のログが失われる可能性があるということだ。もし遅延時間の問題を持っているのであれば,yesを設定する。そうでない場合には,安全性の観点からnoを選択して,この問題は放置するようにする。 no-appendfsync-on-rewrite no

 

Redisの使い方

今回はredis-cliからの使用方法と,PHP言語からの使用方法の2つを説明する。redis-cliとは,Redisをインストールしたときに付属するクライアントプログラムである。ちなみにRedisには,redis-serverというサーバプログラムも付属している。
 
初めに,yumコマンドやapt-getなどのコマンドを使って,Redisをインストールし,Redisを起動する。

#Redisがインストールされているか確認する
which redis
sudo find / -name redis

#インストールされていなければ,Redisをインストールする
sudo yum --enablerepo=epel install redis

#Redisを起動する
sudo redis-server /etc/redis.conf

#Redisが起動したことを確認する
ps aux | grep redis

#Redisが自動起動するように設定する
sudo chkconfig redis on

 
ちなみにRedisを終了させたいときは,Redisをインストールしたときに付属するredis-cliというRedisのクライアントプログラムからshutdownコマンドを実行することで終了させることができる。
 

redis-cliからRedisを使ってみる

redis-cliからRedisを使う方法を説明する。
 
redis-cliからRedisに接続し,データの書き込み・読み込みを行う。

#Redisに接続する Redisはデフォルトでは,6379番ポートをリッスンしている
redis-cli
 
#データの保存
#set key value
set wanko bow-wow
+OK
 
#データの読み出し
#get key
get wanko
"bow-wow"

#データの削除
#del key
del wanko
(integer) 1

#キーに有効期限を設定したい場合はsetexを利用する
#setex key expires value
setex wanwan 60 wow
+OK

get wanwan
wow

#60秒後にはデータが消えている
get wanwan
(nil)

 
単純な文字列型データだけではなく,リスト型のデータを操作することもできる

#dogというリストを作成し,先頭にfooというデータを追加
#lpush key value
lpush dog wan
:1

#dataというリストの先頭にbarというデータを追加
lpush dog wow
:2

#dataというリストの先頭(0)から末尾(-1)までを取得
#lrange key start end
lrange dog 0 -1
1) "wow"
2) "wan"

#dogというキーに紐付くデータを削除
del dog
(integer) 1

 

Redisで利用できるコマンドの抜粋
データ型 コマンド名 コマンド内容 引数
文字列型 SET keyにvalueを保存する key, value
文字列型 SETEX keyにvalueをexpires付きで保存する key, expires, value
文字列型 GET keyに対応する値を取得する key
リスト型 LPUSH keyに対応するリストの先頭にvalueを加える key, value
リスト型 RPUSH keyに対応するリストの末尾にvalueを加える key, value
リスト型 LLEN keyに対応するリストの要素数を返す key
リスト型 LRANGE keyに対応するリストの指定したindex範囲(start..end)の要素を返す key, start, end
リスト型 LTRIM keyに対応するリストを指定したindex範囲(start..end)にトリムする key, start, end
リスト型 LINDEX keyに対応するリストのindexにある要素を返す key, index
セット型 SADD keyに指定されたmemberを保存する key, member
セット型 SCARD keyに対応する要素の数を返す key
セット型 SMEMBERS keyに対応するセット内のすべてのメンバを返す key
セット型 SUNION 指定されたkey1, key2, …の和集合を返す key1, key2, …
セット型 SINTER 指定されたkey1, key2, …の積集合(共通メンバ)を返す key1, key2, …
ソート済みセット型 ZADD keyに対して,指定されたmemberをscoreに保存する key, score, member
ソート済みセット型 ZRANGE keyに対応する要素のうち,指定したindex範囲(start..end)の要素を返す key, start, end
ソート済みセット型 ZRANGEBYSCORE keyに対応する要素のうち,指定したスコア範囲(min..max)の要素を返す key, min, max
ハッシュ型 HSET keyに対応するハッシュの指定されたfieldにvalueを保存する key, field, value
ハッシュ型 HGET keyに対応するハッシュの指定されたfieldに対応する値を返す key, field
ハッシュ型 HKEYS glob形式のパターンを使って,存在するfieldの一覧を返す glob形式のfieldパターン
ハッシュ型 HEXISTS keyに対応するハッシュ内に指定したfieldがあれば1を,無ければ0を返す key, field
ハッシュ型 HDEL keyに対応するハッシュ内のfieldを削除する key, field
型によらず DEL keyに対応するデータを削除する key
型によらず KEYS glob形式のパターンを使って,存在するkeyの一覧を返す glob形式のパターン 例)user:*
型によらず EXISTS 指定されたkeyが存在すれば”1″を,無ければ”0″を返す key
型によらず RENAME keyをold_keyからnew_keyにリネームする old_key, new_key
型によらず TYPE 指定されたkeyに対応するデータの型を返す key

 

PHPからRedisを使ってみる

PHPからRedisを使う方法を説明する。PHPからRedisに接続するためのモジュールは,主にpredisとphpredisの2つがある。この2つの大きな違いは,predisはphpで普通に実装が行われているのに対し,phpredisはPHPのエクステンション(C言語)で実装されていることである。これらがやっていることが,ほとんど同じ内容なのであれば,C言語で作られたモジュールの方が動作が速いはずなので,今回はphpredisを使ってPHPからRedisを操作してみる。

#Cコンパイラが必要なのでgccをインストールする
sudo yum install gcc

#Gitでソースを取得する
git clone git://github.com/nicolasff/phpredis.git

cd phpredis

#phpizeとは,エクステンションを追加する際に,
#phpを最初からコンパイルし直す必要なく,追加するためのコマンド
phpize

#コンパイルする
./configure
make
make install

 
インストールが完了したら,エクステンションとして読み込ませるために,php.iniにextension=redis.soを追記する。
 
読み込まれたモジュールにRedisが追加されているかを確認する。

php -m | grep redis

 
これでphpredisの導入が完了した。
続いて,PHPでRedisの基本操作を行ってみる。

<?php
$redis = new Redis();
$redis->connect("127.0.0.1",6379);

//set(key, value)
$redis->set("dog","baw-baw");

//get(key)
$res = $redis->get("dog");
echo $res;

 
シェルにbaw-bawと出力されれば,コードが上手く実行できている。
上記のコードは動作確認済みなので,そのままコピペして試すことができる。
 
 
以上
 
 
参考
書籍 Redis入門 インメモリKVSによる高速データ管理
書籍 NoSQLデータベース ファーストガイド
書籍 NOSQLの基礎知識 ビッグデータを活かすデータベース技術
ec2にyumでredisをinstall – Qiita
Redisとは 【 Remote dictionary server 】 – 意味/解説/説明/定義 : IT用語辞典
redisドキュメント日本語訳 redis 2.0.3 documentation
PhpRedisでphpからredisを使う – 恥知らずのウェブエンジニア
phpredisのインストール – it-boyの日記

memcachedについて,書籍を読んで勉強したので使用方法をまとめておく。
 

memcachedとは

memcachedは,分散メモリキャッシュシステムを構築することができるソフトウェアの一つ。データベースの読み出し結果などをメモリに保存し、次に同じデータが参照されたときにメモリから即座に返すことができる。複数のコンピュータにまたがる巨大な分散キャッシュシステムを構築でき、台数を増やすことで性能を向上させることができるため、大規模なWebサービスなどでよく用いられる。
 

memcachedの特徴と用途

memcachedは,メモリ上で動作するキー・バリュー型の揮発性データベースである。揮発性なので,memcached自身が停止したり,memcachedが動作しているハードの電源が切れたりすると,memcachedに保存されているデータは消失してしまう。そのため,消えてしまっては困るようなデータを保存することはできない。memcachedはメモリ上にデータを格納するので,非常に高速にデータの書き込み・読み込みを行うことができる。
以上に挙げた特徴から分かるように,memcachedはキャッシュを行うような場面において,その真価を発揮する。memcachedの具体的な用途としては,MySQLやPostgreSQLのようなRDBと組み合わせて,キャッシュ用のデータベースとして使用することで,RDBにかかる負荷を抑えることができる。
 

コンシステント・ハッシングによる負荷分散

memcachedは,コンシステント・ハッシング(Consistent Hashing)と呼ばれるアルゴリズムによってデータ分割・割り当て(負荷分散)を行う。
このアルゴリズムでは,分散型システムを構成する各ノード(memcached)が,論理的にリング(輪)を形作るように配置される。
以下がコンシステント・ハッシングの図である。
 
コンシステント・ハッシングの図
 
図では,4つのノード(memcached)を均等に分散配置している。データのキーの値はハッシュ関数によって求められ,ハッシュ関数を使った演算は,不規則な発番(整理番号の付与)を行う方法として広く利用されている。このようにキーの値として,ハッシュ値を得ることによってキーの値が特定の値に偏ることなく,広く均等に拡散するようになる。
そして,その整理番号に従って,リングの各スペースにキーを割り当てる。キーが割り当てられたスペースを時計回りで進み,そこで最初に配置されているノードにデータを書き込む。コンシステント・ハッシングはこのようなルールのアルゴリズムである。
 

CAS操作によるアトミック性の実現

memcachedにおいて,アトミック性(原子性)を実現するにはCAS操作を行う必要がある。アトミック性とは,トランザクションに含まれるタスクが全て実行されるか、あるいは全く実行されないことを保証する性質をいう。このような性質は,MySQLやPostgreSQLのようなRDBでは標準的に対応しているが,memcachedにおいては,CAS操作と呼ばれる処理を行わなければ,対応することができない。
アトミック性(原子性)が保障されない場合,どのようなことが起こり得るか,簡単な例を示す。
以下の例では,「プロセス1」と「プロセス2」の2つのプロセスが1つのカウンタに対して数値を足している。
 
プロセス1 現在のカウンタの値を取得する …… カウンタの値:0
プロセス1 カウンタの値を1増やす ……………… カウンタの値:1
プロセス2 現在のカウンタの値を取得する …… カウンタの値:0
プロセス1 カウンタの値を保存する …………… カウンタの値:1
プロセス2 カウンタの値を1増やす …………… カウンタの値:1
プロセス2 カウンタの値を保存する …………… カウンタの値:1
 
上記の例では,本来はカウンタの値が2になることを期待しているが,最終的にカウンタの値は1になってしまっている。CAS操作を行わない場合はこのようなことが起こり得る。
 

memcachedの使い方

今回はtelnetからの使用方法と,PHP言語からの使用方法の2つを説明する。
初めに,yumコマンドやapt-getなどのコマンドを使って,memcachedをインストールし,memcachedを起動する。

#memcachedがインストールされているか確認する
which memcached
sudo find / -name memcached

#インストールされていなければ,memcachedをインストールする
sudo yum install -y memcached

#memcachedのリッスンするポートの変更やメモリの割り当てなどを変更することもできる
sudo vi /etc/sysconfig/memcached

#memcachedを起動する
sudo service memcached start

#memcachedが起動したことを確認する
ps aux | grep memcached

#memcachedが自動起動するように設定する
sudo chkconfig memcached on

 

telnetからmemcachedを使ってみる

telnetからmemcachedを使う方法を説明する。まずは,yumやapt-getなどのコマンドを用いてtelnetのインストールを行う。

#telnetがインストールされているかを確認する
which telnet
sudo find / -name telnet

#インストールされていなければ,telnetをインストールする
sudo yum install -y telnet

 
telnetからmemcachedに接続し,データの書き込み・読み込みを行う。

#telnetでmemcachedに接続する memcachedはデフォルトでは,11211番ポートをリッスンしている
telnet localhost 11211

#データの保存
#set key,flag,expires,byte[改行]
#value
set foo 0 100 3
bar

#データの読み出し
get foo

 
いろいろ操作を行ってみる。

#データの保存
set counter 0 0 1
1

#加算(increment)
#+1
incr counter 1

#減算(decrement)
#-1
decr counter 1

#データの保存
set test 0 0 4
test

#データの先頭へ追加
prepend test 0 0 3
bow

#データの末尾へ追加
append test 0 0 5
wanko

#データの削除
delete test

#memcached上のすべてのデータを削除
flush_all

 

名前 説明 注意点
flag データを圧縮するかどうかの指定。0:非圧縮,1:圧縮
expires データをいつまで保持するかをUNIXタイムスタンプもしくは現在からの秒数で指定する 現在からの秒数は30日(60*60*24*30=2592000)を超えることはできない。0を指定すると有効期限無し(半永久的に保持)になる。
byte valueとして保存するデータのバイト数を指定する

 

PHPからmemcachedを使ってみる

PHPからmemcachedを使う方法を説明する。今回はApacheとPHPが既にインストールされているという前提で話を進める。
 
まずは,PECLのmemcachedライブラリをインストールする。

#PHPからmemcachedを使うためのライブラリをインストールする
sudo yum install -y php-pecl-memcached

 
続いて,memcachedにデータを書き込むPHPプログラムと,memcachedからデータを読み込むPHPプログラムを作成する。
 

<?php
//memcachedにデータを書き込むPHPプログラム
$m = new Memcached();
$m->addServer('localhost', 11211);
$m->set('key', 'wanko', 60); // キー,バリュー,有効期限(秒)

 

<?php
//memcachedからデータを読み込むプログラム
$m = new Memcached();
$m->addServer('localhost', 11211);
if ($val = $m->get('key')) {
    echo $val;
}

 
上記のプログラムでは,keyというキーに対して,wankoというバリューを格納し,1分間だけmemcachedが情報を保持している。
 

CAS操作の実践

CAS操作では,getsとcasという2つのコマンドを使ってデータの不整合を発生させないようにする。getsはデータを読み出すコマンド,casはデータを保存するコマンドである。まず,getsコマンドを使うとデータだけでなく,cas idと呼ばれる値も取得できる。cas idはそのデータ識別する一意の値であり,データが更新されると必ず変更される。そして,データの保存時にはcasコマンドに対して保存するデータと一緒にcas idを渡す。このとき,casコマンドに渡されたcas idと,現在のデータを表すcas idが一致したときのみ保存が行われる。cas idが一致しない場合には,getsコマンドで取得してからデータが変更されているということであり,データの保存は失敗する。このように,保存時にそのデータが別のプロセスによって変更されていないかを確認することで,データの不整合が発生しないようにしている。
 

telnetからCAS操作を行ってみる

telnetでCAS操作を行うと,以下のようになる。

#データの保存
#set key,flag,expires,byte[改行]
#value
set hoge 0 1500 10 
1111111111
STORED

#数値の1111111111とcas id(6)を取得できる
gets foo
VALUE hoge 0 10 6
1111111111
END

#cas idを渡して,新しい値に書き換える
cas foo 0 1500 10 6
2222222222
STORED

 
上記では,4~6行目にてデータの格納を行い,9~12行目でデータの取得とcas idの取得を行っている。更に15~17行目でkey, flag, expires, byte, cas idを渡し,値を書き換えている。
 

PHPからCAS操作を行ってみる

PHPでCAS操作を行うと,以下のようになる。

<?php
$m = new Memcached();
$m->addServer('localhost', 11211);

//memcachedからデータを取得
//キー,コールバック関数名,CASトークンを格納する変数
$data = $m->get('key', null, $cas);

//get()で,キーが見つからなかった場合は新たにキーとデータを追加する
if ($m->getResultCode() == Memcached::RES_NOTFOUND) {

    //キー,バリュー,有効期限(秒)
    $m->add('key', 'value', 60);

//get()で,キーが見つかった場合は,CASトークンを使ってデータを更新する
} else { 

    //CASトークン,キー,バリュー,有効期限(秒)
    $m->cas($cas, 'key', 'value', 60);

}

 
これはこちらのページにあったコードを,自分なりに読みやすく書き換えたものである。動作確認済みなので,このままコピペして試すことができる。Memcached::get()はコールバック関数の実行が行えたりと,少し変わった使い方もできるので,こちらのページを参考にして使い方を確認してみてほしい。
 
 
以上
 
 
参考
書籍 NoSQLデータベース ファーストガイド
書籍 NOSQLの基礎知識 ビッグデータを活かすデータベース技術
memcachedとは 【 memory cache daemon 】 – 意味/解説/説明/定義 : IT用語辞典
ACID (コンピュータ科学) – Wikipedia
[CentOS] memcachedをインストールして、PHPから使用する | HAPPY*TRAP
PHP: Memcached::cas – Manual
PHP: Memcached::get – Manual
PHP: Memcached::add – Manual
PHP: Read-through キャッシュコールバック – Manual

WordPressのテンプレートタグについて勉強した。テンプレートタグの使い方について,簡単にメモを残しておく。
 

テンプレートタグとは

テンプレートタグとは,WordPressにおいて,ブログのデータを動的に表示したり,ブログをカスタマイズしたりするときに,テンプレートの中で使われるPHPのコードである。
 

テンプレートタグの種類

テンプレートタグは,大きく以下の2つに分かれる。

  1. ループので使うテンプレートタグ
  2. ループので使うテンプレートタグ

 
WordPressにおいて,記事や固定ページの取得など,特定の操作を行うためにはループを行う必要がある。WordPressは,ループによって繰り返し,記事や固定ページを走査し,指定した条件に合致するページが見つかった場合は,HTMLとして出力する。
 

テンプレートタグの使い方

上記でテンプレートタグの種類について言及したが,まず初めに「ループの外で使うテンプレートタグ」について説明する。
 

ループの外で使うテンプレートタグ

「ループの外で使うテンプレートタグ」には,使用頻度の高いものとして,bloginfo()というテンプレートタグがある。これは,WordPressで作成されたサイトの基本的な情報を取得するためのテンプレートタグである。
以下の表にbloginfo()の使用方法をまとめる。
 

bloginfo()の使い方
コード 意味
<?php bloginfo(‘url’); ?> ブログのURLを出力する。
<?php bloginfo(‘name’); ?> ブログの名前を出力する。
<?php bloginfo(‘description’); ?> ブログのキャッチフレーズを出力する。

 

ループの中で使うテンプレートタグ

続いて,「ループの中で使うテンプレートタグ」について説明する。
以下に,WordPressにおけるループのサンプルコードを示す。
 

<?php
    
    if( have_posts() ){    //投稿があるか調べる
        //投稿がある場合の処理

        while( have_posts() ){    //投稿された記事の数だけ繰り返す

            the_post();        //投稿情報を読み込む
            the_title();       //タイトルを表示する
            the_content();     //本文を表示する

        }
    } else {
        //投稿が無い場合の処理
        echo '記事がありませんでした。';
    }
?>

 
上記は,WordPressにおける最も基本的なループの記述方法である。このサンプルコードでは,WordPressに投稿された記事をwhile文によって走査し,記事が存在する場合に限り,投稿情報・タイトル・本文を出力する。
 
以下は,上記のサンプルコードを適切に分割し,扱いやすくしたコードである。可読性は落ちるが,このように記述することで使用面で様々な応用が利くようになる。
 

<?php if( have_posts() ) : ?>
    <?php while( have_posts() ) : the_posts(); ?>
        <?php the_post(); ?>
        <?php the_title(); ?>
        <?php the_content(); ?>
    <?php endwhile; ?>
<?php else : ?>
    <p>記事がありませんでした。</p>        
<?php endif; ?>

 
これはWordPressに倣った書式である。このように記述することで,複数人でテンプレートを編集した場合でも,一定以上の可読性を保つことができる。
 

ループに条件を設定する

ここで,ループに条件を設定する方法を説明する。記事や固定ページを取得する際に,最新の投稿から2番目の記事のみを取得したい,という場合があるとする。そのような場合はループに条件を設定することで出力を操作することができる。
ループに条件を設定するための基本的なテンプレートタグとして,query_posts()がある。ループを行う直前にquery_posts()を記述することで,query_posts()に渡された引数をループの条件として設定することができる。
以下のコードでは,query_posts()を使用し,ループに条件を設定している。
 

<?php query_posts( array( 'post_type' => 'post', 'posts_per_page' => 1, 'offset' => 1 ) ); ?>
<?php if( have_posts() ) : ?>
    <?php while( have_posts() ) : the_posts(); ?>
        <?php the_post(); ?>
        <?php the_title(); ?>
        <?php the_content(); ?>
    <?php endwhile; ?>
<?php else : ?>
    <p>記事がありませんでした。</p>        
<?php endif; ?>

 
post_typeには,取得したい投稿のタイプを指定し,posts_per_pageには,記事の取得数を指定している。offsetには,最新の記事から何個目の記事を取得するかを指定している。このサンプルコードでは,通常の記事のうち,最新のものから2件目のみを取得している。
 
WordPressのテンプレートタグについての説明は以上で終了となる。これ以上のテンプレートタグの詳細情報については,WordPress Codex 日本語版を参考にしてほしい。
 
 
以上
 
 
参考
WordPressで投稿記事情報の取得方法
テンプレートタグ/query posts – WordPress Codex 日本語版
WordPressテンプレートタグとは