watermint.org - Takayuki Okazaki's note

Go: Bandwidth limit for multiple Reader and Writer

Bandwidth limit is really important for stabilizing systems. Core business logic stops if low priority job consume entire bandwidth of the system. When writing bandwidth limit for Reader or Writer in Go, I found go-flowrate. This library enables easy way to limit bandwidth like the code below.

func main() {
    f, _ := os.Open("data.dat")

    // Create wrapper with 100 bytes per second
    f2 := flowrate.NewReader(f, 100)

    // Biz logic
    // ...
}

APIs are pretty simple and reliable. But go-flowrate can limit bandwidth only for single Reader or Writer. In my case, I was working on file uploader for Dropbox which has concurrent upload feature. This code require limiting bandwidth for multiple I/O. So I decided create new small library for bandwidth limit.

bwlimit

bwlimit is the name of my library. You can see or clone from github bwlimit. Here is code example of bwlimit.

func main() {
    // Limit 100 bytes per second
    bwlimit := NewBwlimit(100, false)

    // Prepare multiple readers
    f1, _ := os.Open("data1.dat")
    f2, _ := os.Open("data2.dat")

    // Create wrapper
    fr1 := bwlimit.Reader(f1)
    fr2 := bwlimit.Reader(f2)

    // Biz logic
    // ...

    // Wait for all Reader to be closed or reach EOF
    bwlimit.Wait()
}

1) Prepare bandwidth limiting object (bwlimit in above code) first. Second argument is the flag for block(true) or unblock(false) for Read or Write operation. 2) Then, create wrapper for each Reader or Writer.

Concept

The concept of bwlimit comes from Toyota’s Production System (TPS). In TPS, Takt time is the core concept for leveling production. Define time unit of ship defined quantity of products.

For example; 1) Takt time is 100ms, 2) bandwidth limit is 1,000 bytes per second. 100 bytes is maximum transferrable data size per takt time. If bwlimit object has two Readers, that mean 50 bytes per Reader per takt time.

Flow control

bwlimit does not carry bandwidth window to next takt time. If the Reader have 50 bytes window per takt time, and the Reader read nothing. Then, window size of next takt time will be 50 bytes. This is because if bwlimit carry unused window to next takt time. bwlimit may allow burst IO for certain takt time. That sometime cause buffer overflow of routers. To prevent incident caused by burst IO.

Github Pages + CloudFlareからAerobaticへ

つい先月、TumblrからJekyll + Github Pagesに移行したところですが、今度はJekyll + Aerobaticに移行しました。

これでこのブログのサーバお引っ越しは、次のように4回目になりました。

  1. WordPress + さくらインターネット (2005〜2013)
  2. Tumblr (2013〜2016)
  3. Jekyll + Github Pages + CloudFlare (2016〜2017)
  4. Jekyll + Aerobatic (2017〜)

WordPressからTumblrに移行したり、TumblrからJekyllに移行するのはコンテンツの調整などでさまざま面倒な点があります。たとえば、既存コンテンツに対するURLを引き継ぎたいといった要望に対して対応するのはコンテンツ個別に調整と動作確認が必要でした。

今回、コンテンツはそのままJekyllで運用するのでインフラ部分の移行のみです。

Github Pagesから移行した理由

基本的な使い方としてはGithub Pagesで充分満足が行くものでした。ただ、気になる点としては下記2点がありました。

  • HTTPSに対応できない
  • Minifierが使えない

Github Pagesでカスタムドメインとして登録した場合、現状ではHTTPSでコンテンツを提供することができません。ブログコンテンツの内容として、HTTPS通信の必要性はさほどありません。ただ、AppleによるApp Transport Securityなどでは2016年末までという期限こそ延長されたもののデフォルトでHTTPSを提供するようにとした方針に変更はありません。こういった状況を考えれば、HTTPSでのコンテンツ提供準備は早くできているに越したことはありません。

次にMinifierです。jekyll-minifierを使おうとしましたがGithub Pagesではうまく動作しませんでした。

Github Pages + CloudFlare

次にGithub Pagesだけではうまく2つの課題に対応できなかったので、CloudFlareを組み合わせることにしました。CloudFlareを経由して通信を行えば2つの課題は解消できます。もともと、watermint.orgドメインのDNS管理はCloudFlareで行っていたのでCloudFlareを経由するかどうかのオプションを変えるだけですみました。

[Client] ---<HTTP/HTTPS>--- [CloudFlare] ---<HTTP>--- [Github Pages]

CloudFlareを経由すると、HTTPSで接続要求があった場合CloudFlare側でHTTPSで処理してくれます。元のコンテンツであるGithub PagesとCloudFlare間は従来通りHTTPでの通信となります。CloudFlareから証明書も無償で発行してもらえます。

CloudFlareではCDNとしてコンテンツキャッシュをするだけでなく、Minifyのような処理も追加オプションとして用意されています。これも使い方は単純にオプションを有効化すればよいだけで、自動的にHTMLやCSSなどのコンテンツを最適化してくれます。

Aerobaticへの移行

上記のように課題は解決されたので移行の必要性は高くありませんでしたが、機能面でGithub Pagesよりも多彩であることと、Aerobaticサーバ側から直接HTTPS通信をサポートすることができるのでこちらに移行することにしました。AerobaticでもCloudFlareと同様に証明書は無償で発行してもらえます。

[Client] ---<HTTP/HTTPS>--- [Aerobatic]

AerobaticはGithubとGithub Pagesの関係と同様に、BitbucketとAerobaticというようにソースコードレポジトリと対にしてコンテンツを管理します。Aerobaticでは複数のブランチに対してサービスの設定が可能で、ステージング用のブランチとステージング用サブドメインを設定するといった運用ができます。

コンテンツは公開順ごとにバージョンが振られていきロールバックなどもgit操作なしで簡単に行えます。

他には、Githubでプライベートレポジトリは有償ですが、Bitbucketであればプライベートレポジトリも無償で利用できます。Jekyllのサイトデータであれば、公開してあっても特に困りませんが、あまりレポジトリ内のファイルが散らかっていると恥ずかしいので、、といった理由でプライベートにできたほうがうれしいですね。

Websiteバージョン

実はTumblrからJekyllに移行する際の本命がこちらのAerobaticだったのですが後述の問題があったので取り急ぎGithub Pagesに移行することにしていました。

Aerobaticでドメイン設定ができない

AerobaticではCDNにAWSのCloudFrontを使っています。証明書はCloudFront側で発行されるものを使うのですが、この処理でエラーとなってしまい、ホスティングの設定ができませんでした。

検索してもなかなか同様の報告が見当たらず解決の糸口がなかったのですが、今回とりあえずAerobaticのサポート宛てに問い合わせをしてみたところ、翌日には解決となりました。

Aerobaticでカスタムドメインの設定をする際、まずCloudFront側からドメイン所有者であることを確認するためのメールを受け取り、承認処理を行います。この操作のあと、Aerobatic上でverify処理を進めれば次はDNSの設定。となるはずなのですがAerobatic上のverify処理で「CNAME already registered with CloudFront」というエラーがでて一向に進みません。

このことをサポートに問い合わせたところ、すぐにエラーを解消してもらえたのですが、おそらく、ドメインwatermint.orgでCloudFrontを使ったことはなかったので承認処理手順の際に何かの拍子で2回処理が実行されてしまったのかもしれません。

jekyll-minifier + Aerobatic

jekyll-minifierとAerobaticの組み合わせは先月試したときにはうまく動作したのですが、移行したタイミングでは動作しなくなってしまっていました。

jekyll-minifierのリリース履歴を見ると2週間前に課題を解決するために、0.1.0がリリースされておりこの影響によるものでした。jekyll-minifierの参照については _config.yml に下記のように依存を書いておいただけだったのでバージョン指定はしていませんでした。

gems:
 - jekyll-paginate
 - jekyll-minifier

このように依存するライブラリのバージョンアップなどで急に動かなくなってしまっても不都合なので、GemfileGemfile.lockでバージョンを固定しておくことにしました。AerobaticのAutomated Buildsで説明がある通り、(1) _pluginsにある*.rbファイル、(2) GemfileあるいはGemfile.lockがあればbundlerによるインストール、(3) _config.ymlgems配列。といった順でライブラリが参照されます。

Aerobaticでの運用

Aerobaticでは無償プランにて2つのドメインに対してHTTPSを含むコンテンツをホスティングすることができます。一方で無償版の制約としては1日のデプロイ回数が5回までに制限されていることです。

一日に何度も記事を公開するようなブログであれば有償プランを選択する必要があります。また、上述のminifierのような処理を入れようとするタイミングではいろいろ試行錯誤するので5回という制約はかなり作業に影響します。Aerobaticは際立って高価なサービスではないので有償版にアップグレードしてもよいのですが、複数のドメインでサービスを提供したり、大量のコンテンツを持っているということもないので現状は見送って1日5回という制約の中で運用することにしています。

デジタルファイリング - データの整理整頓

気がつけばあっというまに2016年の大晦日。昨年の大晦日も大掃除をしたり、ファイルの整理をしたりと今年は整理整頓の一年でした。

断捨離をしつつ部屋の掃除も一段落しましたが、今年はデジタルデータの整理にも時間をかけています。デジタルデータで特に時間をかけたのが写真の整理です。昨年末から約1年かけて紙などアナログデータと、デジタルデータの整理を続けてきました。10万枚を超える写真データの整理とストレージ選びでもご紹介した通りNASを廃してクラウドストレージに移行しました。

この1年試行錯誤をして、整理整頓でこだわるべきだと感じたのは次の2点でした。

  • なるべく長期保存に耐えるフォーマットに統一する
  • なるべく階層を減らし、フラットな構造にする

それぞれどのようにこだわったのか紹介していきます。

長期保存に耐えるフォーマットへの統一

手元に残るうち、自分で作成した一番古いファイルのタイムスタンプを見ると1995年10月31日となっています。当時高校生でしたが、かれこれ20年以上経っています。ほとんどのファイルはまだ変換作業などすれば利用可能ですが、変換せずに閲覧できればファイルの利用価値はぐっと上がります。

あまり古いドキュメントをいまさら変換するのは面倒ですし特にモチベーションもないのですが、最近つくったドキュメント類はPDF/XまたはPDF/Aで保存し、フォントや埋め込み画像なども含めた格好で保存するようにしています。

ドキュメント類は上述のようにPDF/Aなどで保存していれば間違いないのですが、少し面倒なのが動画ファイルです。デジカメで撮った動画ファイルが手元にはたくさんありますが、デジカメの機種によってファイル形式はばらばらです。これらは一括で、H.264のようなに今どきの機器でプレビューしやすいフォーマットに変換しました。

紙からデジタルデータに

紙のデータは10年程度前から積極的にデジタルデータ化しています。クレジットカードの申込書や明細、商品を買ったときに同封されていた注意書きの紙、携帯電話やインターネットサービスの申込書や規約、購入した家電の説明書など。

同等のデジタルデータがダウンロードできる場合には、ダウンロードして保存しておき、そうでない場合にはスキャナーをつかってPDFデータにしておきます。

紙媒体ではサイズがまちまちだったり、大きさや重さの制約があり一ヶ所に保存するのは難しいですがデジタルデータにしてしまえばフォーマットを統一して参照することができます。

紙媒体では契約書など現物が必要なものもありますが、デジタルデータにしておくことで、契約年月日を知りたいとか、契約の種類が何だったかといった確認のためには重宝します。

なるべく階層を減らし、フラットな構造にする

本屋に行くと「整理整頓」「片づけ上手」といったキーワードの本がたくさん見つかります。また、最近では「ミニマル」とか「断捨離」といったキーワードのように捨てることについて注目して説明している記事も多く見られます。

デジタルファイリングもおそらくこの手法をそのまま採用できるだろうというのが最近の考えです。

片づけ指南本によると基本的な考えは「よく使うものは手前に置く」というシンプルなルールであると指南されています。デジタルデータでもこれは同様です。ただ単純にフォルダ構成を考えればいいというわけではなく、仕事の進め方も同時に変えていく必要があります。

最近のファイル

よく使うファイルというのはほとんどん場合最近つくったファイルです。

最近書いている記事のファイル、最近とった写真、進行中のプロジェクトといったファイルが最小の手数で開ける位置にあれば作業効率はバカにならないほど改善します。

ですから、Macならばファインダー、Windowsならばエクスプローラーを開いてすぐにこれらのファイルやフォルダにたどり着くことができるようにします。

こういったファイルやフォルダをデスクトップに置いている人も多いでしょう。デスクトップの使い方については少しこだわりがあるのでこちらも紹介させていただきます。

デスクトップの整理

個人的にはデスクトップは一時的な作業領域ととらえていて、一日の終わりに完全に空っぽの状態にしています。数年前受講したメール整理法の研修を受けました。この研修では「Inboxを毎日空っぽの状態にしましょう」という整理術を紹介していました。

このメール整理術ではフォルダに割り振るか、TODOリストに加えるか、スケジュール帳に書き込むか、削除/アーカイブするか行き先はこの4つだけ。この狙いは、まぜこぜになった情報一覧から必要な情報を探すのにいちいち時間がかかってしまう時間ロスを減らすことにあります。

デスクトップを空っぽにするのも同じ狙いです。たとえばプレゼンテーションスライドを作るときに大量の画像素材を準備するときは、デスクトップへ乱雑に置いていきます。ある程度情報を整理してプレゼンテーションスライドに配置が終わればスライドはプロジェクトのフォルダなどに整理しデスクトップは空っぽにします。

もし、画像を乱雑にデスクトップへ置いたまま、別のプロジェクトの作業をデスクトップ上で始めてしまうと、どのファイルがプレゼンテーション用のものだったか探すための時間ロスが生まれます。さらにデスクトップを空っぽにするにしても、どれが不要なファイルなのか簡単に区別がつかないのでまた時間ロスがうまれたり、「念のため」ということでそのまま放置してしまうことになりがちです。

最近つくったファイルの置き場所

比較的よく利用する最近つくったファイルはWebサイトデザインなどの際によくいわれる「3クリックルール」を想定して整頓するとよいでしょう。チーム作業などのために、共有フォルダの構成が決まっている場合はショートカットなどを活用します。

開くフォルダの設定

ファインダーであれば、ファインダーを開いた時に表示されるフォルダを設定できます。

すべてのファイルを一ヶ所に

ここまでは今までも似たような整理をしていたのですが、今年はNASや外付けハードディスクなどに保存していたすべてのファイルをDropboxに移行していったのでさらにファイリングを工夫しました。

これまでバックアップのために外付けハードディスクに保存したりNASに分散管理するなどしていましたが、Dropboxに移行したことで冗長性の確保ついてはすべてDropboxに依存することにしました。これによって、同じファイルを手元で複数管理する必要がなくなったので、バックアップ頻度などバックアップ漏れがないことを意識しなくてよくなりました。

整理を進めるに当たって、冗長管理をする必要がなくなったのは大きな進歩でした。すべてのファイルが一ヶ所にまとまっていますし、重複したファイルを持つ必要がなくなったので不要なファイルをどんどん削除することができました。ファイル数、フォルダ数が減ったことによってフォルダの見通しがよくなりました。検索も簡単です。

ファイル総量の制限

外付けハードディスクを利用していたときには要領あたりの単価が安いこともあって湯水のようにディスク領域を利用していました。いまはパソコンやDropboxの容量制限もありますが、主にファイル発見への時間ロスを考慮して総量制限をしています。

ファイルが多すぎると探すための時間を使ってしまいます。ご家庭でもDeep learningなどで自動的に分類されて必要なファイルが探せる時代になればいいのですが、まだそういった使い方は先になりそうですから、時間ロスを減らすにはある程度ファイル総量を制限していくほうが理にかなっていると考えています。

11月ごろにMacBook Proを買い替えて、内蔵SSDが1TBのモデルにしました。これはファイル総量はだいたい1TBぐらいだと時間ロスも防ぎつつ必要なファイルを保存していくためにちょうどいいのではないかなと考えたためです。

これまでにデジカメで撮った10万枚の写真総量ではおおよそ2TB強ありました。特にNikon D800で撮影したRAWファイルは1枚当たり50MB前後とかなりの容量でした。ただ、ほとんどの写真は連射写真で個別に再利用することがなかったので、思い切ってすべて削除し、2TB強のデータを170GB程度までに削減しました。

この制限によって、見返すことも簡単になりましたし、内蔵SSD上ですべて処理できるようになったので昔の写真を含めてすべてブラウズするといった操作もストレスなく快適になりました。

フラットに配置する

フォルダの見通しをさらによくするために、すべてのフォルダを3〜4クリック程度でたどり着けるように整理しました。フォルダ階層をなるべく減らしてソフトウエア等の制約で必要がない限り4階層までにおさえるようにしました。

iPhoneやiPadなどのスマートデバイスでもたくさんタップをするよりは縦スクロールをしていくほうが簡単です。近年のパソコンでもタッチパッドなどが使いやすくなりスクロールは非常に簡単です。ですから、ファイルがフラットに並んでいるほうが階層が深くなっているよりもより目的のファイルを簡単に探すことができます。

フォルダ階層

五十音順/アルファベット順/日付順などファイルの種類におうじて工夫します。3年前から購読している新聞はすべてPDFでダウンロードができますが、新聞であれば日付順に並んでいると一目瞭然です。あまりファイルが多すぎても探しづらいので月ぐらいでフォルダを作っています。

すべてのファイルをフラットに配置したことで、過去のファイルを参照することが日常的な作業になりました。NASや外付けハードディスクに保存していた時には、よほどの確信がなければできない作業だったのでこれは大きな変化です。

整理整頓とデジタルファイリング

デジタルファイリングを工夫したことで過去の情報を扱うことが簡単になりました。10年前の家計簿を見直したり、冷蔵庫の製氷機を掃除する方法を調べたり、飲み会の場で友人に3年前の旅行写真を見せたり、2年前の新聞を見直すなど一ヶ所にデジタルデータとして保存していなければなかなか面倒でした。

この十数年でインターネットで一般的な情報を探したり、現在進行中のニュースを知ることは安価でかんたんにできるようになりました。一方で、自分にかかわる過去の情報は自分自身で整理していないとインターネット以前と同様にかんたんには探すことができません。

今年はデジタルファイリングが一区切りついたので、来年はこれらの情報を活用する方法を研究していきます。

Go: 複数のReader/Writerに対する帯域幅制御

ネットワークやディスクへの読み書き処理の際、帯域幅制御をしたい場合があります。低優先度の処理などによって主となるビジネスロジックが阻害されないよう制御するといった目的や、一つのサーバ資源で複数のサービスを提供するときに一つのサービスが資源を消費しすぎないようにしたいといった目的があります。

Goでこのような処理を書きたいとき、ざっと調べてみたところgo-flowrateという実装が見つかりました。

func main() {
    f, _ := os.Open("data.dat")

    // 100バイト/秒の帯域幅制御付きのラッパーをつくる
    f2 := flowrate.NewReader(f, 100)

    // 実際の処理
    // ...
}

使い方もシンプルできっちり制御できるのでこれでいいのですが、制御できるのが一つのio.Readerまたはio.Writerのみに対してのみ可能で、複数のio.Readerio.Writerに対しては制御することができません。

並列処理が必要なプログラムを書いているとちょっとこれではそのまま使えそうにありません。もう少しほかの選択肢を探してもよいのですが、ちょうどいいGoのプログラミング課題ということで新しく実装してみることにしました。

出来上がり

まずはでき上がったライブラリを紹介します。bwlimitとしてGithub上に公開してあります。利用例は次のようなイメージです。

func main() {
    // 100バイト/秒に制限
    bwlimit := NewBwlimit(100, false)

    // 複数のReader
    f1, _ := os.Open("data1.dat")
    f2, _ := os.Open("data2.dat")

    // ラッパーを作成
    fr1 := bwlimit.Reader(f1)
    fr2 := bwlimit.Reader(f2)

    // 実際の処理
    // ...

    // すべてのReaderがClose()されるかEOFになるまで待機
    bwlimit.Wait()
}

まずは帯域幅制御を制御するオブジェクト(上図ではbwlimit)を作り、さらにそれぞれラッパーを作成します。NewBwlimitの第二引数は帯域幅制限しているときRead/Write処理を待たせるかどうかです。

あとはラッパーを普通のio.Readerとして扱うだけです。並列処理をしていて、すべてのReaderまたはWriterについて処理が完了したかどうか知りたくなったので待機するWait()もつくりました。

仕組み

キューを作る方法などいくつか実装方法を検討しましたが最終的にはかなりシンプルな構造になりました。トヨタ生産方式の本を思い出してヒントを得ました。

タクトタイムという一定時間のリズムを刻んで、各拍子のタイミングで利用可能な帯域を現在有効なReader/Writerで分割して利用するアイデアです。

たとえばタクトタイムを1秒間に10回に設定したとします。帯域幅設定を1000バイト/秒にした場合、タクトタイム1回あたりに利用可能な帯域幅は100バイトです。有効なReaderが2つであればこの100バイトをわけあって、50バイトずつの読み取りを許可する枠を設定します。実際に、Reader側が読み取るかどうかはわかりませんが、読み取り可能な上限を制御することでこのような流量制御を行っています。

流量制御と障害防止

あるタクトタイムのときに、読み取りが実際に発生せず50バイト分の枠がまるまるある状態で、次のタクトタイムになったとき読み取り可能枠を50 + 50バイト分にはしないようにしました。流量はこれによってすこし減ってしまいますが、意図しない障害を防ぐためです。

読み取り可能枠が溜まりにたまって一気に転送が行われてしまうと、他のサービスへ影響がでることはもちろん、場合によってはルーターなどのバッファサイズを超えるなどして障害が発生することも考えられます。

利用例

また別の記事として各予定ですが、Dropboxへのファイルアップローダーをいま作っています。このファイルアップローダーで並列してファイルのアップロードをする際に、帯域幅制御をつけたかったのがこのライブラリ作成のモチベーションです。

Disk image file (.dmg) from command line

I prefer .dmg instaed of zip for archiving project data, etc. .dmg is handy for refering files, modify contents without extract files to somewhere.

.dmg can usable as like USB drive. Disk Utility tool can create/update .dmg from folder with various options. Options are like encryption, readonly, compression, etc.

But if you have tens of folders to archive, it’s better to use command line tools.

Create encrypted .dmg file

hdiutil is command line version of Disk Utility app. This command can mount/unmount/create/update disk image files. Please see man hdiutil for more detail.

Below script is part of my workflow of archiving project files. I’m using encrypted .dmg for archive. The script require prepare password file under $HOME/.dmg-password. Please create and store password for .dmg without LF.

And update permission like chmod 600 $HOME/.dmg-password to prevent read from other users. This sequence using password and encryption. But it’s not strong enough, reason described below.

#!/bin/sh

if [ $# -lt 2 ]; then
  echo $0 SRC_DIR DEST_DIR
  exit 1
fi

SRC=$1
DST=$2
PWD="$HOME/.dmg-password"

if [ ! -e $PWD ]; then
  echo Disk Image password not found
  exit 2
fi

for t in "$SRC"/*; do
  if [ -d "$t" ]; then
    echo Creating: $t
    n=$(basename $t)
    cat $PWD |                \
      hdiutil create          \
        -srcfolder "$t"       \
        -fs HFS+              \
        -encryption AES-128   \
        -format UDBZ          \
        -stdinpass            \
        "$DST/$n.dmg"
  fi
done

Preset password for .dmg in Key Chain

It’s kind of pain in neck entering password for opening .dmg everytime. If you open .dmg from Finder.app, the password dialog refuse copy & paste operation.

Password dialog

There is option “remember password in my keychain”. Concept is similar to this.

The password for disk image is stored in keychain which identified by UUID of .dmg. The UUID is referable by command like below.

$ hdiutil isencrypted YOURDISKIMAGE.dmg

Now you can store password through security command.

$ security add-generic-password -a (UUID above) -D "disk image password" -s (YOUR DISK IMAGE).dmg -w (PASSWORD OF DISK IMAGE)

Unfortunatelly, there are no option like -stdinpass. So the password must be passed through command line argument. This mean optential leak through ps command or shell history.

By the way, I’m using below script for preset password to disk images.

#!/bin/sh

if [ $# -lt 1 ]; then
  echo $0 [dmg file]...
  exit 1
fi

PWD=$HOME/.dmg-password

for FILE in "$@"; do
  UUID=$(hdiutil isencrypted "$FILE" 2>&1 | grep uuid | awk '{print $2}')
  BASE=$(basename $FILE)

  echo File: $BASE
  echo UUID: $UUID

  security add-generic-password -a $UUID -D "disk image password" -s $BASE -w $(cat $PWD)
done

When opening .dmg from Finder, operating system ask authorisation of using password by diskimages-helper.

Confirmation dialog

You can skip this dialog by -A option of security command, but this option authorise for all applications. It’s better not use this -A option for better security.