テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

(writeboost report) ctrがmessageの設定項目も設定可能にすべきか?

今, ライトブーストは, ctrでoptional parameterを2つ. messageで7つのチューニングパラメータをとる. 後者は動的に変更可能だから, ctrで受けずに「設定したいなら作ったあとに設定してください」という方針にしていたが, どうもこれでは問題が生まれそうである.

まず, dm-cacheはどうしているかを調べる.

set_config_valueは, messageに渡されたk-vから設定を行う. dm-cacheはmigration_threshould*1という, パラメータを設定する. これはたぶんキャッシュの空き容量が〜以下になったらマイグレーションを強制するというものであるように思う. このポリシーには同意しかねるが, とにかく動的に設定出来そうである.

static int cache_message(struct dm_target *ti, unsigned argc, char **argv)
{
        struct cache *cache = ti->private;

        if (argc != 2)
                return -EINVAL;

        return set_config_value(cache, argv[0], argv[1]);
}

set_config_valueはset_config_valuesで呼ばれ, k-v pairsを, 指定された個数分消費する. 以下は, .ctrの中のコード.

        cache->migration_threshold = DEFAULT_MIGRATION_THRESHOLD;

        r = set_config_values(cache, ca->policy_argc, ca->policy_argv);
        if (r) {
                *error = "Error setting cache policy's config values";
                goto bad;
        }

結果として, dm-cacheは, messageで設定可能なtunable parameterもctrで受ける. 動的に設定可能なのだから, ctr時にも設定可能で当たり前である(ところで, set_config_valuesはdm_arg_setを利用すべきである).

dm-cacheがそうしているからライトブーストもそうしなければいけないという縛りはないが, device-mapper-test-suiteを使うと問題が生まれる.

dm-cacheのテストについて, 典型的な例から下ってみる.

with_standard_cache以下では, activateが呼び出される. activateは, CacheStackを初期化するくらいの意味だと思う(bad nameだと思う). その上で, blockで指定された処理をキャッシュに対して実行する. ここではテストを実行する. このように, device-mapper-test-suiteは, その都度デバイスを再作成し, テストを行うという方針をとっている. 私はこの方針が好きだ(キャッシュの初期化は時間がかかることもあるでしょうが, ストレージのテストは副作用だらけだから, テストを出来るだけ純粋にすることが重要だ).

  def test_fio_database_funtime
    with_standard_cache(:cache_size => meg(1024),
                        :format => true,
                        :block_size => 256,
                        :data_size => gig(10),
                        :policy => Policy.new('mq')) do |cache|
      cache.message(0, "sequential_threshold 32768") # 16M
      do_fio(cache, :ext4,
             :outfile => "../fio_dm_cache.out",
             :cfgfile => "../tests/cache/database-funtime.fio")
    end
  end

  def with_standard_cache(opts = Hash.new, &block)
    stack = CacheStack.new(@dm, @metadata_dev, @data_dev, opts)
    stack.activate do |stack|
      block.call(stack.cache)
    end
  end

activateでは, with_devにcache_tableを読み込んでいる. これが曲者だ. device-mapper-test-suiteは, デバイスを作る時の引数としてテーブルを使っている. このテーブルがloadされることによってデバイスが作れられる. 従って, テスト作成者がデバイスに対してmessageを発行してデバイスをチューンする余裕はない.

  def activate(&block)
    with_devs(@tvm.table('md'),
              @tvm.table('ssd'),
              @data_tvm.table('origin')) do |md, ssd, origin|
      @md = md
      @ssd = ssd
      @origin = origin

      wipe_device(md, 8) if @opts.fetch(:format, true)

      with_dev(cache_table) do |cache|
        @cache = cache
        ensure_elapsed_time(1, self, &block)
      end
    end
  end

  def cache_table(mode = io_mode)
    Table.new(CacheTarget.new(origin_size, @md, @ssd, @origin,
                              block_size, mode + migration_threshold,
                              policy.name, policy.opts))
  end

  module LexicalOperators
    def with_dev(table = nil, &block)
      bracket(create(table),
              lambda {|dev| dev.remove; dev.post_remove_check},
              &block)
    end

    private
    def create(table = nil, read_only = false)
      path = create_path
      tidy = lambda {dm_interface.remove(path)}

      dm_interface.create(path)
      protect_(tidy) do
        dev = DMDev.new(path, dm_interface)
        unless table.nil?
          if read_only
            dev.load_ro(table)
          else
            dev.load(table)
          end
          dev.resume
        end
        dev
      end
    end

    def create_path
      # fixme: check this device doesn't already exist
      "/dev/mapper/test-dev-#{rand(1000000)}"
    end

  def protect_(release)
    r = nil
    begin
      r = yield
    rescue
      release.call
      raise
    end
    r
  end

ライトブーストについていうと, 例えばパフォーマンスを測る時にはライトバックを止めた方が良かったりする(キャッシュデバイスへのリードを避けるため. バッキングのリード性能を最大化するため). 非現実的なチューニングではあるが, 数値を出さなければオポを失う. オポの前には, 実験のリアリティなどというのは後回しで良い. 大体, ストレージの性能などというのはパラメータが多すぎて, 何がリアルかというのはケースに依ってしまう. だから, 最大限に都合が良いケースで数値を出して悪くならない.

結論として, ライトブーストは, ctrでtunable parameterをとる. これは実装的に難しくないが, 問題は, dmsetup tableでこいつらの情報がとれるのに, dmsetup statusでも同じ情報を出力する必要があるかということだ. dm-cacheは, している.

加えて, 以下のような問題も生む. ctrで設定されたチューニングがのちに動的に変更された場合, tableからの出力はこれを反映すべきか?dm-cacheは, していないように思う. 現在の値はstatusで得られるが, これは必ずしもtableとは同一でない. そんなアホな設計があるか!と思うのだが... tableはあくまでも初期化時の保存であるという考え方なのだろうか?

(追記)
他のターゲットを見ても, そもそも何か設定を動的に変更させるという発想があまりない. ライトブーストは7つなので異常である(デーモンがたくさん動く実装も, MikeからはOKが出てる). なので検証のしようがないが, テーブルがコロコロ変わってしまうということはもしかしたら危険かも知れない. ここは素直に, dm-cacheの方針に従うべきかも知れない.

例えば, あるユーザランドツールは, 最初にテーブルを書き込み, 終了時にはテーブルが同一であることで同一のものを終了していることを判定する可能性がある. ふざけた設計だとは思うが, 可能性を否定出来ない. もし, 「変更したパラメータもtableに反映せよ」と言われたらやればいいくらいのものだし, 実装コストも高くないので, dm-cacheに従おうと思う.

*1:弊社マンはregistだし, threshouldだ!