Daydreaming in Brookline, MA

S3バックアップツールbus3.pyを作りました

1 はじめに

Amazon S3とデータベースの勉強を兼ねて、Linux向けのS3ストレージへのバックアップツールbus3.pyを作りました。https://github.com/achiwa912/bus3

2 bus3概要

bus3は指定ディレクトリ以下のファイルやディレクトリをバックアップします。ファイルのコンテンツはS3ストレージに、メタデータはデータベースに保管します。

S3ストレージはAmazonのAWSでなく、ローカルネットワークに接続されたS3インタフェースを持つストレージを使って開発、テストしています。このため、AWSを使うためには多少のtweakが必要になるかもしれません。

データベース(DB)は、当初sqlite3を使っていたのですが、DBのライトがボトルネックとなって多重度が上がらず、全く性能が出なかったので、Postgresに移行しました。Postgresはbus3の外側にあって、別途導入やバックアップを行う必要があります。

DBのバックアップを別途行う必要があるため、bus3はあまり実用的では無いかもしれません。

3 bus3特徴

3.1 機能

  • 指定ファイルやディレクトリのバックアップ、リストアを行います
  • シンボリックリンクやハードリンクをサポートしています
  • extended attributesもバックアップ、リストアします
  • バックアップヒストリーとファイルバージョンを管理していて、指定バージョンへのリストアが可能です
  • ファイルコンテンツに対して、デフォルトで64MBのチャンク単位でのdedupe(シングルインスタンス化)を行います。同じ中身のチャンク(やそれ以下のサイズのファイル)があったら、そのチャンクはS3ストレージ上に一つだけ実体を持ちます。

3.2 技術面で

  • asyncio をフルに活用してバックアップ・リストアの多重度を上げています
    • バックアップするファイルやディレクトリ毎にasyncタスクを生成しています
    • 更に、S3へのオブジェクトの書き込み毎にasyncタスクを生成しています
  • aiofiles, asyncpg, aioboto3 ライブラリを利用しています
  • メモリバッファの使用量上限を制限しており、1GB以上の巨大ファイルを複数バックアップしてもメモリ使用量は2〜3GB程度です(postgres除く)
  • asyncio は基本的に単一コアで動作するため、CPU利用率は120%程度(postgres除く)です。100%を超えるのは、 aiofiles がワーカースレッドを使ったり、OSのシステムコールを呼んだりするためと思われます
  • sqlite3のグローバルライトロックを避けるためにpostgresを使っています

4 使用法

githubのREADMEを参照ください。

5 今回学んだこと

5.1 asyncio

  • 単純に await を使っても多重度が上がる訳ではないことがわかりました。 多重度を上げるには、 asyncio.create_task() を使ってasyncタスク化しなくてはいけません
  • asyncio はタスクスイッチを意識して行わなければいけませんが、その分、そこまで資源排他を意識せずに済むため、スレッドよりも使いやすいかもしれません
  • 同様に、スレッドよりもデバッグしやすいと思います。タスクスイッチのタイミングを意識できるのは、思いの外有益です
  • asyncio はまだ新しい技術なため、足回りが揃っていないと感じました
  • asyncタスクで発生した例外の扱いが難しいです。

5.2 データベース

  • sqlite3はとても気軽に使えますが、グローバルライトロックがあるため、多重度を上げる技術である asyncio とは相性が悪いと感じました
  • これまではSQLAlchemyを使っていましたが、今回直接SQLを書いてみて、SQLAlchemyを使わないほうが手間がかからないと思いました。ORMがDBのデータ構造を隠蔽してくれるのは楽ですが、そのぶん、デバッグで苦労します
  • sqlite3からpostgresに移行してみて、DB間の挙動や仕様の差に苦労しました。

5.3 性能ボトルネック解析

  • CPUやメモリの利用率は top コマンドを使って見ました
  • 標準のcProfileと、可視化ツールであるsnakevizの組み合わせは素晴らしいと思いました。使い方:
pip install snakeviz
python -m cProfile -o prof.bin <yourscript.py> <args>
snakeviz prof.bin
  • これらを使っても、性能ボトルネックの特定は難しいです。

5.4 python

  • argparse が思ったように動いてくれず、とても使いづらかったです。

5.5 開発環境

  • 私はemacs + elpyで開発していますが、どうもセーブ時の自動整形が変だと思ったら、必要なバックエンド機能が動いていなかった、ということがありました。 M-x elpy-config で確認するまでわかりませんでした
  • せっかくelpyを使っているのに、定義箇所や使用箇所へのジャンプ機能を使っていませんでした。 M-. で定義元へジャンプ、 M-, で元の場所にジャンプなのですが、普通の検索で乗り切っていました
  • 同様に、 M-<矢印キー> でリージョンのインデントを一括で変えられることを最近になって知りました。手動でタブキーを連打して整形していました。これで何度バグを作り込んだか。。
  • emacs lispの勉強の必要性を感じました。導入したemacsパッケージで問題が起きた場合、現状では、google検索で解決しないとあきらめるしかありません。