Klipsch R-40M インストール

image_printPrintable Format

去る 9/28 に,注文していた Klipsch R-40M が届いた.当日開梱してインストールした時の記録写真を数点掲載.

荷姿
2基並んで発泡スチロールにサンドされている
箱から取り出して同梱されている説明書等を取り出したところ
Dali Zensor 1 とのサイズ比較 – フロント(Zensor 1 はインシュレータの分高く見えます)
Dali Zensor 1 とのサイズ比較 – トップ (左が R-40M)
Dali Zensor 1 とのサイズ比較 – リア(Zensor 1 はインシュレータの分高く見えます)
R-40M の底面 – 内側の MDF 部?は化粧板部より出っ張っている(0.? ミリ)
天面 – 化粧板(ビニール)の仕上げがよく見える角度から.ちょっと雑
袋ナットとブチルゴムシートでインシュレーター製作
インシュレーターを噛ませてインストール
グリルを外してみたところ – ホーンの形とカッパー色のウーファーがかっこいいと思う
とりあえず TEAC AI-301DA と接続

Zwift で 100km

image_printPrintable Format
Ascenders Team Singapore Zwift Ride – 100km 2.3-2.5W/kg

台風が以前の予報より早く過ぎ去り,明日は絶好のライド日和になるはずだが走れないので,今日のうちに長いめを…と思い 7:30 からのイベントに参加した.

2.3-2.5W/kg と advertise されていたのでぎりぎりもつかどうか?と予想.最初の30分くらいはソリッドに Zone 3 で,これは途中でリタイアかなと思っていたら,その後少し脚が回るようになってきたのと,全体のパワーが少し落ちてきたのとでなんとか完走できた.平坦基調のコースも幸いしたが,たまにくる登りは思い切って standing で 250W くらい踏んだ方が,集団内の位置も良くなって後しばらく楽できるのと,脚も少し軽くなる効果がある気がする.

リーダーの指示も良く,脱落しそうになることもなく結局 100km を2時間半ちょっとで完走.Zwift で 100km は二回目の達成.平均時速が思ったより速く,3時間かからずそれも助かった.平均出力 163W(2.43W/kg),TSS 175.

フラットファイル + シェルスクリプト「で」トランザクション

image_printPrintable Format

ファイルシステム「の」トランザクションではなく,データリポジトリにフラットファイルを使用して,ビジネスアプリケーションをシェルスクリプトで組むときにどうやって「トランザクション」を実現するかという話.

トランザクションは「データ(のセット)を(業務上)整合性のとれた一貫性のある状態に保ちながら更新すること」と言って良いと思う.これを実現するために,「コミット」や「ロールバック」の手段を整備することが必要になる.

SQL の場合,こんな感じにできるらしい.

BEGIN;

UPDATE accounts SET balance = balance + 10000 WHERE name = 'Jiro';

UPDATE accounts SET balance = balance - 10000 WHERE name = 'Taro';

COMMIT;

二つ目の UPDATE 文で,balance に「0または自然数」制約みたいなものが付いていて,なおかつ Taro の口座に 10,000円入っていなかった場合は,二つ目の UPDATE は失敗する.このとき DBMS は勝手に BEGIN の時の状態にデータを戻してくれる – Jiro の口座への 10,000円追加を無かったことにしてくれる(ロールバック).

これに加えて上記の SQL コードには陽に現れていないが,DBMS ではこのトランザクションが開始されると他のプログラムから Taro や Jiro の口座データが更新することはできない仕組みになっている(排他制御).同等のことを,フラットファイル + シェルスクリプトでどれだけシステマティックに実現できるかがテーマである.

排他制御をどう実現するか

ファイルアクセスの排他制御というと「ファイルロック」だが,UNIX では「アドバイザリロック」を使用するのが一般的だ.「マンダトリ(強制)ロック」がサポートされる OS もあるようだ(この辺のことはこちらを参照)が,ここではアドバイザリロックを前提にする.アドバイザリロックというのはあくまで「アドバイザリ」なので,カーネルがプログラムのファイルアクセスを禁止しているわけではない.従って,排他が問題になるプログラム群は,共通の排他制御のルールに従う(簡単に言うと,ファイルアクセスの際にいちいちロックの有無を確認→ロックする→アクセスする→ロックを解除する)ことが要求される.

アドバイザリロックを実現するにも何通りかのやり方がある.字面上いちばん名が通っているのが flock(2) である.ただ flock(2) は C プログラマ向けに用意された API であり,シェルスクリプトから利用するにはなんらかのパッケージを介在させる必要がある.Linux の場合は flock(1) コマンドが用意されているが,他の UNIX OS では一般に利用できない.また flock(1) も flock(2) も NFS に対応していないという仕様上の制限もある.

そういったことから,歴史的に「ロックファイル」の方式がよく使われる.これは,一つのファイルシステム上にパスが同じファイルは複数存在できないという仕様を利用しており,「ファイルAをロックする」ということを「(ファイルAに対応した)ロックファイルaを作成する」ということに読み替えてアドバイザリロックを擬似的に実現するものである.「ロックファイルaの作成を試みたが,できなかった」ということは「ファイルAは他のプログラムで使用されている」ということなので,プログラムはファイルAへのアクセスをいったん諦めて,時間をおいて再挑戦するなり,エラー終了するなりという挙動をするようプログラマがロジックを組む責任がある.

ところで「ファイルロック」を実現するのに「ロックファイル」を使用する,というのが用語上混乱をきたすことがあるかもしれない(自分がそうだったので).「セマフォファイル」という呼称にするとその心配がなくなるかもしれないが,本来セマフォは共有資源にアクセスできるプログラムの数を制限するためのものなので,排他制御の文脈で使うと(共有資源同時アクセス数 1 とすれば排他制御にもなるが)それはそれで混乱するかもしれない.

コミットをどう実現するか

フラットファイル + シェルスクリプトにおいて,データの更新方法はいくつかある.ここではフラットファイルがレコード(行)xフィールド(列)の二次元形式を取っていると想定すると,

レコード追記型更新

new_record="$(<処理>)"
printf "%s\n" "$new_record">> <フラットファイル>

非追記型(ランダム更新 – レコード挿入,削除,フィールド値更新)

<処理> < <フラットファイル> > <一時ファイル>
mv <一時ファイル> <フラットファイル>

こんなところだろう.ビジネスアプリケーションにおいては,レコード追記型更新で全てを済ませられる場合は少ないだろうから,ここは非追記型を想定する.非追記型はレコード追記型にも使用することができる.また,非追記型の更新は「更新試行」→「確定」というトランザクションのコミットの動きそのものである.あとは,トランザクションに関係する複数ファイルを「全て mv で更新する・しない」,言い方を変えると,複数ファイルのうち一部だけ mv で更新されてしまわないようにする制御を加えれば,トランザクションのコミットは実現可能ということになる.

なお mv による更新の魅力は,一時ファイルと本番ファイルが同一ファイルシステムに格納されている場合,それらを置き換えるにあたって I/O が発生しないことである(ファイルシステムメタデータの更新のため微量の I/O は発生するが,データを複製するための I/O は発生しない).この場合,一時ファイルによるフラットファイルの置き換えは一瞬で済ませることができる.

ロールバックをどう実現するか

コミットの考察で,非追記型更新を前提にすると述べた.この場合,ロールバックとは何かというと「mv で一時ファイルをフラットファイルに置き換えないままプログラムを終了すること」で良かろう.ただし単一ファイルの更新ならそれで良いのだが,複数ファイルの更新を行うトランザクションでは少々他の考慮も必要である.

2つのファイルAとBを更新するトランザクションがあったとする.AとBにロックを施して排他を宣言し,ファイルAとファイルBをそれぞれ更新していったん一時ファイルAと一時ファイルBを作成までできた(ここまででエラーが発生した場合は,一時ファイルAもBもない,や,一時ファイルAだけ生成されているという状況だから,そのままプログラムを終了すればファイルAとBの内容は整合性がとれたままである).

問題はこの後で,次は

  • 一時ファイルAをファイルA に mv
  • 一時ファイルBをファイルB に mv

するわけだが,前者が成功して後者が失敗した場合は,ファイルAを更新前の状態に戻さなければならない.ここが工夫のしどころとなる.

ファイルAの更新前の状態を保存するためにまず思いつくのは,トランザクション開始時に cp でバックアップを取っておくことである.

cp ファイルA ファイルA.org

そうして,一時ファイルBをファイルBに mv する操作が失敗したら,

mv ファイルA.org ファイルA

これでロールバックができた,ということになる(この操作が失敗したらどうするかというのは考慮事項だが,それはアプリケーションの要件によって変わってくるのでここでは議論しない.ロールバックが失敗したという通知をさせ,またはログを吐かせ,いったんシステムを停止する – ユーザーからのアクセスを遮断して管理者だけが操作可能にする – というのが定石だとは思う)

問題は,cp によるバックアップでデータ I/O が発生してしまうため,トランザクション開始時のオーバーヘッドが大きいということである.ファイルが数GBあった場合,トランザクション開始操作に数10秒を要することも想定される(RAM を大量に搭載していてファイルシステムキャッシュが潤沢に使えている場合は一瞬で完了する可能性はある – が毎回それは期待できない).

これを解決するのがハードリンクである.大学で共用計算機の SunOS を利用する際に「リンク」は基本「シンボリックリンク」を使え -「ハードリンク」は使ってはならぬと教え込まれたので,その理由もよく考えないまま(今から考えると,複数のファイルシステムにホームディレクトリが収容されていたからとかそういうことなんでは,と思うが)ハードリンクには馴染みのないままこれまで過ごしてきたが,cp の代替手段としてこのハードリンクが使える.

ln ファイルA ファイルA.org

これで「ファイルA」のデータは「ファイルA.org」という名前でも参照できるようになった.内部ではファイルシステム上で同じデータに違うラベルが追加された,ということなので,データ I/O は発生しない.この操作をおこなった後,「一時ファイルA」を「ファイルA」に mv して更新すると,「ファイルA」を指すと更新されたデータが得られるが,「ファイルA.org」を指すと更新前のデータが得られる.

従って,この後「一時ファイルB」を「ファイルB」に mv して更新する操作が失敗した場合,mv ファイルA.org ファイルA でファイルAを元の内容に戻すことが可能である.

定式(決め事)化

トランザクションでのデータ更新,コミット,ロールバックをどう実現するかの骨子は以上である.あとは以上の事柄をプログラマにできるだけ簡単になおかつ確実に実行してもらうための仕組み(決め事)作りである.

現在のところ,以下のような決め事とし,それをサポートする関数 INIT(), BEGIN(), COMMIT(), ROLLBACK(), END() を実装している(関数やファイル名の字面は実際とは異なる)

  • INIT() でトランザクションIDを設定して,本トランザクションで一時ファイル等を置く一時ディレクトリを作成
  • files-to-protect(トランザクション中,他のプログラムからのアクセスを遮断しなければいけないファイル群)を定義
  • files-to-update (トランザクション中更新するファイルと,更新一時ファイル名)を定義
  • BEGIN() で排他ファイルロックとハードリンク作成によるバックアップを実施
  • トランザクション処理はサブシェル内に記述し,エラーが発生すれば non-zero exit させる
  • non-zero exit (異常終了)した場合は ROLLBACK() する(ただし,この場合の ROLLBACK() は実質ほぼ何もしない – バックアップハードリンクを消すだけである)
  • zero exit (正常終了)した場合は COMMIT() するが.ここで失敗すると ROLLBACK() する
  • END() で排他ファイルロックを解除,バックアップハードリンク消去,トランザクション用一時ディレクトリを削除する
# トランザクションの初期化
INIT || exit 1

# このトランザクション中,排他アクセスが必要なファイルをリスト
cat <<EOF > files-to-protect || exit 1
ledger-Taro
ledger-Jiro
EOF

# このトランザクション中,更新するファイルとその一時ファイル名をリスト
cat <<EOF > files-to-update || exit 1
ledger-Taro    ledger-Taro.new
ledger-Jiro    ledger-Jiro.new
EOF

# トランザクション開始
BEGIN || exit 1

dt="$(date "+%Y/%m/%d %H:%M")" || exit 1
name=フリコミ
amount=10000

# トランザクション処理(事前に名前を決めておいた一時ファイルに更新データを出力する)
(
  # Taro の通帳更新
  printf "%s\t%s\t%d\n" "$dt" "$name" "-$amount" > /tmp/transaction || exit 1
  cat ledger-Taro /tmp/transaction > ledger-Taro.new || exit 1

  # Jiro の通帳更新
  printf "%s\t%s\t%d\n" "$dt" "$name" "+$amount" > /tmp/transaction || exit 1
  cat ledger-Jiro /tmp/transaction > ledger-Jiro.new || exit 1
)
es=$?

if [ "$es" -ne 0 ] ; then
  # トランザクション処理が失敗した場合 ROLLBACK() が一時ファイルを消去する
  ROLLBACK || exit 1
else
  # トランザクション処理が成功した場合 COMMIT() が一時ファイルを本番ファイルに mv する
  COMMIT || {
    # コミットに失敗した場合 ROLLBACK() がバックアップハードリンクから本番ファイルをリストアする
    ROLLBACK || exit 1
  }
fi

# トランザクション終了(END() がファイルロック解除,バックアップハードリンク消去を行う)
END || exit 1

Saris Cup 2回目

image_printPrintable Format
順位表
分析画面

なんとか第二集団についていこうとすると,5分くらいパワーグラフが赤々していたのでどっかで脱落してまうな…と思っていたが,そのうち集団のパワーも落ちてきて,なんとか粘れた.黄色領域でも多少は回復❤️‍🩹できるんか?などと頭が巡ることもあるが,ちょっとした数十メートルののぼりがくると,220W程度出さねばならず,それがまぁ辛い.こんな平坦基調のコースでもコレだから,上りが目立つコースだとイッパツで脱落するに違いない.Zwift Academy でスプリンター判定が出てしまったので,今回は最後の300mくらいを頑張って踏み込んだ.結果,第二集団では先頭ゴール.

(Zwift 上の) FTP が 200 → 203 になってもうた.ワークアウトしんどなるわ…

Klipsch R-40M をポチる

image_printPrintable Format

「高能率スピーカーは生々しい」という話がある.

小型スピーカーは基本的に低音が出ないので,わざと高音を出にくくして(高音のドライバーに入る入力を小さくするようネットワークをチューニングして)やるというのが定石のようなのだが,そうすると高音の音の解像度が下がってしまうということらしい.現在使用中の Dali Zensor 1 の場合は 87dB/2.83V/m というカタログスペックだが,大体 80dB 代後半が小型ブックシェルフスピーカーの定番値だ.室内楽でもう少し生々しい音が聴けないか(たとえばヴァイオリンで弓が弦に当たる音が聴こえるとか)?ということで高能率で評判の Klipsch スピーカーをポチった.モデルはいくつかあるが,先日サブウーファーを導入したので,ウーファーはもう極小で良いという判断から最小モデルの R-40M にした.

やっぱり来たら数字を見てわかったような気になりたいので,以下にスペックをダンプしておく.

項目Dali Zensor 1Klipsch R-40M
形式2ウェイ2スピーカー・バスレフ2ウェイ2スピーカー・バスレフ
HF25mm ドーム型ツイーター2.54cm アルミニウム LTS ツイーター
LF130mm コーン型ウーファー10.16cm TCP ウーファー
インピーダンス
再生周波数帯域53Hz – 26.5kHz71Hz – 21kHz
クロスオーバー2.9kHz1,770Hz
出力音圧レベル87dB/2.83V/m91dB @ 2.83V/1M
サイズ(WHD, cm)16.2 x 27.4 x 22.814.6 x 29.8 x 21.6
質量4.2kg3.2kg
定価(ペア,税抜)37,800円43,000円
発売年20112022
Zensor 1 vs R-40M