start

既存と異なる構成のvdevをプールに追加しても特に問題はないらしい

ZFSで既存プールにストレージを追加する場合、既存のvdevと同じ構成のvdevとするのが基本である。具体的に言うと、HDD 3台のRAID-ZプールにHDDを追加するには、HDD 3台を追加しなければならない。HDD 4台を追加しようとしても「mismatched replication level: pool uses 3-way raidz and new vdev uses 4-way raidz」と怒られてしまう。実際のログはこんな感じ。

$ zpool status
  pool: ztank
 state: ONLINE
  scan: none requested
config:
 
        NAME        STATE     READ WRITE CKSUM
        ztank       ONLINE       0     0     0
          raidz1-0  ONLINE       0     0     0
            da0     ONLINE       0     0     0
            da1     ONLINE       0     0     0
            da2     ONLINE       0     0     0
 
errors: No known data errors
 
# zpool add ztank raidz da3 da4 da5 da6
invalid vdev specification
use '-f' to override the following errors:
mismatched replication level: pool uses 3-way raidz and new vdev uses 4-way raidz

だがしかし、ログにもある通り-fオプションを付けると、異なる構成のvdevでも難なく追加出来てしまう。

# zpool add -f ztank raidz da3 da4 da5 da6
# zpool status
  pool: ztank
 state: ONLINE
  scan: none requested
config:
 
        NAME        STATE     READ WRITE CKSUM
        ztank       ONLINE       0     0     0
          raidz1-0  ONLINE       0     0     0
            da0     ONLINE       0     0     0
            da1     ONLINE       0     0     0
            da2     ONLINE       0     0     0
          raidz1-1  ONLINE       0     0     0
            da3     ONLINE       0     0     0
            da4     ONLINE       0     0     0
            da5     ONLINE       0     0     0
            da6     ONLINE       0     0     0
 
errors: No known data errors

この通り。

forceオプションが必要な事からも分かるように、これは非推奨のプール構成である。かといって、何か問題があるかというと実は然程問題ないらしい。vdevの使われ方に偏りが出たり性能が落ちる可能性はあるものの、危険だったり有害だったりはしないそうだ。まぁ、本当に危険だったら、この操作そのものが許されてないよね。

ZFSの仕組み上、プールの特性は構成する各vdevの最も低い特性の影響を受けるので、vdevの特性は揃えておくのが望ましい事から「非推奨」となっているようだ。名前の通りvdevを仮想的な1台の物理ストレージに置き換えて考えると分かりやすいかなと。

ゆえに上記プール例では速度がHDD 3台のraidz1-0に引っ張られる事になる。また、物理HDDが全て同じ容量だとすると、raidz1-1の方が大きいので容量の消費も偏る事になるが、raidz1-0とraidz1-1はストライピングであることから総容量は全て使われる(ハズ)。ミラー構成時の容量にだけ気をつけとけば、そんなに神経質になることもないのかも。少なくとも家庭用NAS用途なら殆ど問題にならない気がする。速度面ではネットワークが最大のボトルネックだしね…。

よくよく考えると、うちのサーバはHDD×3でRAID-Zの所に空き容量低下でHDD×3を追加したもんだから、vdev間の偏りたるや相当なもの。zpool iostatで見てもストライピングというより最早JBOD状態で、こんなのでもちゃんと動いてるんだからvdevの構成違いなんて誤差みたいなものでしょう、きっと。

C#のCreateDocumentTypeがタイムアウトする時の簡易対策

C#のXmlDocumentでHTMLを生成しようと、W3CのDTDを指定してXmlDocument.CreateDocumentType()するとタイムアウトやHTTPステータスコード500で例外を吐くことがある。こちとらvalidなHTMLを生成しようと真面目に指定してんのに、この仕打である(´・ω・`)。みんなW3Cを見に行って慢性的な高負荷状態になってるのが原因らしいが、まぁ当然そうなりますわな…。

コードにすると↓な感じ。

XmlDocument doc = new XmlDocument();
XmlDocumentType docType = doc.CreateDocumentType(
	"HTML",
	"-//W3C//DTD HTML 4.01 Frameset//EN",
	"https://www.w3.org/TR/html401/frameset.dtd",
	null); /* ここでエラー */

XmlResolverでローカルキャッシュしたDTDをマッピングするのが正攻法らしいのだが、ぶっちゃけ面倒。というか、調べても良くわからんかったのでパスw(分かったら追記する…多分)

とりあえずエラーを回避するだけなら、CreateDocumentType実行前にXmlDocument.ResolvernullにしてやればOKっぽい。コードにするまでもないが一応書いておく。

XmlDocument doc = new XmlDocument();
doc.Resolver = null; // 追加
XmlDocumentType docType = doc.CreateDocumentType(
	"HTML",
	"-//W3C//DTD HTML 4.01 Frameset//EN",
	"https://www.w3.org/TR/html401/frameset.dtd",
	null); /* エラーにならない */

・・・と、ここまで書いて思ったが、これで回避できるって事は自前実装したXMLリゾルバでDTD返してやればいいだけなんじゃね?

P4で「Client 'foo' can only be used from host 'bar'」と言われた時の対処方法

Perforceのワークスペースは基本的にマシンと紐付いているため、複数マシン間で使い回すことができない。

普通は使い回しはしないので問題はないのだが、故障などでマシンを交換した時にちょっと面倒なことになる。利用可能なワークスペース一覧に表示されず、たとえ無理やり選択しても「Client 'WORKSPACE' can only be used from host 'OLD-HOSTNAME'」と怒られて使えないのだ。

ちなみに新旧マシンのホスト名を一緒にしておけば問題は起きず、まぁ新マシンで新たにワークスペースを作ってしまうのが正攻法なんだろうけど、巨大なデポだと再取得するのも嫌じゃん? そんな時はp4 clientコマンドでワークスペースが持っているホスト名を変更すれば良い。手順は下記の通り。

  1. p4コマンドが使える状態にする。
  2. p4 client を実行。
  3. ワークスペースの情報がテキストエディタで表示されるので、Host: OLD-HOSTNAME となっているところを Host: 新マシンのホスト名 に変更して保存。
  4. テキストエディタを終了すると、Client WORKSPACE saved.と表示されワークスペース情報が更新される。
  5. 念のためp4 infoで「Client host:」が書き換わってるか確認。

以上で新マシンから既存のワークスペースが使えるようになるハズ。

C#のジェネリックで特殊化っぽいことをする

新年明けましておめでとうございます(遅。開設14年目となるクソゲ~製作所を本年もよろしくお願い致します。

Cのテンプレートには特殊化という、特定の型パラメータの時にテンプレートの実体を別に定義する機能がある。だが、C#版テンプレートとも言えるジェネリックでは、なんということでしょう、特殊化が使えないではありませんか!それをどうにかしてジェネリック特殊化っぽい事をしてみたっていうお話。 こんなコードがあったとする。 <code csharp> public class Node { // 子ノード public List<Node> Children; // 特定の子ノードを取得 public List<T> FindChildren<T>() : where Node { List<T> list = new List<T>(); Type type = typeof(T); foreach (Node e in Children) { if (type == e.GetType()) { list.Add(e); } list.AddRange(e.FindChildren<T>()); } return list; } } public class DocumentNode : Node {} public class PageNode : Node {} public class TitleNode : Node { public string Title; public TitleNode(string title) { Title = title; } } public class SectionNode : Node { public TitleNode Title; } static void Main() { DocumentNode doc = new DocumentNode(); PageNode page = new PageNode(); doc.Children.Add(page); SectionNode section1 = new SectionNode(); section1.Title = new TitleNode("はじめに"); page.Children.Add(section1); SectionNode section2 = new SectionNode(); section2.Title = new TitleNode("つぎに") page.Children.Add(section2); // 全セクションを取得 List<SectionNode> allSections = doc.FindChildren<SectionNode>(); //正しく取得できる // 全タイトルを取得 List<TitleNode> allTitles = doc.FindChildren<TitleNode>(); //★正しく取得できない!! ... } </code> <code> ◆データ構造 [DocumentNode] -Children -[PageNode] -Children -[SectionNode] -Title -Children -[SectionNode] -Title -Children </code> 文章を模したデータ構造を作り、''FindChildren''メソッドで型をパラメータに子ノードを取得しているが、 ''doc.FindChildren<SectionNode>()''は正しい挙動をするものの、''doc.FindChildren<TitleNode>()''の方は空のリストが帰ってくる。''FindChildren()''は''Node.Children''しか見てないので、独立したメンバ''SectionNode.Title''が入るはずはなく当然の挙動である。 こんな時、C++なら特殊化で''FindChildren<TitleNode>''専用の処理が書け同一のインタフェースを提供できるのだが、前述の通りジェネリックは特殊化が使えないの。C#的には''FindChildTitleNodes()''的な別メソッドで提供するのが正しいのかもしれないが、やっぱり統一感に欠けて美しくない(そもそもこんな糞いデータ構造にすんなって話だが例ってことで許してね。) で、知恵を振り絞って''FindChildren''を以下のようにした。 <code csharp> public List<T> FindChildren<T>() : where Node { List<T> list = new List<T>(); Type type = typeof(T); if (type == typeof(TitleNode)) // (1) { List<T> titles = new List<T>(); // (2)-a var sections = FindChildrenInternal<SectionNode>(); section.ForEach(e => { titles.Add(e.Title as T); }); // (3) return titles; // (2)-b } return FindChildrenInternal<T>(); } List<T> FindChildrenInternal<T>() : where Node { List<T> list = new List<T>(); Type type = typeof(T); foreach (Node e in Children) { if (type == e.GetType()) { list.Add(e); } list.AddRange(e.FindChildren<T>()); } return list; } </code> 元の''FindChildren()''を''FindChildrenInternal()''とし、''FindChildren()''には型パラメータ''T''に応じた処理を書くようにした。C++でコンパイラが自動で行ってくれる特殊化による処理の振り分けを、実行時に手動で行うような感じかしら。原理上、実行時コストは増えるが、この程度なら大した影響はないだろう。JIT様もあることだし。 型が増えると''if''と特殊化処理の嵐になるが、型ごとに処理デリゲートを作って''Type''とデリゲート辞書でも作ればすっきりするので大した問題ではない。そもそも、そんな状況は最早「特殊化」の本質から外れてるので設計から見直すべきだろう。 このコードのミソは(1)~(3)の部分。 (1)で処理すべき''T''の型が明確になったものの、(2)-aで''List<T>''としているのはコンパイルを通すため。''List<TitleNode>''でも良さそうに見えるが、''T == TitleNode''になるとは限らないので(2)-bで型不一致エラーになる。''List<TitleNode>''とした場合に''T = SectionNode''で呼び出した時にどうなるかを考えれば、すぐにおかしさに気づいて頂けるかなと。 (3)も一見''titles.Add(e.Title)''で良さそうだが、これまた''TitleNode''が必ずしも''T''に変換可能とは限らないのでCS1503エラーとなる。よって、ちょっとアホくさいが''TitleNode''を自身の型''T''にキャストしてやらなければならない。 メソッドの引数に型パラメータを持てる場面では、[[https://arbel.net/2007/11/22/c-partial-specialization-with-extension-methods/ にも軽量な型情報システムがあればいいのになぁ…。

永遠に讃えよ我が芋煮

PCのHDD整理していたら表題の歌詞が出てきた。確かtwitterで見かけて素晴らしいクオリティに感動しコピペしたものだった気がする。

今軽くググってみたらヒットしなかったので、ここにコピペして保存する。なくすには惜しい出来なので。

永遠に讃えよ我が芋煮
(山形神聖醤油帝国 国歌)

気高きは 醤油の意志
示せ あまねく宇宙に
理想貫く 味
芋の加護は 我らと共に
あり続けん
ガーレ=イモニン
誇りある 鋼の大鍋

2015年最後の日記がこれってどうなのよ。

  • start.txt
  • 最終更新: 2022-07-27 15:26
  • by Decomo