■gfprep,gfpcopy のスケジューリング

- gfprep.c 自身でスケジューリングをおこなっている。
  libgfarm のスケジューリングを使用していない。
  ローカルを優先しない。

- r10374 (2.6 branch) 以前のスケジューリング方法

  - ファイルサイズ + gfarm_get_minimum_free_disk_space() よりも
    disk_avail が小さいホストは、常に書き込み対象外とする。

  - gfsd ホストごとに現在読み書きしている処理数を ncpu で割った小数値が
    小さいホストから順番に、次の読み込み元または書き込み先を選択する。

  - 書き込み先ホストを決める際、上記の値が等しいホスト同士の場合、
    disk_avail が大きいホストを優先する。起動直後は上記の値がすべて 0
    なので、disk_availが一番大きいホストが選択されるが、その後は、読み
    書き中ではない、disk_avail が大きいホストが書き込み先となる。

  - gfpcopy の場合、読み込み元や書き込み先が Gfarm では無い場合は、
    ホストの状態を考慮した計画を行わない。

  - gfprep -x で複製を減らす場合は、disk_avail が小さい順に既存複製を削
    除する。

  - 1 ホストあたり、現在読み書きしている数が ncpu よりも大きい場合は、
    コピーしようとしているファイルを保留して後で処理する。デフォルトで
    は 1000 個 (-F オプション) のファイルを先読みしているが、そのキュー
    の最後に追加しなおすことで保留する。gfprep の場合、他のファイルであ
    れば、書き込み先ホストが異なるため、書き込めるかもしれないため、保
    留して別のファイルを先に試す。あるファイルに対して 2 回目のスケジュー
    リング時にもまだ読み書き数が ncpu より大きい場合は、保留せずに、子
    プロセスのコピー処理が一つ完了するまで待つ。

- r10374 (2.6 branch) 以降のスケジューリング方法に追加された条件

  - ロードアベレージが schedule_busy_load_thresh * ncpu を超えている
    gfsd ホストを書き込み対象外とする。
    その条件によりで書き込み先が不足した場合は、エラーや警告を出して続
    行する。その場ではリトライしない。
    gfpcopy や gfprep を再実行する必要がある。
  - -B で busy なホストだとしても従来通り書き込み対象とする。
  - ロードアベレージと disk_avail をデフォルト 300 秒間隔で更新する。
  - -I でロードアベレージや disk_avail を更新する間隔を指定できる。

- r10450 (2.6 branch) 以降のスケジューリング方法

  - 変更の目的
    - gfpcopy,gfpcopy コマンドを同時に複数実行した場合でも、特定のホス
      トにアクセスが偏らないようにする。従来は disk_avail が大きいホス
      トを優先し、乱数などを使用していなかった。
    - しかし、disk_avail が大きいホストをなるべく優先して書き込み先とし
      たい。

  - gfsd ホストごとに現在読み書き中の数を使用した書き込み先候補の選定は
    行わない。書き込み先候補の選定は後述の方法に変更。読み込み元の選定
    方法は従来と同じ。また、読み書き中の数が ncpu よりも大きくなった場
    合に保留する処理も従来と同じ。

  - disk_avail が大きいホストを優先して書き込み先候補にするために、
    disk_avail が最大のホストから 20% 小さいホストまでを空きが特に大き
    いホストとして候補にする。0.8 を掛けて比較し、候補を絞り込む。
    (例: 最大が 1TB の場合、800GB 以上あるホストが対象)

  - 上記の候補数が並列数 (-j オプション) の 2 倍よりも小さい場合は、
    20% までに絞り込む前のホスト (disk_avail が不足しておらず、busy で
    ないホスト) の候補全ての中から選ぶ。
    (disk_avail が大きいホストの数が特に多くはないので、全体から選ぶこ
    とにするという目的。)
    例えば、並列数が 4 で disk_avail が特に大きいホストの候補が 6 個の
    場合は、6 に絞らず、その他候補全ての中からなるべく順番に選ぶ。

  - ホストごとに書き込み回数カウンタを持たせる。(long long)
    書き込み候補とするたびにそのカウンタを +1000 する。

  - そのカウンタの初期値に 0 ～ 999 の乱数を割り当てる。
    つまり、初期に選択するホストが不定となる。
    gfprep,gfpcopy を複数同時実行する場合でも書き込みが偏らないことを期
    待する。

  - 書き込み回数カウンタが一番小さいホストから順番に選ぶ。
    書き込みに適したホスト候補の中から、ラウンドロビン相当で書き込み先
    を選択することになる。

  - カウンタが大差になっているホストがあれば、その差を縮める。
    3000 以上差がついたホストについては、カウンタが最大のホストから
    -3000 + 乱数(0～999) に補正する。
    disk_avail が大きいグループに、あとから追加されたホストは、カウンタ
    が小さいままである可能性があるため。
    もし、カウンタが大差となると、カウンタが小さいホストは、しばらくそ
    のホストばかりが書き込み先候補となり、アクセスが偏ってしまうため。

    -3000 + 乱数としたのは、最近書き込んだホストのカウンタはそのままに
    し、3 回以上連続して選択されないようにするため。


■gfprep,gfpcopy のディレクトリエントリ並列読み込み方法

- 実装は gfarm_dirtree.c

- gfprep,gfpcopy は、ディレクトリエントリを取得する処理とデータコピー処
  理を複数プロセスで同時に (非同期に) おこなう。
  さらに、ディレクトリエントリの情報を取得自体も並列処理する。

  gfmd がクライアントから離れている場合、ディレクトリエントリを取得する
  処理がネックとなりやすいため、並列処理することでコピー処理をしていな
  い時間をなるべくゼロにしている。

  また、gfprep でほとんどのファイルが複製数が満たされている場合や、
  gfpcopy の差分コピーをする場合など、データ転送が少ない場合は、メタデー
  タアクセスが大半の処理時間となるため、並列にエントリ情報を読み込んで、
  処理時間を短縮している。

  メインループの親プロセス側で Gfarm プロトコルを使って通信する処理とし
  て gfs_mkdir(), gfs_symlink(), gfs_lchmod() などがあり、親プロセス側
  の処理時間としては大きい可能性があるが (なるべく親プロセス側では通信
  したくないが)、子プロセスでファイルコピー処理しながらそれらを実行する
  ため、それらはネックにならないと想定している。

  # 子プロセスでディレクトリ作成もできそうだが、していない。

- main() とは別スレッドで以下の子プロセスを管理する。

  子プロセスは複数 (-J オプション) 動作する。

  下記 2 種類のキューから処理する対象のエントリを複数のプロセスが取り出
  しながら並列に処理する。
    - エントリ情報取得用キュー
    - ディレクトリを辿る用キュー

  上記のキューと複数の子プロセスを使って情報を取得できたエントリについ
  ては、main() 側に返すためのキュー (デフォルト 1000 個、-F オプション)
  を介して返す。

  つまり、データを転送しながら、デフォルトでは 1000 エントリを先読みで
  きる。

  エントリ情報取得処理を優先し、その完了を待ってからディレクトリを辿る
  ため (後述)、ディレクトリの構成によっては、それぞれのキューの合計が並
  列数 -J よりも小さくなることがあるので、常に最大並列数でディレクトリ
  エントリの情報を取得しているとは限らない。

- 最初に指定したディレクトリを子プロセスで読み、見つかった全てのエント
  リをエントリ情報取得用キューに入れる。さらに、ディレクトリが見つかっ
  た場合は、ディレクトリを辿る用キューにも追加する。
  その際、転送元がGfarmの場合は、gfs_readdirplus() で stat 情報も取得し
  ておく。

- エントリ情報取得用キューが空でなければ、エントリ情報を取得する。

  転送元が Gfarm ファイルの場合は gfs_replica_list_by_name() を取得する。
  転送先 stat 情報を取得する。エントリの存在有無も記録しておく。
  転送先が Gfarm ファイルの場合にエントリが存在する場合は
  gfs_replica_list_by_name() を取得する。
  情報を取得したエントリを main() に返すためのキューに入れる。

  # gfs_replica_list_by_name() 相当の情報も、gfs_readdirplus() のように
  # 取得できるなら、gfmd への負担が減るかもしれない。

  子プロセスでエントリ情報取得処理中の数をカウントしておく。完了したら
  カウンタを減らす。これが 0 であれば ディレクトリを辿る用キューを処理
  することができる。(後述)

- エントリ情報取得用キューが空で、エントリ情報取得中の数が 0 で、ディレ
  クトリを辿る用キューが空でなければ、そのキューから取り出して子ディレ
  クトリを読む。

  読んだ結果、エントリ情報取得用キューとディレクトリを辿る用キューに追
  加する。

  エントリ情報取得処理を優先する理由は、親ディレクトリの情報をその子エ
  ントリの情報よりも先に main() 側に返すため。転送先ディレクトリを
  main() で作成する。もし、親ディレクトリを作成せずに、先に子エントリの
  情報を main() に返してしまうと、コピーに失敗する。

--
以上
