スクリプト類とメニューボタン

トップ ソフト 雑記 日記 リンク

12月 12, 2014

libpqxxを使ってみた。

PostgreSQL Advent Calendar 2014 の 12日目です。枠が空いていたので、2回目の飛び込みをしてみました。
昨日はnuko_yokohamaさんのjsqueryを使ってみたでした。

 普段わたしはウェブアプリをPHPで作っていて、そこからPostgreSQLへアクセスするのですが、バッチ処理やループ処理などはCやC++で書くことがあります。
 少し前までは、C言語でバッチ処理を書いていて、ecpgを使った埋め込みSQLを使っていました。
 AIXでDB2を使っていた経験があり、その延長で埋め込みSQLは便利だなと思っていたのです。

 しかし、複雑なことをするに従って、また他のライブラリの都合などもあり、徐々にC++に移行してきました。

 7日目の nuko_yokohama さんのスライドにもあったように、C++でecpgを使うのは、ちょっと微妙です。完全対応では無いので、何か問題が発生したときに切り分けで嵌まりそうです。
 そこで、libpqxxという、C++ライブラリを使ってみました。

http://pqxx.org/development/libpqxx/
 現在、最新バージョンとしては4.0.1が公開されています。
 が、開発版のスナップショットも公開されています。
 4.0.1はリリースが古く、それからちょこっと修正も入っているので、こちらの方がいいかなと思います。
http://pqxx.org/~jtv/tmp/pqxx/snapshot/

 configure して make して make install 簡単ですね。

 コードは、とてもシンプルです。
 コネクションやトランザクション、結果などそれぞれがオブジェクトになるので、簡単にアクセスできます。
// パスワードは .pgpass にでも書いておきましょう
unique_ptr<pqxx::connection> db_conn(new pqxx::connection("host=pgsql.example.jp port=5432 dbname=testdb user=testuser application_name=testprog"));
try {
     pqxx::read_transaction tran(*db_conn);
     ostringstream query;
     query << "SELECT val" << endl;
     query << "FROM tbl" << endl;
     query << "WHERE cond='cond'" << endl;
     pqxx::result res(tran.exec( query.str() ));
     if (res.begin() == res.end()) cerr << "nodata" << endl;
     for (pqxx::result::const_iterator row = res.begin(); row != res.end(); row++) {
          cout << row["val"] << endl;
     }
     tran.commit();
}
catch (const pqxx::sql_error& e) {
     cerr << e.what() << " : SQL->" << e.query() << endl;
}
catch (const pqxx::usage_error& e) {
     cerr << e.what() << endl;
}

 更新でも同じ流れです。
pqxx::work tran(*db_conn);
std::ostringstream query;
query << "INSERT INTO tbl (" << endl;
query << "val, cond" << endl;
query << ") VALUES (" << endl;
query << "  " << std::dec << val << endl;
query << ", " << tran.quote( cond ) << endl;
query << ")" << endl;
pqxx::result res(tran.exec( query.str() ));
tran.commit();

 quoteだけではなく、ちゃんとプリペアドステートメントも使えます。
pqxx::connection_base &con_base(*db_conn);
con_base.prepare("sel", "SELECT name FROM tbl WHERE code=$1");
pqxx::read_transaction tran(*db_conn);
pqxx::result res( tran.prepared("sel")("hoge").exec() );
for (pqxx::result::const_iterator row = res.begin(); row != res.end(); row++) {
    cout << row["name"] << endl;
}
tran.commit();

 プリペアドステートメントはデータベースコネクションへ登録、実際のステートメントはトランザクションから
tran.prepared(ステートメント名)($1)($2)・・・.exec()
という形式で呼び出します。

 嵌まりどころがなくて、拍子抜けするぐらいです。
 以前のバージョンでは、PostgreSQLのbigint型を扱うときにCのlong long int型を使おうとするには、includeファイルの定義を変更したりする必要があったのですが、今のバージョンでは不要です。

これでC++からも簡単PostgreSQL!!

12月 04, 2014

RDS for PostgreSQLを使い始めてみたレポート

PostgreSQL Advent Calendar 2014 の 4日目です。
昨日はNagayasuさんのPostgreSQLカンファレンス2014開催のお知らせと見どころのご紹介 #pgcon14jでした。


 拙作「ふぁぼるっく」 のデータベースをRDS for PostgreSQLへ移行しました。
 一昨日ぐらいに全データを移し終わったところです。
 RDS内部のソフトウェア的なところは、これまでも他の方の記事にあったので、スペック部分に的を当てて書いてみたいと思います。

 それまでは、自宅サーバでよく使うデータやインデックスだけをSSD上のテーブルスペースに置き、あまり参照されないデータなどをHDD上のテーブルスペースで運用していました。今年の5月からVPSのSSDプランを借り、よく使うデータやインデックスだけをVPSのSSDに置いて、残りのデータを自宅サーバに置き、postgresql_fdwで自宅サーバに繋いで利用していましたが、いろいろ問題点が溜まってきました。
  • 自宅サーバのSSDがすぐ劣化する(一年ちょっと)
  • SSDを買い続けるのと、交換作業の度にバッチを止めたり、回復作業が面倒
    (当時はSSDでRAID 1を組むほどの思い切りがなかった)
  • ときどきレプリケーションが失敗してスレーブが止まる
  • VPSに移行したが、負荷が高すぎて処理制限を掛けられ、PostgreSQLがOSごと転けた
  • 自宅サーバに戻って2TB HDD*5(RAID5)マスターサーバと4TB HDD*2(RAID0)スレーブサーバで組んでみたが、やっぱり速度が出ない

自宅サーバの惨状 ふぁぼ収集サービスのつらみより
気がついたら、コードを書くよりインフラのメンテナンス作業の方に時間を多く取られていることに気がつきました。障害を克服する楽しさが無いとは言いませんが、ソフトウェアでやりたいことが溜まっていくのに、なかなか手がつけられません。ストレスが溜まります。
 ここは、インフラ担当を一人雇うつもりで、全データをRDS for PostgreSQLに移行しました。

db.r3.large インスタンス
General Purpose (SSD) 2000GB

 最終的に、上記の構成になりました。
 当初は、r3.xlargeインスタンスでプロビジョンドIOPSの1000 IOPSにして利用しようと考えていました。100GBの構成でテストしてみたり、料金計算ツールでなんとか払える範囲であることを確認したり・・・。

 最小構成で動くことを確認し、あとは本番用サーバを建てようとしたところ、なにやらエラーが。
2000GB で作るなら、6000 IOPS からだよ。
慌てて料金計算ツールで試算してみたところ、月額10万円以上もの金額! とても趣味に使える金額ではありません。100GBの時は1000 IOPSからだったのに・・・。

 ふと考えてみると、EC2のEBSはサイズが1TBまででしたので(2014年11月に16TBまでの拡張が発表されました)、3TBまで使えるRDSは内部でEBSを束ねているのだろうと考えられます。
 そうすると、通常のディスクやGeneral Purpose (SSD)でも、ボリュームサイズが大きければ、単体ディスクの時よりスピードが出るのではないか?
動かしてみた。

 移行中に溜まっていた処理を一気に流しているので、かなりギリギリの処理になっていますが、性能が落ちているなー、というときでも400 IOPS程度出ています。
調子の良いときで2000 IOPS、とても良いときで3000 IOPSでました。

db.r3.large インスタンスは、
 vCPU 2
 メモリ 15GB
 ネットワークパフォーマンス 中
 プロビジョンド IOPS用の最適化なし
という条件で、コア数的には自宅サーバやVPS時代と比べて劣っているのですが、ディスクが高速なおかげで、全く遜色を感じません。
こんなことなら、もっと早く移行しておくべきでした。

ということで、速度も巨大ストレージも欲しているデータベースは、RDSでも幸せに運用できるのではないかと思います。
2014年12月現在、3TBまでですが、これもEBSの拡大に合わせて大きくできるのではないかと期待しています。



 ここまで書いておいて、これ単純にAWSの話で、PostgreSQL関係なくね?と気がついたので、やっぱりソフトウェア的な話も。



 RDSへの移行は、しばらく前から考えてはいたのですが、日常的にテーブル間でデータコピーが発生する構成なため、どのように RDS for PostgreSQL を絡めるか、また自宅サーバで1.7TBほどディスクを使っているデータベースをどうやって移行するかが悩みどころでした。

 AWSから出ていくデータには大きなデータ転送量が掛かってしまうため、AWSのサービスを使い始めたら最後、日常的に参照以外でデータをまとめて取り出すことは考えない方がいいでしょう。こうして、それまで組んでいた「よく使うデータはインターネット側、あまり使わない古いデータは自宅サーバ」という構成は諦めることにしました。
 全てのデータを1台のサーバで運用するのは、それまで自宅サーバだけの時代に経験しているので、あとは移行方法です。なるべく動かしたまま移行したいと考えていたところ、タイミング良く11月の更新で移行ツールのサポートが発表されました。

Amazon RDS for PostgreSQLのアップデート- リードレプリカ、9.3.5のサポート、データ移行、3つの新しい拡張モジュール

 Londisteというツールでレプリケーションを組むことで、マスターサーバからデータを送りつける感じ?でコピーを作成するようです。
 そして検証環境を組んでLondisteを使ってみようとしたのですが、上手く行かない。最新バージョンは3.xなのですが、PostgreSQL wikiには2.xの説明しかありません。3.xではコマンド体系が変わっているようで、なんとか付属のドキュメントを読みつつ試してみたのですが、全く動きそうなところにも持って行けませんでした。
 この時点でVPSでの運用ができなくなり、自宅サーバに戻って速度の出ないディスクを前に未処理のデータが溜まり続けるという前にも後ろにも進めないような状況に陥ってしまいました。
 「早く標準のストリーミングで移行できるようにしてくれ」と願っている時間もなく、RDSの仕組みを見る限り、ターミナルにログインしてpg_basebackupコマンドを叩けるような構成では無さそうです。
 仕方なく、オーソドックスなpg_dump、psqlの組み合わせで移行しました。


 データベース作成には、LC_COLLATE、LC_CTYPEの設定が必要だったため、既存のデータベースの削除と、テーブルスペース作成・データベースの作成までは先に済ませます。

CREATE DATABASE favlook
  WITH OWNER = favlook
       ENCODING = 'UTF8'
       TABLESPACE = hoge
       LC_COLLATE = 'C'
       LC_CTYPE = 'C'
       TEMPLATE = template0
       CONNECTION LIMIT = -1;
続いて元のサーバからデータのエクスポート
 RDSでは、rdsadminユーザーがSUPERUSER権限を持ち、サーバを建てた時点で作られているユーザーにはrds_superuserという権限が与えられています。
 こんな感じ
-- Role: rdsadmin
CREATE ROLE rdsadmin LOGIN
  SUPERUSER INHERIT CREATEDB CREATEROLE REPLICATION;

ALTER ROLE rdsadmin
  SET log_statement = 'none';
ALTER ROLE rdsadmin
  SET log_min_error_statement = 'panic';
ALTER ROLE rdsadmin
  SET log_min_messages = 'panic';
ALTER ROLE rdsadmin
  SET exit_on_error = '0';
ALTER ROLE rdsadmin
  SET TimeZone = 'utc';

-- Role: postgres
CREATE ROLE postgres LOGIN
  NOSUPERUSER INHERIT CREATEDB CREATEROLE NOREPLICATION;
GRANT rds_superuser TO postgres;
単純にpg_dumpでデータを出力すると、テーブルの権限を設定する部分で警告が出まくります。
 ですので、--no-ownerオプションをつけて、ログインユーザーで作成します。
pg_dump -U [user] -f table.sql --no-owner --no-tablespaces -s [database]
これでテーブルを作成し、あとは必要なデータからコピーしていくという流れで移行しました。

 本格的な運用はまだこれからですが、とりあえずtextsearch_jaを使っていた処理をどうしようかなぁ、というところです。サーバ側でカスタマイズは難しそうなので、postgresql_fdwみたいな感じで、postgresql_RPCとかできると良さそうですね。


明日はsoneさんの今、データベースは中国地方が一番アツい!! です。

1月 23, 2014

東京で3日間Wi2を使ってみた。

前回の続き。
SIM無しNexus5で東京に行かねばならない。
手元には、So-net 3GモバイルからOCNモバイルONEに切り替えた都合で余った標準SIM。
標準SIMをMicroSIMに切って使うか?と思ったのですが、So-netのSIMは、破損してたり返さないと3000円払ってもらうよと書かれています。
プロバイダによっては請求されないらしいのですが、値段まで書いてある以上、数日東京に行くために3000円払うのは、b-mobile契約するのと変わらないし、勿体ないなという気持ちが働きました。

そこで目を付けたのがWi2
わたしは大阪に住んでいますが、ちょくちょく無線LANアクセスポイントに表れるWi2。ときどきパスワードの掛かっていないアクセスポイントがあるのですが、繋いでみてもWi2の契約をしないとネットには繋げないようで、基本的には邪魔な存在でした。

しかしモバイル接続が無い状態とあれば話は別。
アクセスポイントの位置を確認すると、結構広範囲で、駅だとまず使えそうです。今回は新宿あたりへ行くので、その付近も確認。
早速ウェブから契約。月額300円。初月無料と書かれているのですが、わたしみたいに3日間だけ必要で解約したりすると、無料で使えたりするのでしょうか?その辺も、後日確認してみようと思います。

新幹線は別契約のようですが、確かに違う設備投資が必要ですし、使うのは一部の人なので妥当なプランだと思います。
新幹線の契約は3時間で150円。子供の頃なら車窓だけで3時間過ごせたのでしょうが、さすがに何回か乗っているので時間が持ちそうにありません。なのでこちらも契約しようかと思ったのですが、注意事項に「接続してから3時間有効」と書かれています。
これはWi2に繋いだときから3時間なのか?新幹線は別のアクセスポイントなのか?駅に着いただけで新幹線の電波を拾って繋いだことになっちゃうのか?
謎がいくつかあって、とりあえず乗ってから契約すればいいや、と家を出ました。

Wi2の使い心地ですが、自宅最寄り駅でも接続できたのにまず驚き。そして速度も申し分ない感じです。
ネットを使うと言っても、Twitterをしているか、4sqでチェックインするか、メールチェックと簡単なウェブでの調べ物ができれば良いレベルですので、実用には十分な回線品質でした。

参考用に、機内モードで再現
新幹線の話は後に回すとして、東京に着いてからですが、東京の駅へ行くと話は別。
特に大きな駅になるほど、駅のどこでも電波が入るというわけではないようでした。
改札付近で入るのかと思えば、ホーム上しか入らない駅、コンコースの人が多いところだけ入る駅など、条件が様々でした。
Wi2には、スマホ用アプリがあって、通信できなくてもどの辺りにアンテナがあるのかが、現在地を中心にピンが立って分かります。
ただし回線に繋がっていないと、その下のレイヤーに地図が表示されません。
真っ白な画面に周囲のアンテナの方角とだいたいの距離が分かるという状態になってしまいます。はっきり言って宝探し。地図の縮尺も分からないので、どのくらい歩いたら良いのか分かりません。

またアンテナピクトが2本立っている状態でも繋がらないところが多く、アクセスポイントは認識しているのに繋ぎに行かない。手動で接続を選択した瞬間に一覧から消えるなど、どうやら品質が良くない感じです。
とにかく、アンテナピクトが3本以上立つ場所を探して、スマホを見ながらうろうろ・・・。
これに結構時間を費やされました。柱の上とかに「Wi2アクセスポイント」みたいな看板が欲しいなと感じました。
駅のコンコースで何とか電波を掴んだので、目の前にあるガラス張りの喫茶店に入ったら電波が届いていなかったという悲しい事態もありました。
とりあえず、隣に座っていた二人組女性がシステムの設計をしている話を聞きながら過ごしたり。あまりああいう所で人に聞かれると困る話をするのは良くないよ。

そして、あらかじめ使える喫茶店を調べて入ってみたこともありますが、こちらは快適でした。
Wi2を使うには、先にアクセスポイントを調べてから行くのがベストのようです。

話を戻して新幹線。
新幹線でWiFiに接続すると、アクセスポイントによってUQ-WiMaxのログイン画面と、ソフトバンクのログイン画面が出てきます。
Wi2の契約時に発行されたユーザーIDが、UQのログイン画面に例示されている形式と同じでした。パソコンから見ていたWi2の説明でも、UQのサービスを使うと書かれていたので、UQの方からWi2のアカウントでログインしたら、新幹線プランの契約画面が出てきて契約かなーと思ったのですが、ログインは拒否されてしまいます。
auガラケーからWi2新幹線プラン画面が文字化け
どうやら乗車前に契約をしておかないと行けなかった模様。これは辛い。
しかし手元には、auのガラケーがあります。パケット代が掛かってしまいますが、契約する程度なら仕方ない。Wi2を検索してマイページから新幹線プランを契約しようとしたら、パソコンでは表示されていたメニューが出てきません。
「メニューが出てこない場合はこちら」というリンクがあったので、開いてみたのですが、文字化け。
画面を下にスクロールされていくと、「同意」「同意しない」だと思われるラジオボタンと、画像で描かれた次へ進むボタン。ラジオボタンを選択して次へ進んでみたのですが、画面が変わりません。
どちらのラジオボタンを選んでも、次へ進むボタンのリンク先は「戻る」になっていました。
これは本格的に車窓を見て過ごすしかないのか。

ダメ元でUQのアクセスポイントに繋いだ状態で、Google等に繋いでみようとしたのですが、ログイン画面が表示されます。
やはり駄目かと諦めかけたとき、手が滑って、ブラウザの検索語句にWi2を入れたつもりが、アドレスとして呼び出されました。
すると、どうでしょう。Wi2の画面が表示されるではありませんか。

UQのアクセスポイントでは、契約者以外がアクセスすると、DNSからログイン画面のアドレスを返している様子?
もしかして、自前でDNSキャッシュを持ち込んだら、契約しなくても使える?なんて思ってしまったり。
何はともあれ、Wi2の画面が出てくるならマイページが開けるか試してみたところ、無事ログイン完了。
新幹線プランのメニューも出てきて、無事新幹線でのWiFi接続が完了しました。
このとき既に40分が経過。長かった・・・。

Wi2で新幹線プランを契約すると、UQ用のログインIDとパスワードが発行されました。
あれ、Wi2のアカウントに出てきた「@Wi2」っていつ使うの?

とりあえず、新幹線の無機質なUQ-WiMaxログイン画面は、契約もできるように誘導するべきではないかと思いました。
復路は、あらかじめ東京駅で契約を済ませてから乗車。
快適な旅となりました。

帰宅後はOCNモバイルONEのSIMが届いたので、Wi2を解約。
月額料金が請求されるのかどうかは、また追記したいと思います。

不満点3つはWi2のサポートに投げておきました。
外で通信するタイミングを考えると、wifiサービスがもっと使いやすくなると、選択肢として有りではないかと思いました。

1月 22, 2014

Nexus5を買って、OCNモバイルONEで嵌まった

LG Nexus5を買いました!やったー。
16GB版が売り切れだったのと、SDカードスロットがないという心配から32GBホワイトにしました。
さくさく動くのがいいですね。
メモリが2GBあるので、Firefoxも動くようになって、デスクトップPCと同期取れるのが良いです。

以前使っていたHuawei IDEOS X6(U9000)はAndroid 2.2だったのですが、出荷時設定に戻したら起動しなくなったので、Cyanogenmod9(Android 4.0)を焼き込みました。
買って最初の頃に、ちょっとROM焼き失敗してびびってしまい、工場出荷時のROMに戻して、この二年ほど使っていたのですが、Android 4.0のU9000も結構さくさく動いていてもっと早く試せば良かったと思っている次第です。

さて、二年前に契約したSo-net 3Gモバイルも、あまり価格に対する利点が感じられなくなり、スマホも二台になったということで、契約をまとめられるサービスを探してみました。
するとOCNモバイルONEが、一日の制限がありつつも、かなりの低価格で利用できます。
我が家は以前OCNと契約していたことがあったのですが、現在は他のプロバイダに乗り換えているものの、メールアドレスを残しておきたくてバリュープランという契約にしてありました。
今回はこれをOCNモバイルONEの契約に切り替えてしまおうと思いました。

サイトを見ると、パッケージを購入して(パッケージ代が事務手数料となる)申し込みをすればいいようです。
NTT-Xストアで安売りされていたので、早速注文。とりあえず、U9000で試してみるために標準SIMを一枚だけ買ってみました。
届いたパッケージを開けて、書いてあるサイトの契約ページを開き、必要事項を記入し・・・。
はて、OCNの会員と紐付ける入力欄がない。
このまま契約しては危険だ、と察知して一度ブラウザを閉じます。
OCNのマイページから契約情報が変更できるか見てみると、変更できるようです。
しかしこちらもフォームを入力していくと、今手元にあるSIM情報を入力する欄がありません。
こちらも危険だと思い、問い合わせフォームから送信。
直後に、小さい文字でパッケージ購入したSIMは紐付けできないと書いてあることに気付く。完全に自分のミスです。

サポートからの返答も同じく、OCNモバイルONEに契約変更した後、パッケージで購入したSIMを紐付けることは不可能とのこと。
パッケージで買うのはOCNと縁もゆかりもない人向けだったのですね。
OCNの契約者は、素直にマイページからSIMを請求するのが正攻法だったようです。
ただサポートセンターの方が対応していただき、現在手元にあるSIMを紐付けた状態で契約変更してくれるとのこと。やったー!ありがとうございます。

こうして、無事U9000は新しいプロバイダに切り替わりました。
そしてNexus5の方はマイクロSIM。2枚目のSIMとして注文するのですが、サポートとのやりとりの中で2枚使いたいと申し出ていたので、サポートの方がこちらも発送してくれるとのこと。
しかし、翌週には東京に行く用事があったので、なんとかそれまでに受け取りたかったのですが、やりとりをした分、発送に時間が掛かってしまったようです。
結局SIM無しNexus5と東京に行くことになりました。

東京での回線をどうしよう・・・ということで、次回に続く。

12月 07, 2013

findAndModifyでお手軽キュー

MongoDB Advent Calendar 2013の7日目です。

MongoDBをメッセージキューとしての利用方法を紹介したいと思います。

拙作「ふぁぼるっく」では、TwitterのREST API呼び出しによるクロールのほか、UserStreamに接続し大量(現在約7000アカウント)の利用者からのふぁぼやRT情報を収集しているため、人気のツイートが現れると突発的に処理量が増えます。
メインのデータベースとしてPostgreSQLを採用していますが、処理量増大時にデータベースでは接続本数が足りなくなり、また接続~レコードの存在確認~レコード挿入までに時間が掛かるため、他の挿入処理まで待っているとUserStreamは情報がどんどん流れてくるために処理しきれないという事態が発生してしまいます。

そこで、とりあえず情報を貯めておくキューを用意することになり、ふぁぼるっくでは、ここでMongoDBを採用しています。
MongoDBに至った理由としては、以下の点があります。

・大量接続が可能
→PostgreSQLだとサーバーリソース的に1000接続ぐらいが限界なので、同時に20000接続まで可能なMongoDBは魅力的。実際にメモリ2GBのVPS上で運用していますが、20000接続までできました。

・接続から挿入までが速い
→書き込み完了前に処理が返ってきます。失敗する可能性もありますが、正常に動いている限りは失敗する可能性が少ないです。要件的には、失敗しても再取得可能なため無視できます。

・重複していても上書きできる
→MySQLで言うREPLACE INTOができます。正確に言うと、重複したキーを持つデータを保存したら常に上書きです。これは複数のUserStreamから同じデータが投入される場合があるのですが、一般的なメッセージキューとは少し違った要件があり、プライマリキーとなる部分が同じものの場合は1件として扱いたいためです。

・複数のワーカーから取得しても重複して取得しない
→キューとして扱うので、ワーカーで取得した情報は、他のワーカーから見えて欲しくない。そこでfindAndModifyコマンド。

 _人人人人人人人_
> findAndModify <
 ̄Y^Y^Y^Y^Y^Y^Y ̄

通常、MongoDBには、insert、update、find、removeなどのコマンドでコレクションにアクセスします。
findAndModifyコマンドは、
{
  query: ドキュメント,
  sort: ドキュメント,
  remove: 真偽値,
  update: ドキュメント,
  new: 真偽値,
  fields: ドキュメント,
  upsert: 真偽値
}
というコマンド書式をしており、findコマンドと同じようにqueryパラメータで指定したオブジェクトが取得できる一方、updateパラメータで指定した値で更新したり、removeパラメータの指定でオブジェクトの削除が行えます。
これら一連の処理はアトミックな処理となっており、オブジェクトの取得と更新がまとめて行われます。
例えば、testコレクションにこのようなデータが入っていたとします。
> db.test.find();
{ "_id" : 1, "name" : "takashi" }
{ "_id" : 2, "name" : "hanako" }
ここで
> db.test.findAndModify({query:{"_id":1},update:{"name":"ten_p"}});
{ "_id" : 1, "name" : "takashi" }
を実行すると、_id:1のデータが取得できました。
内容を見てみると
> db.test.find();
{ "_id" : 1, "name" : "ten_p" }
{ "_id" : 2, "name" : "hanako" }
updateで指定した内容に置き換わっています。
> db.test.findAndModify({query:{"_id":1},remove:true});
{ "_id" : 1, "name" : "ten_p" }
removeを指定すると、データを取得しつつ
> db.test.find();
{ "_id" : 2, "name" : "hanako" }
そのデータが削除されます。

ふぁぼるっくでは、この仕組みを利用して、複数のワーカーが並列してMongoDB上のキューからデータベースへの更新処理を行っています。

以上、MongoDBを使うとこんなこともできるよ!という紹介でした。

ふぁぼるっくではTwitterをお使いの方のふぁぼ情報やふぁぼられ情報を集計しています。
ぜひ登録してみてください!

11月 02, 2013

Treasure Dataを使ってみました。(Upload編)

拙作ふぁぼるっくはPostgreSQLを使用しているのですが、データの蓄積量が増えてきたことから集計処理に時間が掛かるようになってきました。
しかし持っているサーバーはどれも高負荷状態。
なんとか手持ちのCPUリソースを割かずに集計する方法はないものかと考えていたところ、Treasure Dataを思い出したので使ってみました。

アカウントを作って、コマンドラインのツールをインストールします。
gem install td
で完了。
tdコマンドでアカウントを設定します。ちなみに、tdコマンドのバージョンは0.10.91です。

Treasure Dataのバルクインポート説明 http://docs.treasure-data.com/articles/bulk-import を見ると、データはまず加工(Prepare)し、行単位のデータベースへアップロード、そこから列単位のデータベースに変換(Perform)して、PlazmaというTreasure Dataのカラム型データベースへ投入(Commit)するという流れになります。

16個に分割された合計82GBのcsvファイルをアップロードします。
td import:upload --auto-create favlook.fav_old \
--auto-perform --auto-commit \
--parallel 8 \
--columns status_id,id,tp,user_id \
--time-value 0 \
-o prepared_parts /mnt/sdd/tmp/fav_*.csv

「--time-value 0」というオプションについてですが、Treasure Dataでは時間の追加カラムが必要なようで、これがないと時間書式のカラムがないというエラーになってしまいました。
もしデータに時間のカラムが存在するなら「--time-column ~」でカラムを指定すると良いようです。
今回使うデータには、時間カラムがなかったため、--time-value 0で全てゼロとして指定しました。

アップロードは回線速度次第です。気長に待ちましょう。
--auto-perform --auto-commitを付けているとアップロード完了時にそのままperformに進むと思うのですが、今回はいくつかのファイルが失敗してしまいました。
Next Steps:
  => check td-bulk-import.log and re-upload prepared_parts/fav_2012c_csv_66.msgpack.gz: Retry failed.
  => check td-bulk-import.log and re-upload prepared_parts/fav_2013d_csv_17.msgpack.gz: Retry failed.

端末に出てくるリトライ回数は0となっています。tdコマンドのオプション指定を見てみたのですが、リトライの指定方法が分かりませんでした。
td-bulk-import.logというログファイルも出力されるのですが、ローテーションするみたいで、直近のものしか残っておらず、該当ファイルの失敗理由などは分かりませんでした。

とりあえず失敗したファイルを再度アップロードします。
しかし1つ15GBも有るファイルをもう一度上げ直すのは大変。でも、失敗したファイルだけ再送信できます。
td import:upload favlook_fav_old_2013_10_25_1382661066 prepared_parts/fav_2012c_csv_66.msgpack.gz
td import:upload favlook_fav_old_2013_10_25_1382661066 prepared_parts/fav_2013d_csv_17.msgpack.gz

と、アップロードセッション名、対象ファイルを指定します。
prepared_partsというディレクトリは、最初のimport:upload時に指定した「-o prepared_parts」が効いています。
このディレクトリにPrepareしたデータが格納されています。
そして前に指定したセッション名、こちらはアップロード時の画面に出てくるので、それを見ていると分かるのですが、出力が多いと見逃してしまうかもしれません。
これはtdコマンドのimport:listで表示できます。
+---------------------------------------+-----------------+-----------+--------+-------+-------------+-------------+---------------+---------------+
| Name                                  | Table           | Status    | Frozen | JobID | Valid Parts | Error Parts | Valid Records | Error Records |
+---------------------------------------+-----------------+-----------+--------+-------+-------------+-------------+---------------+---------------+
| favlook_fav_old_2013_10_25_1382661066 | favlook.fav_old | Uploading |        |       |             |             |               |               |
+---------------------------------------+-----------------+-----------+--------+-------+-------------+-------------+---------------+---------------+
1 rows in set

アップロードが終わると

Next Steps:
  => execute 'td import:perform favlook_fav_old_2013_10_25_1382661066'.

と、次にやるべきことが表示されます。

td import:perform favlook_fav_old_2013_10_25_1382661066
Job 5451708 is queued.
Use 'td job:show [-w] 5451708' to show the status.

ジョブが開始されました。td job:showで状況が見られるようです。

td job:show -w 5451708
Organization :
JobID        : 5451708
Status       : running
Type         : bulk_import_perform
Priority     : NORMAL
Retry limit  : 0
Result       :
Database     : favlook
Query        :
queued...
  started at 2013-10-25T23:19:16Z
  13/10/25 23:19:23 WARN conf.Configuration: fs.default.name is deprecated. Instead, use fs.defaultFS
  13/10/25 23:19:25 WARN mapred.JobClient: Use GenericOptionsParser for parsing the arguments. Applications should implement Tool for the same.
  13/10/25 23:19:26 INFO storage.S3StorageBackend: listing s3 files with prefix e71b21f4-3b82-4d7b-9ce8-4fc1ca74bf94/ps/
  13/10/25 23:19:28 WARN conf.Configuration: fs.default.name is deprecated. Instead, use fs.defaultFS
  13/10/25 23:19:28 INFO mapred.JobClient: Running job: job_201306191947_257352
  13/10/25 23:19:29 INFO mapred.JobClient:  map 0% reduce 0%
  13/10/25 23:19:53 INFO mapred.JobClient:  map 1% reduce 0%
  13/10/25 23:19:54 INFO mapred.JobClient:  map 4% reduce 0%

とmap & reduceの処理が流れていきます。気長に待ちましょう。
処理が終わったら、td import:listで状況を見てみましょう。

td import:list
+---------------------------------------+-----------------+-----------+--------+---------+-------------+-------------+---------------+---------------+
| Name                                  | Table           | Status    | Frozen | JobID   | Valid Parts | Error Parts | Valid Records | Error Records |
+---------------------------------------+-----------------+-----------+--------+---------+-------------+-------------+---------------+---------------+
| favlook_fav_old_2013_10_25_1382661066 | favlook.fav_old | Ready     |        | 5451708 | 1438        | 0           | 2062874026    | 0             |
+---------------------------------------+-----------------+-----------+--------+---------+-------------+-------------+---------------+---------------+
1 rows in set

StatusがReadyになっています。では、確定してcommitしましょう。

td import:commit favlook_fav_old_2013_10_25_1382661066

すぐに処理が帰ってきましたが、状況を見てみると

td import:list
+---------------------------------------+-----------------+------------+--------+---------+-------------+-------------+---------------+---------------+
| Name                                  | Table           | Status     | Frozen | JobID   | Valid Parts | Error Parts | Valid Records | Error Records |
+---------------------------------------+-----------------+------------+--------+---------+-------------+-------------+---------------+---------------+
| favlook_fav_old_2013_10_25_1382661066 | favlook.fav_old | Committing | Frozen | 5451708 | 1438        | 0           | 2062874026    | 0             |
+---------------------------------------+-----------------+------------+--------+---------+-------------+-------------+---------------+---------------+
1 row in set

という状態で、commitにもしばらく時間が掛かるようです。
Frozenというのがよく分からないのですが、処理が一時停止しているのかと思い、「td import:unfreeze」コマンドを実行してみたのですがエラーとなってしまいました。
ひとまず状況が分からないのですが、Committingなのでしばらく放っておこうかと放置。

td import:list
+---------------------------------------+-----------------+-----------+--------+---------+-------------+-------------+---------------+---------------+
| Name                                  | Table           | Status    | Frozen | JobID   | Valid Parts | Error Parts | Valid Records | Error Records |
+---------------------------------------+-----------------+-----------+--------+---------+-------------+-------------+---------------+---------------+
| favlook_fav_old_2013_10_25_1382661066 | favlook.fav_old | Committed | Frozen | 5451708 | 1438        | 0           | 2062874026    | 0             |
+---------------------------------------+-----------------+-----------+--------+---------+-------------+-------------+---------------+---------------+
1 row in set

見てみると完了していました。Frozen、謎です。

ブラウザでアクセスできる管理画面からも、データが確認できるようになりました。
レコード数が20億6287万4026件、サイズが17.3 GB。
CSVでは82GB有りましたが、項目名が「Compressed Size」となっているように、内部では圧縮して持っているようです。
無料で使える150GBというのは、この圧縮状態を指すのでしょうか?


こうしてデータの準備ができました。
次に集計をやってみます。
(Jobを走らせることはできたのですが、結果の取得方法などがよく分かっていないので、まだ実験中)

8月 12, 2013

ブラウザのホームページが conduit になってしまった。

ある日を境に、 Firefox のホームページが conduit とかいう検索エンジンになってしまった。
普段は iGoogle (もうすぐサービスが終わってしまう)で、新しいタブを開いたときは、 Firefox のよく使うサイトへのリンクが表示されているのだけど、これでは使いにくい。

ときどきアプリをインストールしたときに、インストーラーが「ブラウザのホームページを書き換えるか」と聞いてくるときがあるが、気がつかないうちに許可したか、無断で書き換えるやつがいたのかと思った。

まずはブラウザのホームページ設定を修正しよう。
Firefox だと、URL欄に about:config と入力することで設定画面が表示される。
ここの browser.newtab.url に先ほどの condoit アドレスが設定されてしまっているので、書き換える。
と、書き換えてみたのだが、修正が反映されない。
Firefox 自体が書き換えられてしまったのだろうか。しかしウイルス警告などは出ていない。





プロセス一覧を見てみると、 CtlMngSvc.exe と ctlmng.exe という二つの普段見かけないプロセスが動いている。これが原因らしい。
サービスとして動いてしまっていて、そのままでは消せないようだったが、幸いにもアンインストーラーが用意されていたので、そこから削除出来た。

アンインストールすると、ホームページの設定も保存されるようになった。

このソフトが入った原因がはっきりしないが、海外産ソフトをダウンロードしたときに、それらしいリンクからダウンロードしてしまった気がする。

5月 20, 2013

PostgreSQLのWALでのエラー・トラブル対応

注釈:この記事はQiitaにわたしが投稿していた記事を、自分のブログへ移動させたものです。
PostgreSQLでトラブルになったときに、つい忘れるのでメモ。

何らかのトラブルでNFSなどにWALが送られず、pg_xlogが膨れあがり、容量不足で停止したとき。
問題の無い範囲で、pg_xlogの内容を削除したのに、以下のようなログが出て、WALの送信が失敗するときがある。

cp: cannot stat `pg_xlog/00000001000010D000000000': No such file or directory
LOG: archive command failed with exit code 1
DETAIL: The failed archive command was: test ! -f /mnt/nfs_dir/archivedir/00000001000010D000000000 && cp pg_xlog/00000001000010D000000000 /mnt/nfs_dir/archivedir/00000001000010D000000000
WARNING: transaction log file "00000001000010D000000000" could not be archived: too many failures

原因は /pg_xlog/archive_status ディレクトリに、アーカイブの送信状況が保存されているが、これと実際のファイルの対応が不一致になった場合に発生する。
archive_status ディレクトリに「*.ready」というファイルがあれば、送信前。
送信が完了すると「*.done」になる。
これらを実際の pg_xlog に合わせよう。

広告