最初の部分では、ZFSのvdevでデータがどのように編成されるかを説明しました。 2番目の部分では、現時点で録音が行われる実際の場所を選択するアルゴリズムの仕組みについて説明します。
ここで、タスクを少し複雑にします-最初の部分では、1つのvdevのみを説明しました。 アルゴリズムは、データブロックを書き込むvdevとvdev内のmetaslabの両方を選択する必要があるため、ここにいくつかあります。 本番システムには数十のvdevが存在する可能性があり、それらにデータを正しく分散させることが重要です。すべてのデータをコピーしないと、バランスを取り直すことはできません。 正しいアルゴリズムの目的は、各デバイスでほぼ同じ量になるようにデータを並列化し、不均一な充填を滑らかにすることです
NAME STATE READ WRITE CKSUM tank ONLINE 0 0 0 c1t6d0 ONLINE 0 0 0 c1t5d0 ONLINE 0 0 0
まず、重要な注意事項:ZFSは、プール内のすべてのデバイスが同じサイズになるように設計されています。 そうでない場合、たとえば、1TBのディスクのプールに2TBのディスクを追加すると、2TBのディスクは2倍のデータをもたらし、システムの合計IOPに影響を与え始めます-アロケーターアルゴリズムは、バイト単位のデータ量ではなく、充填率を考慮します。
ZFSには現在4つのアロケーターアルゴリズムがあります。 変数
zfs_metaslab_ops
は、
space_map_ops_t
構造体へのポインターが含まれます。この構造体には、特定のアルゴリズムが使用する7つの関数ポインターがあります。 たとえば、Illumosは
metaslab_df
アルゴリズムを使用し、関数ポインターを含む対応するページは次のようになります。
static space_map_ops_t metaslab_df_ops = { metaslab_pp_load, metaslab_pp_unload, metaslab_df_alloc, metaslab_pp_claim, metaslab_pp_free, metaslab_pp_maxsize, metaslab_df_fragmented };
これらの関数のうち5つは、すべてのアルゴリズムで使用されます。 実際の違いは、
metaslab_*_alloc()
および
metaslab_*_fragmented()
-アロケーター自体、および特定のメタスラブの空き領域をどのように断片化するかを決定する関数。 使用できるアロケータ:DF(ダイナミックフィット)、FF(ファーストフィット)、および2つの実験的なもの、CDFとNDF-誰もその意味を知りません。
それらのFFは最も単純です-AVLツリーを順番にトレースするときにデータの断片を最初の利用可能な空きスペースに書き込み、収まらない場合は記録ブロックをセグメントに分割します。 このため、FFは非常に遅いアルゴリズムです。1つのブロックを書き込むには、十分な数のセグメントが入力されるまでツリー全体をトレースする必要があるためです。 たとえば、1GBのデータを記録する場合、最悪のケースは512バイトの2000万セグメントであり、結果として非常に強力なデータの断片化です。 FFは、他のアルゴリズムが最後のオプションとして使用します。異なる場所を見つけることができない場合-たとえば、このメタスラブの空きスペースが4%未満の場合、DFはFFを使用します(
int metaslab_df_free_pct = 4;
)。 FFの唯一のプラスは、断片化されたメタスラブを100%満たすことができるのはそれだけであることです。
* DFアルゴリズムの動作は異なります-現在記録されている
freemap
フリー
freemap
フリースペース
freemap
を構築し、
freemap
フリースペースのサイズや近接度によってソートし、速度の観点から最適なデータ配置オプションを選択しようとします記録、ディスクヘッドの動きの数、記録されたデータの最小限の断片化。
たとえば、
metaslab_weight()
関数はそれらすべてに対して機能します。これにより、ディスクプレートの外側の領域に配置されたメタスラブが優先されます(短ストローク効果のため)。 SSDのみを使用する場合、アルゴリズムのこの部分を無効にしてZFSを調整することは理にかなっています。ショートストロークはSSDには適用できないためです。
したがって、データは
ZIO パイプラインからアロケーターアルゴリズムに入ります-そこから
metaslab_alloc()
関数(書き込み用のアロケーター自体)と
metaslab_free()
(スペースを解放し、ガベージを収集します)。
metaslab_alloc(spa_t *spa, metaslab_class_t *mc, uint64_t psize, blkptr_t *bp, int ndvas, uint64_t txg, blkptr_t *hintbp, int flags)
そこで渡されます:
*spa
spa-データ配列(zpool)の構造へのポインター。 * mc-メタスラブのクラス。特に、
zfs_metaslab_ops
へのポインターが
zfs_metaslab_ops
ます。
psize
データサイズ。
*bp
ブロック自体へのポインター。
ndvas
特定のブロックに必要なデータの独立したコピーの数(1はデータ、2はほとんどのメタデータ、3はAVLツリーの上位のメタデータの場合。メタデータの複製のポイントは、メタデータを持つブロックが1つしかない場合ツリーセグメントが失われると、その下にあるすべてのものが失われます。このようなブロックは同じブロックと呼ばれ、アルゴリズムはそれらを異なるvdevに書き込もうとします)。
次に、
txg
は書き込み中のトランザクショングループのシーケンス番号です。
*hintbp
それらの隣のブロックも論理的にディスクの隣にあり、同じvdevに移動することを保証するために使用されるヒント。
*hintbp
アロケーターが特定の割り当てオプションを使用するかどうかを知ることができる5ビット-
*hintbp
を使用するか無視するか、およびギャングを使用するかどうか(より効率的な作業のために子ブロックのグループをヘッダーと同じvdevに書き込んでください) ZFSプリフェッチおよびvdevキャッシュ)。
define METASLAB_HINTBP_FAVOR 0x0 define METASLAB_HINTBP_AVOID 0x1 define METASLAB_GANG_HEADER 0x2 define METASLAB_GANG_CHILD 0x4 define METASLAB_GANG_AVOID 0x8
/* * Allow allocations to switch to gang blocks quickly. We do this to * avoid having to load lots of space_maps in a given txg. There are, * however, some cases where we want to avoid "fast" ganging and instead * we want to do an exhaustive search of all metaslabs on this device. * Currently we don
/* * If we are doing gang blocks (hintdva is non-NULL), try to keep * ourselves on the same vdev as our gang block header. That * way, we can hope for locality in vdev_cache, plus it makes our * fault domains something tractable. */
次に、実際には、コードの最も重要な部分があります。ここで、書き込み用のメタスラブを選択するロジックは次の
metaslab_alloc_dva()
です
metaslab_alloc_dva()
関数には約200行のトリッキーなコードがありますが、これについて説明します。
最初に、すべてのvdevのメタスラブのすべてのグループを取得し、アロケーターサイクル(
mg_rotor
)を使用して、もしあればヒントを使用します。 現在、記録が望ましくないvdevをスキップします。たとえば、ディスクの1つが死んだ、またはraidzグループが復元されているvdevです。 (障害が発生したデバイスからは割り当てないでください。)また、ディスク上にコピーが1つしかないデータについては、何らかの書き込みエラーが発生したディスクもスキップします。 (失敗したvdevにシングルコピーデータを書き込むことは避けてください。)
ローターは、記録のために送信されたデータがなくなるまで円を描いて動作します。 このサイクル内で、最適なvdevが順番に選択され、次に
metaslab_group_alloc()
を使用して最適なメタスラブが選択されます。次に、このメタスラブに書き込むデータ量を決定し、vdev'aの使用率を他のユーザーと比較します。 コードのこの部分は非常に重要であるため、完全に提供します。
offset = metaslab_group_alloc(mg, psize, asize, txg, distance, dva, d, flags); if (offset != -1ULL) { if (mc->mc_aliquot == 0) { vdev_stat_t *vs = &vd->vdev_stat; int64_t vu, cu; vu = (vs->vs_alloc * 100) / (vs->vs_space + 1); cu = (mc->mc_alloc * 100) / (mc->mc_space + 1); mg->mg_bias = ((cu - vu) * (int64_t)mg->mg_aliquot) / 100; }
たとえば、2つのディスクのアレイに1MBのデータを書き込む必要がある場合、
そのうちの1つが20%、2番目が80%の場合、最初に819KB、2番目に205KBを書き込みます。 修正:プールの空き領域とvdevの空き領域を比較するため、結果はわずかに異なります。 ここで、非常に興味深いことができます-数か月前に、ZFSの各vdevaのレイテンシ統計を追加しました(
vdev_stat_t->vs_latency[]
にあります;それらはまだIllumosに追加されていません)。新しいデータを記録する際に、そのスペースと空きスペースの両方を任意の割合で考慮するか、それだけを使用するかのいずれかの要因。 このような修正されたアルゴリズムも作成しましたが、実稼働システムではまだ使用されていません。 アレイ内に異なるタイプと速度のディスクがある場合、またはディスクの1つが停止し始める(スローダウンする)場合には意味がありますが、これまでのところそれほど悪くはなく、エラーもありません。
最後に、繰り返しの後、ループは書き込み用のすべてのデータをメタスラブグループに送信し、このトランザクショングループ(txg)で終了しますが、
metaslab_weight()
システムはメタスラブグループで動作し(記事の冒頭を参照)、
space map
システムを通じて、maxfreeを考慮します(連続した空き領域の最大部分)、AVLツリーのトレースと対応するアルゴリズム(DF、FF、CDF、NDF)を使用して、アルゴリズムに最適な方法でデータをプッシュします。その後、最終的にディスクに書き込むブロックの物理アドレスを取得します。データは
sd
(Scsiデバイス)ドライバーへの書き込み用にキューに入れられます 。