おそらくはそれさえも平凡な日々

mattn/memoでmemoのテンプレートが使えるようになったので便利

最近メモソリューションを見直していて、glidenote/memolist.vimmattn/memoを相互運用する、メモはDropboxに放り込んでおくのが良かろうという結論になった。

相互運用する上で少し困ったのが、glidenote/memolist.vimには、メモを作る際のテンプレート機能があるのだが、mattn/memoにはその機能が無かった。

なので、pull requestを送って 取り込んでもらった。

使い方としては、以下のように設定ファイルにテンプレートファイルを指定します。

memotemplate = "/path/to/template.txt" # default: '~/.config/memo/template.txt'

テンプレート自体は以下のようなGoのtext/template形式です。

---
title: {{.Title}}
date: {{.Date}}
---

{{.Title}}
===========

このようにすれば、 memo new でYAML Frontmatter付きのMarkdownを出力してくれます。

ちなみに、テンプレートファイルには、上記のtext/template形式の他にも、以下のような glidenote/memolist.vim の形式も直接利用が可能(!)です。

title: {{_title_}}
==========
date: {{_date_}}
tags: [{{_tags_}}]
categories: [{{_categories_}}]
----------

これは、相互運用上は便利なのですが、やりすぎ感があるなーとか思ったのですが、これもmattnさんにシュッと取り込んでもらえたので助かりました。

ぜひご利用ください。

ISUCON8は6位と惨敗

ISUCON8だめだった。「死闘の果てに」チームで @najeira さんと @bluerabbit777jp さんと組んで6位でした。順位的には悪くないのかもしれないけど、そもそも優勝できない時点でダメ。しかも結構僕が勝てる問題内容だったし、優勝と3位が学生チームということで、なんというか、色々ショッキングであるとともに脱帽です。

しかし、すごく良い問題かつ運営だった。楽しい時間を過ごさせてもらい、運営の皆様には感謝申し上げます。これまでのISUCONの集大成とも言えるような問題だったと思います。

チームメンバーにも恵まれただけに、優勝できなかったのはなおさら残念。まあ、冷静さを欠いたところと、純粋に実力が足りなかったというところが敗因でしょう。良い経験になりました。

当日の朝

najeiraさんと、bluerabbit777jp さんがやる気で、ディスプレイ持参。そして「一番良い席を取ろう」ということで、受付開始前から集合することにした。結果として、狙い通りに一番乗りで入場し、狙い通りの席に設営をすることができた。

チームメンバーが前のめりでモチベーションが高かったのは、今回結構引っ張ってもらえたな、と感じている。

今回はオンライントレード

今回は、外部APIが絡むオンライントレードということで、まあとにかくボリュームが大きかった。ファイルもいつものISUCONのシングルファイルではなくて、modelやcontrollerに分割されていた。ドキュメントやレギュレーションもめちゃくちゃ書き込んであって、これには正直面食らった。

これで、結構焦ってしまって、レギュレーションやドキュメントを読み込むのを怠ってしまったのが結局敗因になった。

言語はGoを選択。というかメンバー的にそれしか考えてなかった。

序盤の戦い

最新のトレードを取得するところが豪快に全件取得しているところを、単に SQLに LIMIT 1 を足すだけでスコアが大幅に改善した。多分これは全チームの中でうちのチームが一番早く見つけたと思う。このあたりは、 bluerabbit777jp さんと najeira さんが見つけていたけど、今回は bluerabbit777jp さんのSQL周りのボトルネックをすばやく見つけるところに関しては全般的に頼りになった。

で、予選と同じく、 github.com/najeira/measure を仕込んで、時間のかかっている処理を探して、細かいところを潰し終わった後は、najeiraさんがlogのAPI送信部分の非同期化を担当して、僕はRunTradeの最適化をする感じで役割分担をすることにした。

序盤の落とし穴

このあたりで暫定トップに立ち、良い感じに戦えているつもりだったけど、実葉逆で、一旦冷静になるタイミングを設けるべきだった。特にこのくらいの段階でレギュレーションの読み合わせ読み直しをするべきだったなーと思っている。結果として、logのAPI送信部分をbulk送信できる仕様を読み落とすという今回一番の痛恨のミスをすることとなった。

あとは、今回は僕はインフラは他のメンバーにお任せで、僕はアプリケーションコードを書くことに集中するつもりだった。しかし、他のメンバーがDockerの設定が不慣れであったこともあったので、ここもこのあたりで役割分担を見直す作戦会議をしておけばよかった。結果として僕も役割分担が不明確なまま中途半端にインフラをいじったり助言することになってしまい、終わった後に後悔した。

他には、MySQLにパーティションが切られていることはパーティションスキーとして当然最初に気づいていたが、これを素直に外すことを勘ぐってしまって、手を出さなかったことは悔やまやれる。

楽しいRunTrade最適化問題

僕が担当したトレードの処理を実行するRunTradeの部分は、今回僕にとって一番楽しく難しくやりがいのある部分だった。これが「今回の問題の急所の一つだな」とすばやく気づくことができたのは、今回の手応えの一つ。

まず、無駄に再帰処理になっている部分を外し、関数の実行時間の統計を取りやすく修正した。その後に、この処理がユーザーが取引を追加する毎に無駄に重複して呼び出されていることも見抜いたので、これをロックを取りながら高々一つの処理しか動かないように修正をおこなった。

このへんで一旦マージしようとしたら、僕のしょぼいミスでシングルトンの初期化にミスっていて動かなかったり(ここはnajeiraさんにレビューしてもらって解決した)、najeiraさんの変更と盛大にコンフリクトしてここの解消に時間がかかってしまった。

この後の処理として、アプリケーションサーバー1台でしかRunTradeの処理を動かないようにしたり、そもそもRunTrade自身の処理を見直すところまでやりたかったので、そこが結局手を入れられなかったのは悔いが残る。

特に、RunTrade自体の処理を最適化するところは、今回の問題の一番面白いところだったと思うのでそこまでいけなかったのは非常に残念。初期実装だと、売り注文か買い注文のどちらかを一つに固定して、それに適合する買い注文なり売り注文なりを探す素朴な処理になっていたのだけど、ここは売り注文と買い注文をそれぞれ複数にもできるということを懇親会のときに運営の人から聞いて「やっぱりそうだったかー」という気持ちになりました。

最後の一手

何をやっても点数が伸びず、インフラ構成も、DB1台、アプリケーション1台からも脱却できずこまっていたのだが、 bluerabbit777jp さんが、トレードの集計情報を集計テーブルに持たせる処理を入れて、これが結構効果があった。

後は、負荷を上げるために、ソーシャル拡散機能を確率的にonにする調整を行いながら終了。このあたりの調整で、結局非同期でやっていたログ送信部分を同期処理に戻すということもおこなうことになった…。最終的には、1万3千点位を安定的に出すようになっていた。

最後は、再起動試験も行うのを諦め終了した…。DB1台、アプリ1台しか使えなかったので、再起動試験しなくても大丈夫だろうと思ってはいたのですが、それを含めてそもそも再起動試験を諦めたのは、競技者として失格感がありますね…。

敗因の一つはlogのAPIのbulk送信を見落とすミス

そんな感じで競技が終了し、1位には3倍近くの差をつけられての6位でした。

敗因は、logのAPIのbulk送信を見落としていたことに尽きます。ここの非同期化が必須であることはチームメンバーで見抜けていたのですが、ドキュメントを見落としていたことは痛恨です。

ここさえ気づいていれば、かなり順位をあげられたと思うのですが、そもそもそこをちゃんと読まなかったことが実力不足ということはわかっています。

まとめ

自分が得意だったはずの分野で学生に惨敗してしまったことは、ショックが大きい。去年3位で終えて、もうISUCONは引退しようと思う気持ちもあったが、今回の惨敗を受けて、まだまだ来年もリベンジしようと思った。何より、ISUCON1からずっと本戦に出続けているのは、fujiwaraさんと僕の2人だけなので、そこは負けるまで続けようと思う。

今回は、本当に非の打ち所がない問題と運営だった。バクもなくベンチマークも快適でした。毎年運営のレベルも上がっていて、本当にすごいことだと思う。

LINEさんの運営や、DeNAさん、カヤックさんの問題作成に関しては、予想通り流石だなと感じたのですが、サーバー提供のGMO ConoHaさんもサーバー構成や、人員構成含めすごい体制でのフルコミットだったようで、そこは懇親会などで話を聞いて感服しました。懇親会前の運営企業アピールタイムでもっとそこを押せばよかったのに、とも思ったりしました。

ということで、ISUCON続いて欲しいと思っております。来年も楽しみにしております。

ISUCON8予選突破した

今年も予選から素晴らしい運営、問題で堪能させてもらいました。

去年本戦3位で終わり、思うところがあり、今年は一人で出るか、もしくは出るの見送ろうと思ってたんだけど、TwitterのTL上でnajeiraさんが、メンバー募集していたのを見て、急に出たくなってしまって一緒に参加させてもらうことにした。

これまで、同僚か元同僚かとしかチームを組んでこなかったので、あまり馴染みがない人と組むのは楽しそうだし、そういう新しいチャレンジも面白そうに思ったのだ。

ということで「死闘の果てに」と言うチーム名で、najeiraさんとbluerabbitさんと参加することにした。チーム命名はnajeiraさん。

事前の顔合わせでも、fabricでdeployしましょう、解析には github.com/najeira/measure を使いましょう、とかこれまで使ったことのないツールを使うことになって、楽しみになったし、実際良い経験になった。

分担としては、bluerabbitさんがインフラメイン、najeiraさんと僕でコードを書くという感じ。実際にはnajeiraさんもNginxへの入れ替えとかインフラタスクもやってもらえたので助かった。

予選当日

予選は日曜参加で、najeiraさんの職場の環境を使わせてもらった。ちゃんとした椅子もディスプレイもあり、ゲストWifiもしっかりしていて快適で良かった。

今回の問題は、ISUCON2を彷彿とさせる問題だけどしっかりボリュームもあって手ごわかった。

最初にミドルウェアの構成を見て、MariaDBなのはCentOS7だからそりゃそうかという感じ。h2o使っているところを見て、なるほど、これはDeNA出題だな、とも。h2oのままでも問題ないとは思ったけど、najeiraさんが使い慣れているNginxに切り替えていて、そこは僕も特に反対する理由もなかったのでそれはそうしてもらった。

僕はまず肩慣らしに、rankのvalidatをDB引かないいようにしたり、sheetsテーブルをオンメモリ化したりしていた。この辺はあまり意味がなさそうではあるけど、コードを触りながら雰囲気を掴むためにやったという感じ。

あとは、reservationsテーブルのcanceled_atとreserved_at辺りのクエリが厄介なことになっていたので、last_updated_atというカラムを追加して、最終更新で並べたい場合はそっちを見るようにして、インデックスもそっちにだけ張るようにした。この時初期化処理で、以下のようなSQLを発行するようにした。

UPDATE reservations SET last_updated_at = GREATEST(reserved_at, IFNULL(canceled_at, reserved_at))

恥ずかしながら、 GREATEST を知らなくて、najeiraさんにその場で教えてもらうというグレートな体験をした。

この辺り、粛々と変更してたんだけど GREATEST の中で IFNULL しないと上手く動かなかったり、 SELECT * してるところを展開しようとしたらミスってたりと、細かいところで手こずってしまって、チームにも迷惑をかけてしまった。

このへんで、najeiraさんが getEvents 周りの処理を効率化したのも合わせて点数はそれなりに伸びた。この時点で14時くらいで、点数は2万点くらい。

Redis不発

で、空席管理に関しては、これは「ISUCON2で見たやつや」という感じになって、RedisのSetを利用した実装を書き始める。

16:30には予約のデータをRedisに入れる実装が完了。あとはDBから予約を取るのを止めてRedisのみにすればよい。そこの実装も17時には完了。

と、思いきや全然ベンチが通らない。その時点で、bluerabbitさんのreserved_sheets テーブルの追加が有効に機能しており、4万5千点と予選突破が濃厚な点数が出ていたので、この実装を入れることは残念ながら諦めることに。

終わるまでの間、コードを見直していた所、ものすごく馬鹿なミスをしていたことが判明。それが以下のコード。

rediKey := fmt.Sprint("s:%d:%s", event.ID, params.Rank)

…お分かりだろうか。fmt.Sprintf の "f" が足りない!!!これはひどい…。

ということで、一箇所キーの作り方を間違えていたのでありました。いやー予選通って良かったですね。これが原因で通ってなかったら戦犯ものです。

直してベンチ回したかったものの、スコア保全のために断念。予選でスコア保全策に出てしまうと最後ちょっと手持ち無沙汰になってしまうのは致し方ないとは言え残念な気持ち。

しかし、ISUCON2で優勝したときはtypesterさんがしっかりRedis実装を入れていたのに、今回、僕が同じことができなかったのは無念というか、衰えというか現場間の鈍り感じずにはいられませんでした。

とは言え、何はともあれ本戦に行けたのは良かった。本戦ではもっとバチッと強い修正を炸裂させて、満足のいく結果を出したい。

najeiraさんとbluerabbitさんとは結構うまく連携が取れたんじゃないかと思う。najeiraさんとbluerabbitさんの二人がチームを組んだ経験があって息が合っていたので、後は僕が合わせるだけだったのでやりやすかったのかもしれない。良い経験になって楽しかった。

予選が良い問題だったので、本戦も楽しみです。運営の皆様、いつもありがとうございます!

チームメンバーのレポート

ghqを使っていても(使っていなくても)goimportsを爆速にする

ghqを使ったローカルリポジトリの統一的・効率的な管理について」というエントリで書かれているように、 ghq.root$GOPATH/src を一緒にする運用で長年やってきて、goimports が遅いことに少し困っていたのだが、以下でめちゃくちゃ快適になった。

どれくらい快適になったかというと、エディタの保存時に goimports を気兼ねなく実行させられるようになったくらい。以下解説。

.goimportsignoreを使う

最近の goimports には $GOPATH/src/.goimportsignore という除外リスト機能がある。ここにインポート対象外にしたいディレクトリを $GOPATH/src 以下の相対パスで記述すると、 goimports はそこを無視する。 ghq を使って、 $GOPATH/src 以下にGo言語以外のリポジトリもガンガンcloneしまくっている自分としては非常に助かる。

goimports-update-ignore で自動生成をする

もちろん、この .goimportsignore をイチイチ記述するのは現実的ではないので、 goimports-update-ignore というツールで自動生成する。go get でインストールできる。

% go get github.com/pwaller/goimports-update-ignore

goimports-update-ignore はGo以外の言語のディレクトリを抽出して、 .goimportsignore に追記してくれるコマンド。以下のように実行する。

% goimports-update-ignore -max-depth 5

-max-depth オプションはデフォルトで3だが、ちょっと足りないので5にしている。実行時間はちょっとかかるのでちょっと待とう。僕の手元だと40秒程度。

goimports-update-ignore の地味に良い点は、自動生成部分以外に自分が除外したいディレクトリの記述をしておけるところです。例えば、僕はGoのソースコードを読むために github.com/golang/go をcloneしていますが、これは当然 goimports の対象になって欲しくない。ただし、これは当たり前ですがGo言語のプロジェクトなので、 goimports-update-ignore には除外対象とはみなされません。

ですので、僕の .goimportsignore の冒頭は以下のようになっています。

github.com/golang
# Generated by goimports-update-ignore 2018-08-28 15:54:41.607268 +0000 UTC
anonscm.debian.org
cloud.google.com/go/bigtable/testdata
cloud.google.com/go/datastore/testdata
(snip)

先頭の github.com/golang は手で書き加えたものですが、この時 # Generated... 以前の部分は、 goimports-update-ignore を再度実行しても書き換わることがないのです。ですので、ここに自分が個別に除外したいディレクトリを書いておくことができます。

僕は goimports-update-ignore は気が向いたときに再実行して、 .goimportsignore を再生成するようにしています。これで .goimportsignore を活用できるようになり、 goimports が現実的な速度で動くようになりました。

最新の dragon-imports を使う

「現実的な速度」で動くようになったと前項で書きましたが、それでもGoライブラリの数が増えると挙動がもっさりしてきます。そこで、更に goimports を速くする方法として、 dragon-imports という裏技的なツールがあります。

これは、今ローカルに入っているライブラリ情報を抽出して、 goimports コマンド自体を作り直してしまうというなかなか豪快なツールです。詳しくは作者の「goimportsを高速化するdragon-importsコマンドをつくった」というエントリを参照ください。

こちらも go get でインストールして、 dragon-imports コマンドをおもむろに実行するだけです。

% go get github.com/monochromegane/dragon-imports/cmd/dragon-imports
% dragon-imports

これで、 goimports コマンドが作りなおされ、実行速度が爆速になりました。こちらも気が向いたタイミングで再実行して作り直すと良いでしょう。

実は、 dragon-imports は近年のGoのフィーチャーに追随できていないところがありました。そこで、最近僕が盆栽活動のように幾つかpull requestを送り、取り込んでもらいました。これで、 .goimportsignore を見るようになったり、 vendor/ を無視するようになり精度が向上しました。

結果、以前よりも安定しつつも更に爆速になり、出たばかりのGo1.11でもお使いいただけるようになっています。もし古いものをお使いの場合は最新をお試しください。

ということで、これでかなりGoの開発が快適になりました。

ちなみに、 dragon-imports は「みんなのGo言語」で僕が書いた章の中で紹介させてもらっていたので、開発にコントリビュートできたのは嬉しかったです。

みんなのGo言語【現場で使える実践テクニック】

中小規模のOSSにおけるバージョニング戦略について

TL;DR

本編

OSSのバージョニングについての個人的な意識について書きます。「中小規模」というのは、個人の趣味だったり、企業でやっていたりしてもそんなにリソースをかけられない場合くらいの規模感、つまり僕自身がメンテナンスに関わっているようなソフトウェアの規模感でもあります。とは言え、リソースが十分なプロジェクトなどほとんど無いと言えるので、多くの場合に当てはまるでしょう。

互換は壊すな!…なるべく

そもそも互換は壊すなよ、と言う話。互換は壊さないほうがいい。これに異を唱える人はいないでしょう。

「互換を壊したらメジャーバージョンを上げればいい」ではなくて「絶対に壊さない」くらいの意識でAPI設計をする。それくらい慎重に設計したとしても互換を壊さないといけない日は往々にしてやってくる。

非互換修正は自分に跳ね返ってくる

そもそも、互換を壊すことはユーザーを失うことです。非互換修正に追随するコストをユーザーに払わせる以上のメリットをユーザーに示せるかどうかという当然のトレードオフ以前に、これまでせっかく積み上げてきた既存ユーザーの一部を間違いなく失うという事実は受け止めなければならない。

メジャーなソフトウェアであればいざしらず、システムの片隅で使われるようなソフトウェア、つまり僕たちが作っているようなソフトウェアに非互換修正が入り、システム全体が動かなくなるとしたらたまったものではないでしょう。

それが、数少ないユーザーの全てを失う要因になるかもしれません。だからこそ、非互換修正をしないように注意を払う必要がある。これはソフトウェアとしての協調性の話でもあります。

もちろん、自分以外誰も使っていないようなソフトウェアであればその限りではありません。

それでも互換は壊れる

もちろん人間は間違えるのでイケてないインターフェースを作ってしまうことは避けられません。また、過去には良いとされていたインターフェースも、時代が変わってイマイチになることもなる。それに、人間が進化してより良いインターフェースを発明することもあります。

脱線しますが、そもそもインターフェース設計は難しいものです。semver的には、バージョン0の間は互換を壊してもいいことになっているので、そこで実際にユーザーに利用してもらい、トライアンドエラーを繰り返しながらインターフェースを固めていくのは良いやり方です。

話を戻すと、いくら初期設計時に慎重を期したとしても後方互換を壊したくなることはあります。とは言え、本当に壊す時にはそれ以上に慎重になるべきです。新インターフェースのほうが今より間違いなく良いという確信を持ってから互換を壊すべきです。

「新インターフェースのほうが今より間違いなく良いという確信」とはつまり、現行バージョンを使い続けるユーザーを最終的には切り捨てる覚悟をするということです。「新インターフェースのほうが間違いなく良いから、全員移行すべき」だと考える必要がある。新旧インターフェースどっちでも良いのであれば、混乱を招くだけなので、非互換修正すべきではないでしょう。

その際、移行パスを含めて事前アナウンスをしっかりやることが理想です。semver的にはメジャーバージョンを上げることになるでしょう。

複数バージョンをメンテしようなどとはゆめゆめ思うべからず

「メジャーバージョンを上げたとしても、旧バージョンもちゃんとメンテすればいい」という意見もあるかも知れませんが、それはほとんどの場合非現実的な理想論で、やるべきでもないと考えます。気軽に非互換変更をしてメジャーバージョンを上げ、逆にここを真面目に考えすぎて消耗してしまうケースが結構あると感じたことがこのエントリを書くモチベーションになっています。

ソフトウェア開発における再優先事項はメインラインに手を入れ続けることであり、旧バージョンのメンテナンスに労力を費やすことは、よほどリソースに余裕が無い限りは意味がありません。意味がないどころか、メンテナのモチベーションに逆効果になることも多いでしょう。

もちろん移行猶予期間として、一定の間、旧バージョンのメンテナンス期間を設けることは誠実ですし、ユーザーが減ることも防ぐでしょう。リソースに余裕があればやれるに越したことはありませんが、余裕があればの話です。旧バージョンのメンテナンスという、あまりモチベーションが上がらなさそうなタスクをやりたい人がいるかどうかも大事な観点です。

それ以外の理由で旧バージョンのメンテを期限を設けず継続することは無駄だと考えます。旧バージョンにも使い続けられる良さがあるのであれば、新バージョンは名前を変えるなどして、別ソフトウェアとしてプロジェクトを分けてしまった方が良いでしょう。

結果として古い方のソフトウェアのメンテナンスは滞るかも知れません。それでも使い続けたい人がいるのであれば、その誰かにメンテナンスを引き継いでしまうこともできます。そっちの方が、旧バージョンのメンテナンスを押し付けられることより、モチベーション高く引き受けてもらえるのではないでしょうか。

まとめ

話が戻ってきますが、結局の所、非互換修正は作者側も大きな痛みを伴う決断です。それらを踏まえて、非互換修正を入れるかどうかの判断をするべきでしょう。

セマンティックバージョニングに対しても思うところがあるのでなにか書こうと思ってましたが、今回はここまで。とは言え誤解を生まないように書いておくと、semver自体は悪いものだとは決して思っていません。

「UNIXという考え方」における「沈黙は金」は常に正しいか

みんな大好き「UNIXという考え方」に「沈黙は金」という項目があって「コマンドが成功した場合には余計な出力をすべきではない」ということが書いてあります。

ユーザーに会話調で話しかけることがユーザーを助けることだ、と考えているプログラマが多すぎる。UNIXは一般に「ドライ」で、ただ「事実」だけを伝える。

まず、この項目はこの本の中では「10の小定理」というものの中に含まれるもので、小定理については必ずしも常に正しいとは限らないというような注意書きが書かれていることを先に断っておきます。

コマンドラインツールはデフォルトでは余計な出力をすべきではないというのは基本同意です。詳細な途中経過のログを流したいのであれば、 --verbose オプションが実装され、それが利用されるべきでしょう。それに最近作られたツールは出力を出し過ぎで煩わしく感じることも多くなってきました。

ただ、それは少し偏屈な技術者の思考じゃないのかとも思うようにもなりました。とっつきやすさのために、少しだけフレンドリーなログを出してあげるのもアリなのではないでしょうか。実際、初めてコマンドラインを触ったとき、あまりにもぶっきらぼうでとっつきづらいと感じる人がほとんどでしょう。 svc とか初めて触ったときビビりませんでしたか?

なので、標準ではログを吐きつつ、 --silent オプションで出力を抑制するようなパターンもありだと思います。

ただ、ここで大事なのは「ログ的な出力には必ず標準エラー出力を使う」ということです。標準出力は他のプログラムに受け渡されうるデータを出力する目的のみに利用すべきだからです。これは、定理9の「すべてのプログラムをフィルタにする」にも書かれていることで、これは完全に正しいです。

ところで、最近Go製のツールによくあるんですが、 --help の出力が標準エラー出力に出るのはページャーに渡しづらくてめんどくないすか? 2>&1 | less とかやるのがダルい。これは「明示的にヘルプ出力が望まれているのだから標準出力に出すべき」派です。

UNIXという考え方

git-set-mtimeをリリースしました

https://github.com/Songmu/git-set-mtime

ローカルファイルとディレクトリのmtimeをgitの最新コミット日時に書き換えてくれるツール。rosylillyが作っていたやつを以前から使わせてもらっていたのだけど、それをforkしたままバイナリリリースもしました

% go get github.com/Songmu/git-set-mtime

でもインストール可能です。オリジナルに対する変更点は以下のとおりです。

特にruby版削除は大きな変更になってしまうので、fork側で別途メンテナンスさせてもらうことにしました。ライセンスなどは元のライセンスを残しつつ適宜アップデートしているつもりですが、変なところがあったらご指摘ください。

たまに便利です。ご利用ください。

blogsync v0.9.1をリリースしました

https://github.com/motemen/blogsync

はてなブログ用の便利コマンドラインツールであるところの、blogsync v0.9.1をリリースしました。最近は日本語READMEを載せたり、バイナリリリースも作ってあるので便利にご利用いただけます。

直近の大きなアップデートは以下のとおりです。

omit_domain によるダウンロードパスの変更

omit_domain という設定項目が追加されました。この設定をオンにすると、そのブログをダウンロードしてきた際に、ダウンロードパスにドメインが含まれなくなります。

yourblog.example.com:
  local_root: /path/to/myblog
  usename: xxx
  ...

例えば、上記の設定ですと、 /path/to/myblog/yourblog.example.com/ 配下にblogの内容がダウンロードされます。

このとき、 yourblog.example.com/ というディレクトリが掘られてしまうのですが、それが煩わしい場合は、 omit_domain の出番です。

yourblog.example.com:
  omit_domain: true
  local_root: /path/to/myblog
  usename: xxx
  ...

このように、omit_domain を設定すると、 /path/to/myblog/ 直下にブログをダウンロードできます。

設定ファイルの読み込みの改善

blogsyncは、ローカル設定(./blogsync.yaml)とグローバル設定(~/.config/blogsync/config.yaml)の2つの設定ファイルを持つことができます。

最新バージョンでは、ローカル設定とグローバル設定に同じブログドメインが設定されている場合、その設定がいい感じにマージされるようになりました。同一の設定キーが設定されている場合は、ローカル設定が優先となります。

まとめ

実は、Mackerel の各種ドキュメントは、はてなブログを利用しており、版管理のためにblogsyncとGitHubを組み合わせています

というように、blogsyncは一部ではかなり実績のあるツールですのでぜひお試しください。

もう少しテスト書くなどして、バージョンv1.0.0を出したいと考えています。

DBIx::Sunny 0.9991をリリースしました

https://metacpan.org/release/SONGMU/DBIx-Sunny-0.9991

みんなだいすき、安全安心のkazeburo wareであるところのDBIx::Sunnyですが、この度、バージョン0.9991をリリースしました。pull requestを送りまくっていたら、コラボを頂いたといういつもの流れです。

最近の大きな更新は以下の通りです。

特にNamed placeholderはアツくて、以下のようにクエリ発行系のメソッドの第2引数にhashrefを渡すと利用できます。便利。

$dbh->select_all('SELECT * FROM users WHERE id IN (:ids) AND status = :status', {
    ids    => [1,2,3],
    status => 'active',
});
#SQL: 'SELECT * FROM users WHERE id IN (?,?,?) AND status = ?'
#@BIND: (1, 2, 3, 'active')

内部的には、cho45氏作のSQL::NamedPlaceholderを利用しています。このモジュールは、はてなブログ内部でも開発当初から利用されていることもあり、長らく実績のあるモジュールなので安心してご利用いただけます。

最近大きめのリファクタリングを入れたので、一旦0.9991というバージョン番号で出してますが、しばらくおいて問題なさそうならバージョン1.0000を出そうと考えています。

より便利になった、DBIx::Sunnyをぜひご利用ください。

ちなみに、Perlでversion.pmを使わないでバージョン宣言をする場合、小数点以下は4桁にするのが良いんじゃないかと最近は考えています。ちなみにPlackもそうなってました。

それは、Perlのバージョン番号の変換ルールの仕様で、マイナーバージョンとパッチバージョンは小数点以下3桁ごとに区切られるからです。

つまり、0.9991は、v0.999.100と等価です。パッチバージョンをまともに3桁割り当てると、小数点以下6桁になって見づらくなってしまうので4桁にするのが穏当なのではないか。

ところで、version.pmを使わないでバージョン宣言をするのは、そのモジュールがPerl5.8系をサポート対象とする場合の話で、Perl5.8系を切り捨てて良いのであれば、素直に、version.pmを使ったほうが良いでしょう。

IoT時代の自宅ネットワーク

タイトルは釣りです。IPv6の話とかも出てきません。以下の様な話です。

OCNからNuro光に乗り換えた

10年近くOCNを使っていたのをNuro光に乗り換えた。自宅のマンションでキャンペーンやってて簡単に乗り換えられそうだし、大分安くなるので契約した。

ただ、正直、ブロッキング問題がなかったら今回の変更には至らなかったと思う。

ネット回線は金額よりも品質のほうが大事なのでこれまで選定はコンサバに振ってたし、それでOCNの回線には大きな不満もなかった。乗り換えることで品質がどうなるかは未知数でリスキーであることもあり、単に安くなるだけだったら今回の乗り換えにはいたらなかったと思う。

なので、ブロッキング問題が今回の背中を押すきっかけにはなったと思う。ただ、そこまで強いポリシーを持っているわけでもないので、キャンペーンをやってなかったらそのままOCNを使い続けていたと思う。

当たりルーターを引いた

Nuro光が回線工事時に持ってくるルーターは何種類かあるのだが、それを指定することはできない。工事が終わった後に別のやつに変えてもらうように手配をすることはできるらしい。ただし有料。

今回は、運良く当たりルーターを持ってきてもらうことができた。高速無線通信可能なIEEE802.11ac対応のEchoLife HG8045Qという新モデル。

もう一つ、ZXHN F660Aという、IEEE802.11ac対応の新しいルーターがあるのだけどこっちが来ていたらかなり困ることになって危なかった。というのも、こちらは同時接続機器数が10台なのだ。

ご家庭のIP機器の多さ

10台あれば充分と思われるかもしれないが、実は全然充分ではない。僕が職業柄多めだというのもありそうだが、とはいえガジェッターでもないのでメチャクチャ多いわけでもないと思う。以下に書き出してみる。

実に17台。全部が常時つながってるわけではないが、それでも10台では全然足りない。

ということで、今や色々なものが簡単にインターネットに繋がる時代なので、家庭内でもIPを持っている機器は想像しているよりずっと多い。ルーターのIP払い出し一覧を眺めていてびっくりした。家庭ですらこの有様なのですからオフィスネットワークはマジで大変そうですね…。