読者です 読者をやめる 読者になる 読者になる

Iruca Log

東京で暮らすWeb系エンジニアが日々感じたこと

Memcachedの通信プロトコルの違いによるパフォーマンスを比較してみた(Binary か Text(Ascii)か)

memcachedJavaから使用している際にクライアントライブラリとしてspymemcachedを使用している人は多いですよね。
クライアント・サーバ間の通信に用いるプロトコルとして"ASCII"モードと"BINARY"モードの2種類がありますが、
どちらがどれくらい早いのか気になったので調査してみました。
結果はこちら。

f:id:iruca21:20160123233927j:plain
f:id:iruca21:20160123233937j:plain

Get(同期型)はBinaryプロトコルの方が35%速い。
が、Set(非同期型)はどちらもそんなに変わらない、といったところでした。
spymemcachedのget関数は同期型(sync)、
set関数はデフォルトで非同期型(async)で動いているので、実際はsetが本当に完了した時間を調べるとまた違ったグラフが出てくると思われます。

しかし、2byte~4096byteくらいであればgetの性能はほとんど変わらないんだなあ…。


何はともあれ、みなさんがspymemcachedを使うときの1つの指標になれば幸いです!


プロトコル仕様については下記のブログが紹介してくださっているのでご参照ください。
http://blog.ohgaki.net/memcached-protocol-and-security

実行環境:
クライアント側ライブラリ: spymemcached 2.8.4
サーバ側: memcached 1.4.24-2ubuntu1 amd64
備考:
・簡単のためシングルスレッドで実行
・同じLAN内のホストのmemcachedに対して実行

実験に使ったソースコードは以下に貼っておきますね!ではでは。

package net.iruca;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.spy.memcached.AddrUtil;
import net.spy.memcached.ConnectionFactory;
import net.spy.memcached.ConnectionFactoryBuilder;
import net.spy.memcached.MemcachedClient;

import org.apache.commons.lang3.RandomStringUtils;

/**
 * Memcachedサーバに接続して指定回数のキャッシュ読み込み/書き込みを1スレッドで実行する。
 * MemcachedのBINARYプロトコルとASCIIプロトコルの性能差を測るために作成した(2016/01/23)
 * 
 * @author iruca
 * 
 */
public class MemcachedTest {

	// memcachedサーバのホスト名:ポート番号(通常11211). (e.g. "192.168.75.131:11211")
	private static String memcachedHosts;
	// 実行時引数: 0=BINARY, 1=TEXT protocol
	private static ConnectionFactoryBuilder.Protocol protocol;
	// 0=書き込み(set), 1=読み込み(get)
	private static int operationType;
	// 書き込みか読み込みを行う回数
	private static int numOfOperations;
	// 書き込むキャッシュの長さ(byte)。この長さのasciiランダム文字列をキャッシュに詰め込む
	private static int cacheLength;
	// 書き込みを行う場合に設定するTTL(秒)
	private static int ttl;
	// この文字列に "1", "2", .... "[numOfOperations]"を付与した値をcache key文字列としてget/setする
	private static String keyPrefix = "key";
	// cacheLengthの長さの乱数文字列をキャッシュの値として与える
	private static String value;
	// memcached接続に使用するclient
	private static MemcachedClient client;

	/**
	 * 実行方法: java -cp ./memcachedtest.jar net.iruca.MemcachedTest
	 * [memcachedホスト名] [プロトコル(0=TEXT, 1=BINARY)] [試行の種類(0=set(書き込み),
	 * 1=get(参照))[試行回数] [キャッシュ値の長さ(byte)] [ttl(秒)]
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			// 実行時引数読み込み
			readArgs(args);
		} catch (Exception e) {
			System.out.println(e.toString());
			System.exit(-1);
		}

		// キャッシュバリューをランダムASCII文字列で生成
		value = RandomStringUtils.randomAscii(cacheLength);

		ConnectionFactoryBuilder builder = new ConnectionFactoryBuilder();
		// プロトコル(BinaryかText(Ascii))を設定
		builder.setProtocol(protocol);
		// spymemcached独自のログが吐かれてしまうのを抑制するための設定変更
		System.setProperty("net.spy.log.LoggerImpl",
				"net.spy.memcached.compat.log.SunLogger");
		Logger.getLogger("net.spy.memcached").setLevel(Level.WARNING);
		ConnectionFactory factory = builder.build();
		// 試行開始時間
		long startTime = 0;
		try {
			client = new MemcachedClient(factory,
					AddrUtil.getAddresses(memcachedHosts));

			// 試行開始時間を記録
			startTime = System.currentTimeMillis();

			// 指定のオペレーションを実行
			if (operationType == 0) {
				executeSet();
			} else {
				executeGet();
			}

		} catch (IOException e) {
			e.printStackTrace();
		}

		// 試行終了時間を記録
		long endTime = System.currentTimeMillis();

		// 実行にかかった時間を出力
		System.out.println(String.valueOf(endTime - startTime));

		System.exit(0);
	}

	/**
	 * 実行時引数を変数にセットする
	 * 
	 * @param args
	 */
	private static void readArgs(String[] args) throws Exception {
		if (args.length != 6) {
			throw new Exception(
					"Usage: java -cp ./memcachedtest.jar net.iruca.MemcachedTest [memcachedホスト名] [プロトコル(0=TEXT, 1=BINARY)] [試行の種類(0=set(書き込み), 1=get(参照))[試行回数] [キャッシュ値の長さ(byte)] [ttl(秒)]. 参照を行う場合でも便宜上キャッシュ値の長さとTTLを適当に指定してください・・・・");
		}
		memcachedHosts = args[0];

		if (Integer.valueOf(args[1]) == 1) {
			protocol = ConnectionFactoryBuilder.Protocol.BINARY;
		} else {
			protocol = ConnectionFactoryBuilder.Protocol.TEXT;
		}

		operationType = Integer.valueOf(args[2]);

		numOfOperations = Integer.valueOf(args[3]);
		cacheLength = Integer.valueOf(args[4]);

		ttl = Integer.valueOf(args[5]);
	}

	/**
	 * numOfOperationsの回数だけ、valueの値を書き込む試行を行う
	 * 
	 * @param value
	 */
	private static void executeSet() {
		String key;
		for (int k = 0; k < numOfOperations; k++) {
			key = keyPrefix + String.valueOf(k);
			// set実行
			client.set(key, ttl, value);
		}
	}

	/**
	 * numOfOperationsの回数だけ、読み込みの試行を行う
	 */
	private static void executeGet() {
		String key;
		for (int k = 0; k < numOfOperations; k++) {
			key = keyPrefix + String.valueOf(k);
			// get実行
			client.get(key);
		}
	}
}