不安定なホスト(非固定IPアドレス, 非常時稼働)をサーバーにする

需要があるらしいので、この話。

不安定なホスト(非固定IPアドレス―浮動IPアドレス, ステートフルIPアドレス, あるいは間違っているけれど可変IPアドレス― または 非常時稼働システム)をサーバーにする、というテーマで、 方法はたくさんあるのだが、私の結論は「商業レベルでやることではない」である。

待ち受ける

DDNS

最も普通の方法だが、実際にはかなり制約が多い。

SFPレコードは効かないし、DNSレコードは反映をコントロールできない1ので逆引きが効かなかったりでサーバーとしては結構痛いことになる。

リバースフォワーディング

これはかなり簡単な方法で、例えば次のようにする。

# ssh -g -R 80:localhost:80 serverhost

これによってインターネットから安定して接続できるホストに代理ホストとして公開してもらう。

不安定なホストの非稼働時は代理ホストが応答する。

フロントエンドプロキシ

リバースフォワーディングと同様に代理ホストに公開してもらうのだが、不安定なホストに通信を転送させるのではなく、中継させるという方法。

まずなんらかの方法で代理ホストと不安定なホストを接続する。 例えばVPNで接続する。

# pppd updetach noauth silent nodeflate pty "ssh root@remote-gw /usr/sbin/pppd nodetach notty noauth" ipparam vpn 10.0.8.1:10.0.8.2

あるいはSSHによる転送を行う。

$ ssh -R 8080:localhost:80

そして、代理ホスト上でリバースプロキシを動作させる。例えばSquidやNginxをプロキシとして、ローカルネットワークホストとして、あるいはローカルホストの別ポートとして転送する。

VPNで接続している場合、代理ホストからは不安定なホストは不安定なグローバルIPアドレスではなくローカルなIPアドレス10.0.8.1として認識される。

不安定なホストの非稼働時は代理ホストが応答する。

逆接続モデル

利用可能なサーバーが限定されてしまうが、サーバーからクライアントに接続するモデルがとれるのであれば、不安定なホストであることはあまり問題にならない。 あるいはサーバーリレーするタイプ(送信専用メールサーバーなど)でも機能する。

VPNで接続する

インターネット公開しているのでないのならば、代理ホストを経由するなどしてVPNで接続し、ローカルなアドレスを代わりに使うという方法が効く。

非IP

「IPアドレスが固定でない」ことはIPアドレスを用いる場合にのみ問題として発生する。 だったらIP以外で通信すれば良い。

ただし、この場合はほとんどローカルなIPアドレスで運用する方法も適用できる(VPNで接続するなど)ことになるし、 リンクローカルな通信であることがほとんどだから、これを転送するハードルを考えるとあまり易しくはない。

例えばセキュリティの都合上でIP接続を許可していないAoEサーバー、なんていうのはアリだ。

Zeroconf

リンクローカルでIPアドレスが固定でないことを考慮しなければならないケースはあまり見当たらないが、 リンクローカルなのであればZeroconfが使える。

IPアドレス制限されたホストに接続する

レンジで許可する

ステートフルに割り振られるIPアドレスのレンジが十分に制限されているのであれば範囲で許可すれば良い。

ゲートウェイ経由

固定IPアドレスを持つホストを経由して接続する。

SSHならば割と簡単にできる。

$ ssh -L 10000:destination.example.com:10000 proxyhost

VPNで代理ホストを経由しても良い。

別の方法で接続する

例えばSSHやVPNなど特定のポートだけIPアドレス制限の対象外とした上で、これらのセキュアトンネルを通じて接続する。

MySQLサーバー等、認証が強力でないサービスのためにIPアドレス制限がなされているのであれば、SSHのように認証が強力な方法についてはIPアドレス制限から除外し、これを経由することを許す、というわけである。

許可されているホストに依頼する

ゲートウェイ接続はできないまでも融通の効くホストで接続が許可されているホストがあるのであれば、これをプロキシとして接続する。

このような場合普通はアプリケーションプロキシとして使うか、SOCKSプロキシとして使うものだ。 このプロキシに対する接続に認証を求めるべきで、異なる方法で安全性を担保させるわけである。

ちなみに、以前紹介したSSHフォワーディングの場合

  1. ローカルなポートフォワーディングにより代理ホストから不安定なホストに通信を中継するようにする。 (これ自体は公開されていない)
  2. プロキシコマンドとして外部ホストから代理ホストに対して接続を行う
  3. これによって確立された経路を使って代理ホストのlocalhost(lo)に対してSSHを通す (これがダイナミックポートフォワーディングによって不安定なホストに転送される)

という方法で、計3本のトンネルが通されることになる。


  1. 「TTLに従う」と信じているなら、ちょっとハッピーすぎる。

ReadyNasでSFTPしたい

概要

ReadyNASは、だいたいNTT-X Storeのメルマガで嫌というほど見ているNASだと思うが、NAS製品ではちょっと安めで定番である。

機能的にも充実していて使えるNASなのだが、ちょっと弱点がある。 それは、「iSCSI LUNにディスク全体の90%しか割り当てられない」ことだ。 どうも、90%以上ディスクを使わせないという考えかららしいのだが、はっきりいって10%もデッドにされるのは無駄である。

仕方ないので残り10%(3Tくらいはある)をなんとか使いたいのだが、そうなると共有ドライブということになるだろう。 まぁ、iSCSIボリューム以外に共有しない形で存在するディスクがあるとそれはそれで便利でもある。

しかし、その選択肢は、SMB, NFS, AFP, FTP, HTTP…

SFTPがない。 せっかくディスクの暗号化をしているのに、さすがに共有ディスクがネットワークから丸見えではお話にならない。 やはりSFTPがなくてはならないだろう。SFTPが使えればNemoやSSHFSも使えるし。

そこでReadyNASでSFTPを使えるようにする。 普段LinuxやSSHをよく使っている人には目新しさのないお話だ。

具体的手順

SSHDを有効にする

システム → 設定 → SSHで有効にする。 「SSHを有効にする」にチェックを入れて適用するだけで良い。 警告が出るが、続行する。

鍵を作る

ssh-keygen -f ~/.ssh/readynas_rsaという感じでいいだろう。

アカウントを用意する

アカウント → ユーザー → 新しいユーザー でユーザーを作る。 特にNFSを使う気がないのならUIDはそれほど気にする必要はないだろう。

ユーザーを作ったらユーザーを選択し、設定からSSHタブを選択。 「シェルアクセスを許可」にチェックを入れ、「公開キーのインポート」でreadynas_rsa.pubをアップロードする。 このとき、「rsyncのみ」のチェックを外すこと。

ログインして設定

これでログインできるようになった。

コントロールパネルから設定した鍵は~/.ssh/ssh_authorized_keysに入るようになっているが、このファイルは書き込みできず、~/ssh_authorized_keysも有効である。 そこで、もう1ペア鍵を作り、

$ scp -i ~/.ssh/readynas_rsa ~/.ssh/readynas_sftp_rsa.pub user@readynas:.ssh/authorized_keys

のようにコピーするのだが、その前にコピー元のファイルでcommand="internal-sftp"を手前に追加しておくといいだろう。

これでSFTPの準備は完了

~/.ssh/configに記述しておけば簡単にSFTPアクセスができるようになった。 非常に柔軟に扱うことができてとても良い。

なお、ルートディレクトリを除くとどれがドライブディレクトリかはすぐ分かると思うが、/homeもディスクアレイのbtrfsがマウントされている。 ドライブディレクトリ以下にもhomeというディレクトリがあるけれど、これは「共有」にある「ホーム」にあたるもので、別にホームディレクトリに直接ファイルを配置しても問題はなさそうだ。

なにはともあれ、とても使いやすくなってバンザイ。

「ZshからRubyにしたら速くなる」 その理由とテクニック

現在取り組んでいるプロジェクトで、パフォーマンスチューニングの一環として当初Zshで書かれていたスクリプトをRubyで書き直すことによって、60倍程度の高速化を実現した。 もちろん、単純に書き換えただけではなく、可能な限りfork/execをしないようにしたり、コストがかかる処理を最小にするなどの工夫を伴って手に入れた結果だが、「ZshでしていたことをRubyに書き換えた」だけでも相当な効果があった。

このパフォーマンスチューニングは単にプログラムを書くだけの人には生まれにくい発想である。 Unix、そしてLinuxのシステムや、プログラミング言語処理系に関する知識がないと考えられない要素が多いのだ。

そこで、この話を解説する。

「ZshよりRubyが速い」そのわけ

根本的な話として、Zshはそもそも遅い処理系だ。 「Zshが遅い」という話はZshのメーリングリストでもちらほら話をされる。 別にBashと比べて遅いということではないのだが(Bashもまた非常に遅い処理系だからだ)、状況によっては速度が問題になる程度に遅い。

Rubyも相当に遅い処理系であると言われていたし、実際かなり遅かったのは事実だ。 それでもZshに比べれば随分早かったのだが。

だが、それ以降、Rubyは高速化に取り組み続けている。対して、Zshはあまり高速化には取り組んでいない。だから、差が開いている。

しかし、理由がそれだけというわけではない。

Zshは純粋なインタープリタである。対して、Rubyはスクリプト言語ではあるがバイトコードインタプリタ型である。 この違いは、syntax errorが起きるタイミングが、Rubyがスクリプトを実行しようとしたタイミングであるのに対し、Zshはその行に到達したときであることからもわかる。

インタープリタ型であれコンパイラ型であれ、ソースコードを機械語に変換しなければならない、という点は変わらない。 その違いは方法とタイミングである。

インタープリタ型言語の場合、「1行ずつ(1コマンドずつ)変換する」のである。 その変換方法はもちろん処理系によって異なるのだが、Zshの場合、complex commandでも複数の文をまとめて変換することはしないし、ループによって繰り返される場合でも一度変換したものを使いまわしたりはしない。

対してRubyは、最初にコード全体を構文木に変換する。 RUby 1.8までは構文木インタープリタによってこれを実行していたが、Ruby 1.9以降はこれをさらにバイトコードに変換し、バイトコードインタープリタ(VM)によって実行するようになった。 バイトコードはRuby専用の機械語のようなもので、VMによって非常に小さなコストで実行できる。 Ruby 2.6からはJITコンパイラも追加され、部分的にCコードを生成し、これをネイティブコンパイラ(例えばgcc)によってバイナリコードに変換する(こともできる)。

これで1行だけのようなコードだとあまり差は出ないし、Zshでは1行だけどRubyでは何十行という可能性もあるので、このようなケースではRuby有利というわけではなくなる。 だが、ループで何度も同じコードを実行するような場合には非常に大きな差になってくる。 今回の場合、テスト段階で500回のループであったことから、大きな差になったということである。 だからループ回数が増えると倍率的にも速度差はさらに開く。

fork/execとコンパイルにかかる時間

Unix関連に少し知識がある人であれば、「forkはコストが重く遅い」というのを聞いたことがあると思う。

だが、この認識にはちょっと注意が必要だ。 というのも、C言語の速度から見た時に「forkする時間があればどれだけ実行できるか」という点を考えるとsystemで外部コマンドを呼び出すとそこだけ局所的に時間がかかる、という状況が発生する。

だが、実際にはfork(2)しても1000分の数秒にすぎない。 どちらかといえばそれよりもexec(2)のほうが重いのだが、それでもせいぜい100分の1秒程度だ。 だから、C言語で書いている場合ですらそれなりに長くなる場合はむしろ実行コストを省略できてコマンドを呼び出すほうが速かったりする。

昔のUnixではfork(2)はもっともっと遅かった。 現在のLinuxにおいてfork(2)が速くなったのはコピーオンライト形式であることの恩恵が大きい。 古典的なUnixではfork(2)は呼び出した時点でプロセスのメモリをコピーしていた。直後にexec(2)する場合はコピーしたメモリの内容は全く使わないのでかなりの無駄だ。

ところが、現在のLinuxにおいてはfork(2)によってメモリはコピーされない。共有されるのである。 そしてforkされたプロセスが共有されているメモリに対して書き込みを行った時に別に領域を確保してそれを変更する仕組みだ。

結果的にfork自体は一瞬に近くなっている。

そして、もうひとつ重要なのが「コンパイル時間」だ。 Rubyは起動時に対象スクリプトの変換を行う。 だが、この変換コストは速くなるに従って増加している。以前は構文木に変換するだけだったのが、1.9からはさらにバイトコードに変換する時間が必要になったし、2.6でJITを使うとさらにCコードを生成してそれをコンパイルする時間まで必要になっている。 つまり、Rubyはだんだん「実行は速くなっているが、実行に着手するまでは時間がかかるようになっている」のである。

これは、例えばechoであれば

% time /bin/echo > /dev/null
/bin/echo > /dev/null  0.00s user 0.00s system 79% cpu 0.001 total

ということになるのだが、Rubyだと空っぽに近くても

% time ruby -e 'nil'         
ruby -e 'nil'  0.04s user 0.02s system 59% cpu 0.089 total

結構時間がかかる。 つまり、一瞬で実行が終わるRubyスクリプトを何度も何度も繰り返して呼び出すと、トータルではかなり時間がかかるわけだ。 もともとのスクリプトは本体はRubyで、呼び出しがZshだったので、20並列で各500回、Rubyによるコンパイルがかかっていた。だから、かなりの時間がかかっていたのだ。

だが、「Linuxのforkはメモリが共有され、ほとんど一瞬で終わる」という点を利用すると改善の余地がある。 それは、実行可能なRubyスクリプトをライブラリ化する、という方法だ。

ZshからRubyを呼び出す場合、どうしてもRubyを呼び出すたびにRubyによるコンパイルをかけざるをえない。 当初は10000回コンパイルされていたのだが、500回のループをZshではなくRubyで行うようにすれば20回で済むようになる。だが、それでも20回のコンパイルが必要だ。

しかし、呼び出すスクリプト自体をRubyに変えてしまえば、実行しようとするスクリプトをライブラリとしてロードするという方法がとれるようになる。 ライブラリとしてロードすると、そのコンパイルは呼び出し元スクリプトをロードしたときに行われる。 もちろん、呼び出しの目的は呼び出すだけであり、直接そのライブラリの機能を使うわけではない。だが、この状態からforkすると、「コンパイル済みコードがメモリ上にあるRubyプロセス」が出来上がる。

この時点でスクリプトを実行する方法は「メソッドを呼び出す」(あるいは、その機能を果たすオブジェクトを作ってメソッドを呼び出す)だけである。 繰り返し呼び出すループを書くのも、単にRubyのループを書いて、そこで繰り返しメソッドを呼び出すなりオブジェクトを作るなりすれば良い。 呼び出し元スクリプト側では並列分だけforkしたあと、Process.waitallでもしていればいいわけだ。

これはZshに対して、「Rubyスクリプトのコンパイルが1度だけでいい」「execする必要がない」というメリットをもたらしている。 どちらも結構コストの高い処理であるから、繰り返し実行する場合は非常に大きなコストになり速度を低下させる。「処理自体は軽いのだが果てしなくループする」タイプのスクリプトに対してこの方法は本当に効く。 なぜならば、そのようなスクリプトに対してはコストの高い呼び出しをしているとコストのほとんどは呼び出しで占められ、実行コストは小さいためにスクリプト自体を高速化しようとがんばったところでほとんど無意味だし、逆に呼び出しコストを軽くすると劇的に速くなるからだ。

シェルスクリプトで並列処理

なんだか検索件数が多いので、シェルスクリプトによるコンカレンシーのお話をしよう。

ただし、bad design (変数を書き換えるとか、相互にやりとりするとか)は除外する。

また、Zshを前提とする。

投げっぱなし

まず基本。投げっぱなしはとても簡単。

シェルスクリプトではジョブコントロールは無効になっているので、SIGHUPの送信はなされないので、さっさと終了してしまっても大丈夫。

処理の終了を待ちたい場合はwait

flock

flockを使う方法は簡単でシンプル。 ロックファイルを使ってファイルデスクリプタを開きっぱなしにし、そのファイルデスクリプタを指定してロックする。

まずファイルデスクリプタ9.lockファイルをライトモードでオープンする場合

そしてファイルデスクリプタ9を閉じる場合

これを利用するとこんな感じ。

ロックしている間に共有しているリソースの読み込み/変更を行い、ファイルデスクリプタを閉じる。

リソースを読むより簡単な方法は、ひとつのストリームを共有したファイルデスクリプタとして開き、 ロックを中に読むことである。

ワーカーを生成するサブシェルの標準入力はqueueファイルにリダイレクトされている。 そのため、ファイルデスクリプタ0queueファイルなのだが、そのサブシェルの子プロセスであるワーカープロセスはリダイレクトしていないため、このファイルデスクリプタが共有される。 結果、全てのワーカーはqueueファイルを標準入力とするのだが、ストリーム自体を共有しているため、どのワーカーが一行読んだとしてもストリームの位置が変更され、次に読み込む位置は他のワーカーにとっても変更される。実際

WORKER 1: foo
WORKER 2: bar
WORKER 3: baz

となる。

producer-consumer キュー

もっと凝ったことがしたいのであればUNIXドメインソケットを使ってproducer部分をシングルスレッド化することができる。

zsocket -lのタイミングで接続を受け付けているのだが、zsocket -lしていないタイミングでは接続しようとするプロセスをブロックするため、producer側の処理は直列に行われる。

双方向性があるときはproducerと直接やりとりできるのはメリット。

Orbit designの場合

私が採用しているOrbit designはレギュレーターはZshスクリプトなので、基本的にこのような並列化手順をとっている。 とはいえ、ものによっては直列(serial)になっていたりする。

ただ、並列化されているものが多い。ワーカープロセスは最も多いもので5。

基本的にワーカースクリプトが受け取るのは処理対象ID(ほとんどの場合ファイルパス)だけである。 それ以外の情報はスクリプト側で生成するか、別途取得するかする。

Mimir Yokohama ウェブサイトの「タグ機能」の仕組み

Mimir Yokohamaのウェブサイトにこっそりとタグ機能が追加された。

だが、PureBuilder Simply自体にはタグ機能がない。 この実現方法は発想力勝負な部分があった。

ドキュメントにデータを持たせる

「記事情報」でも行われている方法として、Markdown YAML Frontmatter内に情報をもたせ、Pandocテンプレートで存在する場合だけエレメントを生成するような手法を取っている。

例えば帯域においては

というYAML Frontmatterが書かれている。 記事情報などは自動的に生成することができないため記事ごとにかかれており、若干執筆コストを上げている面もあるが、 なにしろMimir Yokohamaには力が入っているのでそれくらいどうということはない。 基本的なフォーマットをコピペしてしまえばそれほど難しくない部分でもある。

ちなみに、Pandocテンプレートで

$if(pickable)$
Something
$endif$

とした場合、pickable: no (つまりfalse)ならばここは生成されない。

この追加情報としてtagsが加わったのである。

問題は検索

タグを表示することは簡単だが、普通に考えればタグから同一タグの記事を辿りたいし、タグで検索もしたい。

単純な方法としてGoogleを使うこともできるのだが、それは必ずしもタグつきの記事が上位にくるわけではなく、思ったようには動作しない。 ちゃんと検索機能を用意する必要があったのだが、できればPureBuilder Simplyの枠組みの中で行いたいところである。

PureBuilder Simplyは原則として「MarkdownまたはReST文書から生成する」という前提になっており、 ACCSもindexデータベースからMarkdownドキュメントを生成し、このあとはPandocで処理している。

なのでPureBuilder Simplyの枠組みで処理するためにはMarkdownドキュメントを生成しなくてはいけない。

それなら全てのindexを探し回ってタグを集めればいいじゃない。

ARGV.eachしているので、全ての.indexes.rbmを指定すれば良い。 あとはpbsimply-pandocで処理できるが、タグに登場した.indexes.rbmは実際に記事が存在しているものではなく拾ってほしくないので消しておく。

.indexes.rbmとして書き出すようにした意図の一部に、このように外部からドキュメントデータにアクセスするというものがあった。

これによってドキュメント解析しなくてもメタデータを利用して機能拡張してページに含むことができる。

テンプレートにとうとう限界が

だいぶ魔改造されているPandocテンプレートだが、今回は限界が垣間見えた。

タグクラウドらしくエントリ数の多いタグを大きく表示したいのだが、Pandocテンプレートに計算機能や比較機能がなく、CSSにもないため、 Pandocテンプレートだけでは実現できない。MarkdownにHTMLを直接書くという方法はあるが(Markdown自体はRubyで生成しているため)。

また、URIエンコーディングをする方法はデータを二重に持たせる以外になく、それでもふたつの値を同時にとるイテレータがPandocテンプレートにないため、URIだけでURIエンコーディングをおこなう方法がない。

eRubyを使ってもいいのだが、できれば使いたくない。 現時点ではタグクラウドの大きさ分けはしておらず、URIもURIエンコーディングせずに使用している。

Git/Mercurialを使いデスクトップとラップトップで作業する

GitやMercurialでの分散作業というのは、高度なユーザーやギークばかりでなく、多くの人にとってメリットのある手法だ。 デスクトップを母艦としてラップトップを使う人にとっては確実に意味のあることだろう。

基本的な話

まずいまいちど基本的な手法を確認しよう。 まず、基本となるリポジトリを作る。

Gitの場合

$ cd ~/Repositories/Myrepo.git
$ git init --bare
$ cd ~/Work
$ git clone ~/Repositories/Myrepo.git

Mercurialの場合yvarn

この違いは、Gitがワーキングツリーを持つリポジトリに対する同期を非推奨としていることによる。 そのため、同期するためにはベアリポジトリを作り、その上でワーキングリポジトリとしてcloneする必要があるのだ。

Mercurialの場合は特にそのような必要はなく、ワーキングツリーを持つリポジトリをcloneすれば分散作業できる。

ここでは話を楽にするためにMercurialを使おう。

デスクトップコンピュータのdragon.localホストの~/Work/Wyvarnリポジトリを作ったとする。 この作業を外で続けたくなったので、ラップトップ(knight.local)でこのリポジトリをcloneする。

$ cd ~/Work
$ hg clone ssh://jrh@dragon.local/Work/Wyvarn

Mercurialのsshアクセスはパス部が相対パスで始まっているものとみなすので注意してほしい。絶対パスで指定するためにはパス部を/ではじめる必要がある。

過酷な作業を終え、充実の成果をあげた。では家に帰ってその成果を反映しよう。

$ cd ~/Work/Wyvarn
$ hg push

続きはデスクトップで。デスクトップでリポジトリのアップデートを反映する。

$ cd ~/Work/Wyvarn
$ hg update

変更に強い構成にする

だが、実のところこのようなシステムではファイルの配置が変わったり、ファイル名が変わったり、ホスト名が変わったりするのはよくあることだ。 そのたびにすべてのリポジトリを修正するのはかなりの手間がかかる。

これにはふたつの抽象化レイヤーの導入が有効だ。

まずは、ホスト名を、そのホストに依存したものではなく、専用の固定名を与える。 ホストが少ないのであれば/etc/hostsファイルで良いだろう。

192.168.2.100          repo

多いのであれば、dnsmasqを使うと良い。

続いてリポジトリへのパスを抽象化しよう。 これは、次のようにするといい。

# mount --bind ~jrh/Repositories /mnt/repo

その上で

/home/jrh/Work/Wyvarn -> /home/jrh/Repositories/Wyvarn

のような状態にしておけばいいだろう(実体の位置を間違えないように)。

そして、ラップトップでは

$ hg clone ssh://jrh@repo//mnt/repo/Wyvarn

のようにすればいい。

Linux Tips

YouTubeのプレイリストからタイトルを抽出する

結局使わなかったのだが、ワンライナーで書いた。 比較的素直なHTMLなので解析は簡単。行指向ではないので、PerlでなくRubyにした。

$ curl 'https://www.youtube.com/playlist?list=<playlistid>' | ruby -e "s = STDIN.read" -e 's.scan(/<a class="[^"]*pl-video-title-link[^"]*"[^>]*>(.*?)</m) {puts $1.strip }' | grep -v 動画は削除されました

ffmpegでh.264/aacな360pのmp4を

元動画は1080pのmovまたはmp4。 オーディオはいじらず、元々aac(ac3)。

$ ffmpeg -i <infile> -vcodec libx264 -s 640x360 -crf 34 -strict -2 <outfile>.mp4

ちなみに480p(16:9)は720×280。 -crfの値は18-28が推奨されている(小さいほど高ビットレート)が、今回はモバイル向けなので34を指定。

なお、6の増減でビットレートはおよそ1:2の変動となる。

ffmpegでCowon M2向けの動画を作る

COWON M2はXVidとmp3のAVI動画で、解像度は320×240またはWMVをサポートするとある。

WMVだと結構サイズが大きいので、AVIで作る。 ソースは前回と同じくh.264*ac3のMOVまたはh.264*m4aのmp4。

$ ffmpeg -i <infile> -vcodec libxvid -acodec libmp3lame -b:v 372k -b:a 128k -s 320x240 <outfile>.avi

随分としょぼい解像度の上にアスペクト比も壊れる(プレイヤー側で調整することは可能)が、案外見られる。 ただし、360pでも細部は潰れてしまっているのでよく分からない部分は出てしまう。

XineのUIの文字化けを直す

fontにHerveticaを要求しているので、フォントエイリアスを設定すれば良い。

某コンテストの投票方式の問題点

先日まで某サイトでwebコンテストが実施されていた。だが、これにかなりの技術的問題点があったので、言及しておく。

会員に対する投票と、一般の投票は、票の重さが違う、という仕様で、一般投票は1日に1票、ということだった。しかし、この重複票排除というのは、現実にはまず不可能である、とされている。

重複票排除については1995年頃から議論されていた。1人1票、と設定しても、どうやってその人が既に投票したかを確認するか?ということだ。方法としては、IP, IP+UA, Cookie, 登録制が一般的だった。

IPはゲートウェイホストによって個々のインターネットホストに与えられるため、同一IPの投票を重複とみなす、という方式だ。だが、この方式は、インターネットカフェやケータイ(これは2000年以降)で問題が生じることと、NATを用いるために同一世帯の家族を「重複票とみなしてしまう」という問題があった。一方、PPPならば「電話をかけ直す」ことでIPアドレスは振られ直すことが多く、この重複を排除できない。

IP+UAは、IPとUAの両方が一致する場合重複とみなす、というもので、会社、ネットカフェなど共有回線がまるごと重複とみなされる問題を回避しようとした。しかし、UAは当時は特にバリエーションがそれほど多くなかった上に、詐称することも可能だったため、会社などそれなりの規模になるとかなりの確率で、環境を揃えているネカフェではほぼ確実に重複とみなされ、一方重複投票したい人は容易に回避できた。

Cookie方式はブラウザに「投票した」という情報をもたせることで管理しようというものだ。比較的単純で効果があったが、手元に複数の、Cookieを共有しないブラウザがあれば回避されてしまうし、単にCookieを削除するだけでいくらでも投票できてしまう。

登録制は、重複登録をいかにして防ぐかが問題となる。また、登録制にすることでハードルが上がり、投票数は劇的に低下する。重複票を防ぐ効果は低く、それでいてむしろ避けられることになるため、よほど自信のある(中身にというよりも、popularityにおいて)プロバイダでなければ採用は逆効果だった。

これらの問題の難しさを諦めて、逆手にとったのがAKB方式といえる。つまり、重複投票はしても構わないが、その票数は買わなければならない口数方式だ。

例えば住基カードを使えば1人1票は実現可能だが、厳密性を求めるならなりすましの対策という非常に困難な問題にぶつかることになる。それに、選挙でもなければ同定に住基カードなど使えない。

携帯電話に限る、という方式はお手軽であり、普及している。電話番号を使うことで同定できるためだ。だが、そのような理由でコンテンツを携帯電話に限ることは、アクセシビリティの観点から言っても好ましくないし、やはりアクセスはかなり減少する。それに、そのような目的で電話番号を取得するのはいかがなものか?ということもある。

このほか、TwitterやFacebook, Google+のようなopenIDを使って認証する、という方式もある。電話番号よりはいくらかソフトなやり方だが、その分効果は低下する。

このように非常に難しい重複投票の制限だが、そのコンテストでは、単純にCookieを使う方式だった。CSRF対策か、セッションクッキーを使うようになっていたが、その場合、単純にブラウザのプライベートウィンドウを開いてアクセスし、投票して閉じれば無限投票が可能だ。

もっとあげつないやり方としては、curlなどでセッションクッキーを保存するようにして投票ページを取得したあと、投票するという2回のコネクションを張るだけで投票できる。この間0.1-3.0sec程度なので(私の環境で)、ループすれば1時間で1500票は入れられる。

これはさすがに中止になるか無効になるかするように思う。

もう少し考えて作ってもよかったのではないだろうか。

現在のシステムについて

私のコンピュータシステムについて質問があったのでまとめておく。

とはいえ、Linuxのシステムは全て説明するのは困難なので、概要に留める。

ハードウェア

ASRock FM2+Killer Extreame, AMD A10 7700K, 32GB RAM, 128GB SSD, 3TB HDDx5というのが主な構成。

外付けマルチドライブ、Canon MP630とEPSON GT-S630がある。プリンタ、スキャナはWindowsからは使用していない。

AUDIO I/OはAUDIO 4 DJ(セレクター経由でBOSE)、TASCAM US-366+FOSTEX AP05+VAIO付属スピーカー、アナログ出力のlogicool。US-366はLinuxでは扱えない。通常はClassic Proのヘッドフォン、製作時やリスニングではULTRASONE HFI-580を使用。

キーボードはオウルテックのメカニカルキーボード(青軸)、マウスはサンワサプライのBLUE-TECH有線、マウスパッドはELECOMの滑マウスパッド(大)。ELECOMのゲルハンドリストを使用。

机がL字型の自作となっている。

モニターは21.5インチのFullHDデュアル

システム

ディスクは以下のようになっている。

disk0(SSD)
Linuxシステム(/boot, /, /swap)
disk1,2,3,5
Btrfsボリューム
disk4
Windowsシステム(NTFS/UEFI)

Btrfsボリュームは2レッグミラー(RAID1+0相当)で運用される。サブボリュームを用いた運用で、サブボリュームの直下がEncfsディレクトリとなっており、~/.share.encfsにマウントされる。これをencfsで~/shareにマウントして使用する。

そもそもホームディレクトリの主要なディレクトリやファイルはシンボリックリンクとなっており、これをマウントしないとシステムをまともに使用することができない。システム自体もLUKSで暗号化されており、オフライン攻撃に対する強度を確保する。

Manjaro/WindowsともにUEFIでブートされる。ディスクは全てGPTとなっている。

Linux

ディストリビューション
Manjaro Linux JPを使用。0.8.10からのローリングアップグレード。
カーネル周り

Manjaro kernel 3.17を使用。AMD Catalystドライバで、オーバースキャンの設定をしてある。

ただし、この状態ではデスクトップ環境を問わず、HDMIモニター(セカンダリー)のカーソルがやがてバグる。

デスクトップ環境

XFce, Awesome, KDE, Cinnamonを用意。通常はKDEを使用。

KDEについてはSuper+Leftでウィンドウを左に寄せる、などエッジリサイズオプションを設定してある。

Conkyを使用しているが、KDEではRSSがうまく領域を確保されないことが多いので、あまり使っていない。

日本語環境

fcitx+Mozc-UT。

fontsは、Archで用意されている日本語フォントはだいたい入っている。

インターネット

ウェブブラウザはFirefox latest(binから入れたもの。スクリプト起動)とMaxthon, opera developerを使用。

メールはMaildeliver(自作スクリプト)でチェック、振り分け、フィルタリング、通知などを行い、Claws Mailで読む/出す。通知はデフォルトの通り、SOXとNotify Sendを使用。

TwitterはMikutter(Git)を使用。2chはJD(AUR)を使用。

その他、Skype+Sype Call Recorderを使用。

マルチメディア

半自動でCDをflacとOgg Vorbisに取り込むようになっており、Amarokで音楽コレクションを再生。

PDAPにはスクリプトを用いてOgg Vorbisを転送。Ogg VorbisはV192kbps。

ビデオはVLC/SMPlayer。

音声についてはPulseAudio経由で、出力デバイスはアナログオーディオ、HDMI、そしてNI AUDIO4 DJがあるが、設定によりAUDIO 4DJから音楽のみ出すようにしている。これはPulseAudioで設定してある。現在、Audio 4 DJから鳴るのはAmarokとSMPlyer。

画像は通常はGwenviewだが、Thunar+ViwniorやRistrettoも使用。

画像は投稿時などの変換はImageMagick。その他、GIMPとInkscape。

開発

PDocはmedit、コードはKate、プレーンテキストはTeaを使用。

バージョン管理はGitで、サイトのテキストもGitで管理、大部分はリモートGitを使用している。

リモートGitはCodebreak;, GitHubが主。

開発言語はRubyとZsh

シェル
XFce4 TerminalとZshを使用。Zshはmomongaをベースに拡張したrcで使用している。EXTENDED_GLOBは常にON。

Windows

デスクトップ

外観はU-7imate Final Version for Windows7, MacType, tronnixカーソルテーマを使用。

ランチャーはLaunchy。Rain Meterも控えめに使用。フォントは源柔フォント, 源真フォント, Rounded M+, Noto Sans Japanese, Ricty。

ユーティリティ
  • GeekUninstall
  • PeaZip
ファイラ
  • As/R
  • Hina File Master
日本語入力環境
Google日本語入力
マルチメディア
XnView
音楽制作環境
  • SONAR X3 PRODUCER
  • FL STUDIO Signature Bundle
  • KOMPLETE 9 ULTIMATE
  • Cabuse 6 LE
インターネット
ウェブブラウザ
  • Cyberfox AMD64bit
  • Sleipnir
メール
Oepra Mail
エディタ
  • Uneditor
  • VXEditor
セキュリティ
  • ZoneAlarm Free Firewall
  • Avast! AntiVirus Free

音楽制作環境なので、基本的に余計なものは入れないようにしている。

Windowsでウェブブラウジングを楽しむようなことはない。メールもメインアカウントのIMAPアクセスだけだ。だが、ブラウザは調べ物したりSoundCloudにアクセスしたりで必要となる。

パフォーマンス

Linuxでの並列作業や、WindowsでのDTMで使われているこのコンピュータのパフォーマンスについて述べる。

Linuxにおいて10000プロセスをforkして行う作業は、ほぼ問題ない。が、GUIはフリーズしたり、ひどい遅延を起こす。どちらかというと、fork時が問題になるようだ。ちなみに、Nepomukでシステムが使えなくなるまでには26万プロセスがforkされていたと記憶している。

おおよそ並列作業のために見境なくforkしても問題にはなりにくい。ただし、gccなどでは、やはり多くても12スレッドがいいようだ。並列性は、残念ながらあまり高くない。やはりこの作業ではコア数が必要となるので、FX-8が欲しいな、と思う。

全体にはCPUがボトルネックになっている。システムはSSDからロードされているため、ストレージ性能はそれなりにある。ネットワークもGbEでそれなりの帯域がある。RAMはKDEでも2GBもあればロードできるため、実際は8GBを越えることは稀だ。

しかし、ソフトの起動時に待たされる、特にKDEの起動が遅いことについては、やはりCPUがひっかかっているようだ。これはGIMPやLibreOfficeの待ち時間についても同様。KDEでもひっかかりが生じることがあり、明確にどこでということは読めないのだが、どうもCPUらしい。KDEは明らかにXFceのようにはさくさく動かない。リソースに常に余裕があれば、体感的な速度にはそれほど差がでないはずなので、常にではないが動きが悪いと感じるということはKDEに対して余裕がないのだろう。

ちなみに、パイプでのXZなどCPU中心となるものに関しても結構遅いと感じられる。コンパイルも意外と待つ。「すごく速い!」という印象はない。

IOスピードは、まぁ、この程度だろう。RAMも十分なのだが、それでももう少しあると快適になるシーンというのはある。バッファで使いきってしまうことは結構あるので、その時にはもっとメモリーがあればIO待ちの時間が減る。また、tmpfsに置けるファイルも増えるだろう。とはいえ、64GB化はエクスペリエンスの向上は乏しいだろう。

WindowsのDTMにおいては、ストレージスピード、RAM共に完全に余裕である。ストレージスピードが足りないようならば、ネットワークストレージのRAID0でさらなる高速化を準備していたが、その必要はなかった。

RAMは、Windowsでは逼迫したことがない。WindowsはRAMの使い方が下手だ、ということもあるが。

16トラックのシンセに20本のプラグインエフェクトを入れてもRAMには十分な余裕があったが、問題はCPUだ。上限に張り付くようなことはないのだが、US-366でlowest latencyにしていると、3トラック目あたりからノイズが乗ってくる。DTM作業をストレスなく行うには、かなりパワーが足りない印象だ。

AMP A APUは、クロック周波数がブースト時のものになっているので、その最大周波数で動いてくれるわけではない、というのが結構痛い。見ていると、ひとつのコアが60%くらいになるとノイズが乗り始める。この場合、他のコアは40%程度の使用率となっている。

Highest latencyにしてもノイズが除去できないこともある。結局、モアCPUパワー!な状態だ。

音楽制作においては、比較的低レベルでの動作が多いためか、どうもハードウェア的にインテルのほうがいいことが多いように思う。性能は同じでも、うまく動いてくれる気がする。一方、LinuxではFX 8がとても気になる。

現状、DTMではコアよりパワー、一方Linuxではパワーよりコアである感じがするのだが、まぁこれだけCPUの話ばかりになってしまうほど、CPUが遅い!

ちなみに、CPUについでパワーが足りないのは、ゲームなどしていないにもかかわらず、グラフィックだったりする。Windowsではもたつくというほどではないが、それでもYouTubeなどでは「んっ」と思う時がある。デュアルディスプレイの負荷が厳しいのかもしれないが、安定性も足りなかったりする。Windowsでもだ。

一応、この構成でもFHDx2+4kが可能なようではあるが、相当もっさりしそうだ。

クリエイティブな環境のためには、もっとCPUとGPUが必要だ、という結論に達した。

なおストレージ容量だが、現在はだいたい1.3TB。一時2.7TBまで行っていた。ネットワークが50Mbps程度しか出ていない(しかも、down率が10%を越える)ので増加は抑えめだが、本来限界まで簡単に行くとは思う。btrfsでいくらでもふやせるのはいいのだが、バックアップが難しくなっていくのは難点である。

画像PDFに黒塗りを入れる

ドキュメントスキャナで作成したPDFファイルのドキュメント、公開したいが一部データは個人情報であり公開できない…

そんなようなケースに私は遭遇した。まずは単純にPDFエディタを試そうとしたのだが、それはうまく動かなかった。LibreOfficeでもだ。

となれば、「一度画像にバラす」というのが無難な方法だろう。imagemagickでバラすことができる。

$ convert something-pdf-file.pdf out.png

シンプルな話だが、実際にできあがったPNGファイルをみてみるとガタガタで品質はかなりひどい。どうやらxpdf/popperを使ったほうがよさそうだ。

$ pdftoppm something-odf-file.pdf out.ppm

これでppmファイルで出来上がる。ppmファイルはgimpで編集できるので、gimpを使って編集すれば良い。pdftoppmを使って変換した場合、ImageMagickと比べるとかなり品質は良い。そして再編する。

$ convert out*.ppm out.pdf

ここではImageMagickを使う。これによる品質劣化はなさそう?だ。

ちなみに、ImageMagickを使ってppmをjpegにすることはできるが、品質オプションなしだとjpegから再編すると容量は123%程度に膨らんだ(PPM=26MB, JPG=32MB)。-quality 30まで落として再編すると、6MBまで落ちた。品質は、今回は便箋に書かれた文字であるため、このレベルなら問題ないだろう。サイズ縮小においても効果のある手法だ。-quality 15ではかなり荒れるが、それでも可読性に問題はない。このバージョンに差し替える予定でいる。ちなみに、2ページのデータはもともと2.4MBのPDFファイルだが、208kBまでの縮小に成功した。

このような場合、mogrifyを使ったほうがてっとりばやい。これはglobを使って一気に変換することを可能にする。例えばmogrify -format jpg -quality 15 *.ppmのようにだ。出力ファイル名を指定しても構わない。その場合、拡張子の前に連番が入る。