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

statsのcmd_setは、incrなども含むのか?

memcached

twitterで@nekoyaさんがつぶやいていて、memcachedは他のミドルウェアとかよりはソースコードはだいぶ簡潔だろうし、このぐらいならコード調べるのはわりとできそうだな、と思ったのでせっかくなので追ってみた。

結論としては、incrの更新の場合には、statsのcmd_setは更新されず、add/replace/set/append/prependの場合には、更新されるようです。以下は詳細。

というわけで、githubからcloneしてくる

        git://github.com/memcached/memcached.git
        git checkout 1.4.13 -b 1.4.13

とりあえず一番最新の安定版のタグである1.4.13をみてみることにする

とりあえず伝家の宝刀git grepする

            $ git grep cmd_set *.c
            memcached.c:    APPEND_STAT("cmd_set", "%llu", (unsigned long long)slab_stats.set_cmds);
            slabs.c:            APPEND_NUM_STAT(i, "cmd_set", "%llu",

予想通りわりと特定はすぐできたらしい。

            $ git grep set_cmds
            memcached.c:    stats.get_cmds = stats.set_cmds = stats.get_hits = stats.get_misses =
            stats.evictions = stats.reclaimed = 0;
            memcached.c:    c->thread->stats.slab_stats[it->slabs_clsid].set_cmds++;
            memcached.c:    c->thread->stats.slab_stats[it->slabs_clsid].set_cmds++;
            memcached.c:    APPEND_STAT("cmd_set", "%llu", (unsigned long long)slab_stats.set_cmds);
            memcached.h:    uint64_t  set_cmds;
            memcached.h:    uint64_t      set_cmds;
            slabs.c:                    (unsigned long long)thread_stats.slab_stats[i].set_cmds);
            thread.c:            threads[ii].stats.slab_stats[sid].set_cmds = 0;
            thread.c:            stats->slab_stats[sid].set_cmds +=
            thread.c:                threads[ii].stats.slab_stats[sid].set_cmds;
            thread.c:    out->set_cmds = 0;
            thread.c:        out->set_cmds += stats->slab_stats[sid].set_cmds;

ざーと見つつ想像すると、threadローカルなものと、グローバルなstatsの記述があるっぽい。とりあえずmemcached.cあたりでマッチしたやつを追っていく。

memcached.cで、

            /*
             * we get here after reading the value in set/add/replace commands. The command
             * has been stored in c->cmd, and the item is ready in c->item.
             */
            static void complete_nread_ascii(conn *c) {
                assert(c != NULL);

                item *it = c->item;
                int comm = c->cmd;
                enum store_item_type ret;

                pthread_mutex_lock(&c->thread->stats.mutex);
                c->thread->stats.slab_stats[it->slabs_clsid].set_cmds++;
                pthread_mutex_unlock(&c->thread->stats.mutex);

というのがあって、メソッド前のコメントを読むに、set/add/replaceのときに、set_cmdsは++されるようだ。コメントからだとincrがどうかはよくわからないのでもうちょい追ってみる

            static void complete_update_bin(conn *c) {
                protocol_binary_response_status eno = PROTOCOL_BINARY_RESPONSE_EINVAL;
                enum store_item_type ret = NOT_STORED;
                assert(c != NULL);

                item *it = c->item;

                pthread_mutex_lock(&c->thread->stats.mutex);
                c->thread->stats.slab_stats[it->slabs_clsid].set_cmds++;
                pthread_mutex_unlock(&c->thread->stats.mutex);

                /* We don't actually receive the trailing two characters in the bin
                 * protocol, so we're going to just set them here */
                *(ITEM_data(it) + it->nbytes - 2) = '\r';
                *(ITEM_data(it) + it->nbytes - 1) = '\n';

                ret = store_item(it, c->cmd, c);

#ifdef ENABLE_DTRACE
                uint64_t cas = ITEM_get_cas(it);
                switch (c->cmd) {
                case NREAD_ADD:
                    MEMCACHED_COMMAND_ADD(c->sfd, ITEM_key(it), it->nkey,
                                          (ret == STORED) ? it->nbytes : -1, cas);
                    break;
                case NREAD_REPLACE:
                    MEMCACHED_COMMAND_REPLACE(c->sfd, ITEM_key(it), it->nkey,
                                              (ret == STORED) ? it->nbytes : -1, cas);
                    break;
                case NREAD_APPEND:
                    MEMCACHED_COMMAND_APPEND(c->sfd, ITEM_key(it), it->nkey,
                                             (ret == STORED) ? it->nbytes : -1, cas);
                    break;
                case NREAD_PREPEND:
                    MEMCACHED_COMMAND_PREPEND(c->sfd, ITEM_key(it), it->nkey,
                                             (ret == STORED) ? it->nbytes : -1, cas);
                    break;
                case NREAD_SET:
                    MEMCACHED_COMMAND_SET(c->sfd, ITEM_key(it), it->nkey,
                                          (ret == STORED) ? it->nbytes : -1, cas);
                    break;
                }

もう一箇所set_cmdsを使っているところを調べると、complete_update_binという関数にぶちあたる。
set_cmds++しているところの下の方を見て想像するに、add/replace/append/prepend/set時にこのメソッドが呼びだされるようだ。

そして、最初にぶちあたった方をあとから見返してみると、complete_nread_asciiも下の方に同様の処理をしていたようだ。

            static void complete_nread_ascii(conn *c) {
                assert(c != NULL);

                item *it = c->item;
                int comm = c->cmd;
                enum store_item_type ret;

                pthread_mutex_lock(&c->thread->stats.mutex);
                c->thread->stats.slab_stats[it->slabs_clsid].set_cmds++;
                pthread_mutex_unlock(&c->thread->stats.mutex);

                if (strncmp(ITEM_data(it) + it->nbytes - 2, "\r\n", 2) != 0) {
                    out_string(c, "CLIENT_ERROR bad data chunk");
                } else {
                  ret = store_item(it, comm, c);

#ifdef ENABLE_DTRACE
                  uint64_t cas = ITEM_get_cas(it);
                  switch (c->cmd) {
                  case NREAD_ADD:
                      MEMCACHED_COMMAND_ADD(c->sfd, ITEM_key(it), it->nkey,
                                            (ret == 1) ? it->nbytes : -1, cas);
                      break;
                  case NREAD_REPLACE:
                      MEMCACHED_COMMAND_REPLACE(c->sfd, ITEM_key(it), it->nkey,
                                                (ret == 1) ? it->nbytes : -1, cas);
                      break;
                  case NREAD_APPEND:
                      MEMCACHED_COMMAND_APPEND(c->sfd, ITEM_key(it), it->nkey,
                                               (ret == 1) ? it->nbytes : -1, cas);
                      break;
                  case NREAD_PREPEND:
                      MEMCACHED_COMMAND_PREPEND(c->sfd, ITEM_key(it), it->nkey,
                                                (ret == 1) ? it->nbytes : -1, cas);
                      break;

                  case NREAD_SET:
                      MEMCACHED_COMMAND_SET(c->sfd, ITEM_key(it), it->nkey,
                                            (ret == 1) ? it->nbytes : -1, cas);
                      break;
                  case NREAD_CAS:
                      MEMCACHED_COMMAND_CAS(c->sfd, ITEM_key(it), it->nkey, it->nbytes,
                                            cas);
                      break;
                  }
#endif

メソッド名から想像するに、complete_nread_asciiは、asciiベースのプロトコルで更新した場合に呼びだされるhookで、complete_update_binは、バイナリプロトコルの場合で更新した場合に呼びだされるメソッドなのかな。

asciiの方だけ、casがあるのはなんでかなと思いつつ、http://gihyo.jp/dev/feature/01/memcached-1.4/0004 に言及されていた。

テキストプロトコルでは,CAS値はgetsコマンドを発行することにより取得しますが,バイナリプロトコルではほぼすべてのコマンドに対してレコードのCAS値を取得できる仕様になっています。バイナリプロトコルでは24バイト固定長リクエスト・レスポンスヘッダ内の8バイトをCAS値に割り当ており,その領域をつかってCAS値が送受信されます。

どうやら、そのあたりの仕組みはプロトコル上は、バイナリプロトコルには(必要ないので)ない、とのことのようだ。

追記

まだ結論にはいたっていませんでした。

incrの場合にどうなるか、を追っていたのであった。
さきほどの、complete_update_binの呼びだし元を調べてみると、一箇所で、ずばり、complete_nread_binaryという関数のようです。

static void complete_nread_binary(conn *c) {
    assert(c != NULL);
    assert(c->cmd >= 0);

    switch(c->substate) {
    case bin_reading_set_header:
        if (c->cmd == PROTOCOL_BINARY_CMD_APPEND ||
                c->cmd == PROTOCOL_BINARY_CMD_PREPEND) {
            process_bin_append_prepend(c);
        } else {
            process_bin_update(c);
        }
        break;
    case bin_read_set_value:
        complete_update_bin(c);
        break;
    case bin_reading_get_key:
        process_bin_get(c);
        break;
    case bin_reading_touch_key:
        process_bin_touch(c);
        break;
    case bin_reading_stat:
        process_bin_stat(c);
        break;
    case bin_reading_del_header:
        process_bin_delete(c);
        break;
    case bin_reading_incr_header:
        complete_incr_bin(c);
        break;

incrの場合の処理のときは、どうやら、complete_update_binは呼びだされないもよう。

ということで、cmd_setはincrを含まない、といえそうです。