2010年12月30日木曜日

Scala+Lift+MongoDBで作っているWebサービスを仮公開

http://catary.jp/

開発中のサービスを仮公開してみました。
これから細々と確認や調整をしていって1月11日にリリース予定。
ベースの部分はできていてこれからコンテンツ部分の機能を提供していきます。
使った技術とか
  • 言語とFWはScala 2.8.0 + Lift 2.1 + Maven 3.0
  • RDBはPostgreSQL9。ユーザマスタ等に利用
  • コンテンツの保管にはMongoDB1.6
  • サーバはAmazon EC2上のCentOS5.4
  • 投稿されるファイルの保管は全てAmazon S3
  • S3に保管したファイルを国内VPSのnginxでキャッシュして配布

10月にScalaを初めて触って、11月~12月と試行錯誤しつつなんとか色々作れるようにはなってきた。
PHPに比べるとかなり生産性は上がってきた気がする。
来年はちょいちょい事例やケーススタディを発表できるように頑張っていきます。

2010年12月27日月曜日

[Scala]LiftのLiftRules.rewriteでURLrewriting

開発に専念しててなかなか記事が書けない・・・軽めに書けるネタをひとつ。
Liftのrewriteを使ってURLを整えるコード。
今回やりたいのはこういう変換
  • IDだけが来たらindexにIDを渡す
    /a/[id]
    -> /aaa/index?id=[id]
  • IDと機能が来たら機能にIDを渡す
    /a/[id]/[function]
    -> /aaa/function?id=[id]
  • IDと機能とコンテンツ指定が来たら機能にIDとコンテンツ指定を渡す
    /a/[id]/[function]/[contents]
    -> /aaa/function?id=[id]&contents=[contents]
ポイントはURL上はIDを手前に持ってきて
ID:Aの機能XのコンテンツZ、という表記にするところ。

src/main/scala/bootstrap/liftweb/Boot.scala
LiftRules.rewrite.prepend(NamedPF("WorldRewrite") {
case RewriteRequest(
ParsePath( "a" :: id :: Nil, _, true , false ), _, _) =>
RewriteResponse(
List ( "aaa" , "index" ) , Map( "id" -> id)
)
case RewriteRequest(
ParsePath( "a" :: id :: function :: Nil, _, true , false ), _, _) =>
RewriteResponse(
List ( "aaa", function) , Map( "id" -> id)
)
case RewriteRequest(
ParsePath( "a" :: id :: function :: contents :: Nil, _, true , false ), _, _) =>
RewriteResponse(
List ( "aaa", function) , Map( "id" -> id, "contents" -> contents)
)
})
これでいけてるようです。バージョンは2.1。
  1. ParsePathで変換前のパスを指定。ディレクトリの区切りはリスト的な表記でどうにかしてくれます。
    後ろの引数はtrueになっているところがabsolute(絶対パス)、falseになっているとこがendslash。endslashの動きがいまいち読めないのでfalseにしてます。
    ここで指定しているidとかfunctionは続くRewriteResponseで利用します。
  2. ReqriteResponseで変換後のパスを設定します。
    まず第一引数には呼び出すビューの位置を指定します。ここでParthPathで設定した名前を利用できるのでList( "aaa", function )とすれば/aaa/functionになります。
    第二引数のMapではGETパラメータを設定できます。
  3. クエリストリングは特に何も指定しなくても勝手に引き継いでくれるみたいです。

2010年12月21日火曜日

PHPからScalaに移行して感じた事

Scala使ってアプリ作り始めて2カ月ほど経ったので感想を書いてみる。
あんまり技術ないのでぬるいプログラマとしての見方になるけどご容赦ください

これまでの経歴

たぶんどういう経歴かでScalaの印象は変わってくると思うので説明。
  • 16歳くらいの時にC言語で初プログラミング
  • その後はC/C++(DirectX)->Java(Applet)->Perl
  • 学生時代はPerl+ActionScript+JavaScript+Java Servlet
  • 仕事初めてからはほぼPHP。あとはJavaScript(jQuery)くらい
  • C~Java系から入ってその後PHP、という所がポイントな気がします
  • マークアップ、デザイン、PMとかもやるのでピュアなプログラマではないです

Scalaのいいところ

  • 静的型付け。C~Javaから始めた人間としては動的形付けはどうにも気持ち悪くてしっくりこない。
  • flatMap、Case class、クロージャとか便利。最近はifとforeach(構文の方)が気持ち悪い
  • 非同期な処理を書きやすい。特にPHPは言語内ではどうしようもない。
  • Javaと比べるとコード量はかなり少なく書ける。記法がC・Java系と同じで読みやすかった
  • [Lift限定?]Ajaxの処理が書きやすい。WebサービスでUI作ってると実感する

Scalaの微妙なところ

  • Javaの資産が使えるとはいえ事例が少なくてよくわからないことも多い
  • なんだかんだでコンパイルに時間はとられるよね
  • 個人で使う分にはいいけど、人口少ないし仕事で使うにはどうかな・・・。
    サービス系はともかく受託のSIでは微妙?

他に検討した言語

  • Java→さすがにもういい
  • Ruby→ちょっと出遅れ感が
  • Python→同じく出遅れ感が
  • Perl→昔使ってたし新しい言語にしたいなあ
  • Erlang→どう使っていいかわかりません
  • Haskell→意味がわかりません
  • LISP→レベルが足りません

Scalaの楽しいところ

  • 程よく難しい。PHPっぽい書き方もできるけどちょっとずつ変えていける
  • Webサービス作るうえで実用性は高い
  • コミュニティが最近活発

まとめ

  • CとかJavaから入って今PHP使ってる人が個人でなんかやるならとても楽しいと思います。ソースは私。
  • JavaScript(jQueryとか)使える人はとっつきやすいかと

2010年12月20日月曜日

コミックマーケット79に出展(企業)します

もう残り1週間ちょいしかないですが告知。今作っているサービスのデモとグッズを持ってきます。
一応、その時点でβオープンをするつもりです。残り時間があんまりないけどScalaさんならきっとなんとかしてくれる・・・!

-----------
コミックマーケット79
@東京ビッグサイト
12/29(水)、30(木)、31(金)
西4階企業ブース
923: Catary/かたりて
-----------


こんなストラップ作ってみました。これとクリアファイル2枚を付けて1000円で販売の予定。


こんな見た目してますが裏側はScala+Lift+MongoDBで作ってるので、事例としてフィードバックもできていったらいいなと思う次第です。
サービスの紹介は再掲ですがこんな感じ。

「Scalaな生活(2)」懲りずにまた作った

作業に詰まったので気分転換に。相変わらずのターゲット不明さ・・・。

名前がないと掛け合いに困るからとりあえず適当に設定。
由来はお察しください。どっちも言語や作者のからきてます。

2010年12月19日日曜日

「Scalaな生活(1)」コミPo!で誰得な漫画を作ってみた

「まったく絵を描かなくても、誰でもマンガを完成させられるソフトウェア」という触れ込みのコミPo!を入手したので早速誰得な漫画を作ってみた。
タイトルで展開がネタバレになってるのは気にしないでください。
続く・・・かどうかは未定。

2010年12月18日土曜日

[Scala]spawnを利用した非同期updateのコード

前回の記事で書いた課題を解決するために非同期updateの仕組みを追加。

どんな時に使うのか

  • その場で結果を利用しないupdate
  • できるだけ早くレスポンスを返したい場合
  • キャッシュ更新やインデックスの再構成とか

コード

scala.concurrent.ops.spawnで包めば別スレッドで処理してくれるのでそれだけ。本格的にやるならメッセージキュー使ったほうがいいんだろうけど、今のところそんなに負荷高まる予定もないので、、、
ただエラー時のロギングは追加した方がいい気はする。
scala.concurrent.ops.spawn{
mCtReaction.update(("world.id",world.id.toLong),("$addToSet" -> ("subscribers"->user.id.toLong)),Multi)
}

使ってるところ

前回記事の機能で、購読/購読解除のボタンを押した際に既存記事の購読者情報を置き換える部分。ここはボタンを押した瞬間には意味がないので数秒遅くなってもあまり影響が無い。
あとはプロフィールを更新した際にキャッシュを更新する部分にも入れる予定。

2010年12月16日木曜日

Scala+Lift+MongoDBでのタイムライン&購読機能実装

(この記事はScala Advent Calendar jp 2010の10日目です。というか今日誕生日だった、、、)

今個人的に作っているサービスで実装している機能なんですが、MongoDBを利用していてあまり事例がないケースかもしれないので細かく書いてみます。テスト環境上で稼働させているので実際にどんな動作をするかも確認できます。

前提条件が多いのでScalaの話になるまで少しかかりますがご了承ください。。。

説明編

作る機能
まず、機能の概要から。
  1. テーマ毎に投稿のタイムラインがある
  2. ユーザは好きなテーマを購読できる
  3. 購読したテーマの投稿をまとめたタイムラインがホームに表示される
  4. ポストにはテーマと投稿者の名前とアイコンが表示される
ざっくり上記4つの要件を満たすものを作ります。
MongoDBの特徴とか
まず簡単にMongoDBの説明から。Scalaを活用されてる皆様ならみんな詳しいと思うのでざっくり&個人的にいいと思う点など。
  • スキーマレスで事前定義なしでコレクションを保存できる。
    ただこのメリットはO/Rマッパが強力でテーブル定義を自動でやってくれるSchemifierがあるLiftではそこまで価値は無い気もする
  • Replicaset + Shardingによる高スケーラビリティ
  • 配列を含むレコードなど複雑なデータ構造を保存できる。
    配列内の要素にマッチするfindを使えるなど豊富なクエリ機能があってこれを活用します。
  • JoinおよびTransactionはできない。これが要件(4)で問題になるので工夫が必要になります。
サンプルアプリ
http://dev-ct3.catary.net/
id : scalaadvent
pass : mongodb

で動きを確認できます。登録されてるデータ等については深く気にしないでください。。。
適当なデータで新規登録してから使ってもらってもOKです。
データの構造
この機能で使っているテーブル(コレクション)は4種類で、うち2種類でMongoDBを利用しています。
  • [RDB]User:ユーザの認証とNickname、Icon等を格納
  • [RDB]Theme:テーマの情報(名前、Icon)等を格納
  • [MongoDB]Subscriber:UserとThemeの関連(購読情報)を格納
  • [MongoDB]Post:投稿の情報を格納
MongoDB側の構造と関連するインターフェイスは以下図のようになっています。
このインターフェイス部分をScala+Liftで実装していきます。

処理のポイント
  • 購読者の情報は投稿1つ1つに配列で持つ。MongoDBはドキュメント内の配列もインデックスと検索をかけられるので後から参照するのが楽
  • ニックネームやサムネイル等もそれぞれ持つ。MongoDBはjoinができないので常にキャッシュを持っている感じ。

実装編

LiftのmongoDBサポート
Liftには標準でMongoDB関連のサポートがあるため、ありがたく利用させててもらいます。
http://www.assembla.com/wiki/show/liftweb/lift-mongodb
を見れば大体わかるというかほぼそのまま使ってます。
1.dependencyの追加
Mavenを使っているのでPOM.xmlのdependencyに必要なモジュールを追加します。sbtの場合もきっと似たような事をする必要があるかと。
<dependency>
<groupId>net.liftweb</groupId>
<artifactId>lift-mongodb_2.8.0</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>net.liftweb</groupId>
<artifactId>lift-mongodb-record_2.8.0</artifactId>
<version>2.1</version>
</dependency>
必要なimportは以下5つほど。JsonDSLはいい感じにJsonを記述できるようになるパーサのようです。
import net.liftweb.mongodb._
import net.liftweb.mongodb.record._
import net.liftweb.json._
import net.liftweb.json.JsonAST.JObject
import net.liftweb.json.JsonDSL._
2.MongoDocumentを利用してモデル定義
チュートリアルにあるように、trait MongoDocumentとtrait MongoDocumentMetaを利用してモデルを定義します。このへんなMapperと似たような感じです。
import net.liftweb.mongodb._
import net.liftweb.mongodb.record._

case object CataryMongoIdentifier extends MongoIdentifier {
val jndiName = "catary"
}

case class mTheme( id: String, cataryId:String, name: String, thumb: String )

case class mUser( id: String, cataryId:String, name: String, thumb: String )

case class mCtSubscriber(_id: String, theme: mTheme, user: mUser, regdate: String )
extends MongoDocument[mCtSubscriber] {
def meta = mCtSubscriber
}

object mCtSubscriber extends MongoDocumentMeta[mCtSubscriber] {
override def mongoIdentifier = CataryMongoIdentifier
override def collectionName = "subscriber"
}

case class mSubscribers( user:String )

case class mCtPost(_id: String, theme: mTheme, user: mUser, catagory: String, text:String, time:String , subscribers:List[String] )
extends MongoDocument[mCtPost] {
def meta = mCtPost
}

object mCtPost extends MongoDocumentMeta[mCtPost] {
override def mongoIdentifier = CataryMongoIdentifier
override def collectionName = "post"
}
MapperよりもScalaのオブジェクトそのものっぽく定義できるので気分がいいです。List[String]を持ってしまえるあたりとか。
mThemeとmUserはmCtPostとmCtSubscriber両方が持っているなど構造的にも自然です。
3.投稿(Insert)の実装
ドキュメント(RDBでいうレコード)を保存するには、2で作ったモデルを作ってsaveするだけで終わります。が今回は「購読したものだけのタイムライン」を実装するためにSubscriberコレクションから現時点での購読者を取得して各投稿データに持たせます。
問題なのはその際にmongoDBのdistinct(特定のフィールド、条件の値を配列で返す構文)を使いたいのですがどうやらLiftのMongoDocumentでは提供されていない、、、。ただ、Javaのcom.mongodbを利用すれば使えるのでそちらを経由してどうにかします。

MongoDB.use(CataryMongoIdentifier) ( db => {
val coll = db.getCollection("subscriber")
val search = new BasicDBObject
search.put("theme.id",theme.id.toString)
/*
* List展開部分は要改善。このままだと非効率。。。
*/
var subscribers:List[String] = List()
coll.distinct("user.id",search).foreach(v => subscribers = subscribers.::(v.toString) )

//ここからドキュメントの保存。長く見えるものの実際はインスタンスを生成してsaveするだけ
val post = mCtPost(
ObjectId.get.toString,
mTheme(
theme.id.toString,
theme.cataryId.toString,
theme.themeName.toString,
UrlHelper.themeThumb(theme)
),
mUser(
user.id.toString,
user.cataryId.toString,
user.nickname.toString,
UrlHelper.userThumb(user)
),
"post",
text,
(new Date()).getTime().toString,
subscribers
)
post.save
4.テーマの購読
テーマ購読時はSubsucrierのインスタンスを生成+saveした上で、既存Postの購読者にも追加します。
updateの第一引数は検索条件、第二引数がupdateの内容になるのですが、ここで"$addToSet"を使うと配列に上書きすることができます。"$push"も似たような動きですが"$addToSet"は同じ値が複数入らない、はず。。。
第三引数で渡しているMultiは条件に一致したドキュメントを全て更新するための指示です。MongoDBのupdateはデフォルトだと発見された1件目のみの更新をします。
val subsucribe = mCtSubscriber(
ObjectId.get.toString,
mTheme(
theme.id.toString,
theme.cataryId.toString,
theme.themeName.toString,
UrlHelper.themeThumb(theme)
),
mUser(
user.id.toString,
user.cataryId.toString,
user.nickname.toString,
UrlHelper.userThumb(user)
),
(new Date()).getTime().toString
)
subsucribe.save
//既存データの更新
mCtPost.update(("theme.id",theme.id.toString),("$addToSet" -> ("subscribers"->user.id.toString)),Multi)
5.テーマの購読解除
解除する際はfindを利用して1件データを取得し、deleteを呼び出します。購読時とは逆に、"$pull"を指定して購読者を取り除きます。こういった「ドキュメント内配列のあるデータだけ操作する」実装が楽にできるのがMongoDBのいいところです。
mCtSubscriber.find(("user.id" -> user.id.toString) ~ ( "theme.id" -> theme.id.toString)) match {
case Some(subscriber) => subscriber.delete
case _ =>
}

//既存データの更新
mCtPost.update(("theme.id",theme.id.toString),("$pull" -> ("subscribers"->user.id.toString)),Multi)
6.タイムラインの取得
問題の「購読しているテーマの投稿をまとめる」機能ですが、ここが簡単でfindAll一行で終わりです。
これを書くためにMongoDBにしているといっても過言ではないくらいのあっけなさ。
第一引数で「subscribers内にuser.idが含まれるドキュメント」という抽出をしています。
第二引数はsort順で、時間の降順を指定しています。またSkip、Limitを使うと取得範囲を指定できるのでPaginationの処理で利用します。(今回はとりあえず先頭100件)
あとはflatMapでSnippetに適当にバインドしましょう。
mCtPost.findAll(("subscribers" -> user.id.toString),("time" -> -1),Skip(0),Limit(100)).flatMap( item =>
bind("item", xhtml,
// Snippetへのbindを書く
// "name" -> data
)
)
7.プロフィール更新時の処理
購読者コレクション、投稿コレクションともにある時点のニックネームやアイコンをそのまま保持しているのでマスタが更新された場合は合わせて更新してあげる必要があります。これも1行ずつ書けば終わりです。
mCtSubscriber.update(("theme.id",theme.id.toString),("$set" -> ("theme.thumb" -> UrlHelper.themeThumb(theme)) ),Multi)
mCtPost.update(("theme.id",theme.id.toString),("$set" -> ("theme.thumb" -> UrlHelper.themeThumb(theme)) ),Multi)
8.課題
とりあえず動いていますが色々と課題はあり。
  • distinct時の効率が悪い。そもそも一度リストを取得しているのがばかばかしいので、ここはストアードファンクションにしてしまった方がいいかもしれない
  • 冗長なデータが多いのでデータが多くなるとディスクを大量に消費しそう
  • この実装でパフォーマンスがどれだけ出るのか未知数。記述は楽なんだけど・・・
  • 既存データへのupdate系は非同期に処理させるようにしたい。Scalaの場合spawnを使えばいいんだろうか?

まとめ

さくっと書こうとしたものの長いうえに説明しきれてない感じになってしまった、、、すみません。
アプリに組み込んでしまってるのでMongoDB部分のコードだけ見てもわかりにくい部分もあるかと思います。自分で作ってるものなのでTwitterで声かけてもらえればより詳しい話しや他のコードを見せられます。

MongoDBに関しては、RDBでは面倒だった処理が簡単に書けたり、逆にRDBで簡単にできることがまったくできなかったり使いどころは難しいですがうまく使えば面白いことができそうです。

Lift+MongoDBの組み合わせがそもそも事例少なすぎなので色々試してフィードバックできたらと思っています。という感じで明日の人にバトンタッチ!

おまけ

今作ってるアプリの資料。近々Openするつもり。

2010年12月14日火曜日

node.jsをAmazon EC2のmicroインスタンスに入れる時の注意

インスタンスが安いのでテスト時、開発時はいつもMicro instanceを使ってるんだけどnode.js入れる時に引っ掛かったのでメモ。


ここのサイトを参考にnave経由で入れようとしたら、ビルドが1時間たっても終わらないうえに途中でkillされてその後サーバの負荷が100%に。
stopも受け入れてくれないので無理やりforce stopするなどしないとだめでかなり焦った。

どうにもうまくいかないなーと暫く考えて、もしやインスタンス性能?と思い当たりAMIをバックアップして一時的に性能のいいインスタンスに変更。それでやったらあっさり数分で終わった、、、かなり性能が絞られているようです。

node.jsに限らず、少しお金はかかるもののビルドするときは性能のいいインスタンスで起動しなおしたほうがいいかも。こういう手が気軽に使えるのはEC2のいいところですね。

2010年12月12日日曜日

第1回 MongoDB JP & CouchDB JP 合同勉強会 in Tokyoメモ

ギリギリ繰り上げで参加できることになったようだったので行ってきた。ちょうど会場近くの友人宅の家にいたから昼からそのまま直行。ちょうど今からmongoDB使いはじめてるからすごいいいタイミング。


ざっくりとメモと生かせそうな所を書いておく。

MongoDB JPからのメッセージ @doryokujin
  • 11月18日設立
  • 12月12日時点で200人弱
  • MongoDB Conference in Japan開催に向けての活動。日本でぜりやりたい!
  • 2011年2月末~3月 開催を目指して調整中

MongoDBを用いたソーシャルアプリのログ解析 @doryokujin


  • ログ解析をどう活用していくのか。GTEの場合は「意思決定を支援するための結果」に特化している
  • MySQLすばらしい。がスキーマの制約が行動ログの保管には厳しい
  • Hadoop、HIVE、pig強力だが大げさ、、、
  • Cassandra、HBaseはバックエンドとして強力だがフロントエンドには面倒
  • mongoDBは全体的に便利。joinはできないが範囲検索ができるなどfindが強力
  • インデックス張るのに時間かかる?
    →かかるけどバックグラウンドでのんびりやってくれる
  • データを見せながら会話、議論ができるのがよい
mongoDBのRESTinterface、これいいな。
と思ってオフィシャルみたらNode.js版もAlphaだけど作られていた。Pythonよくわからなくてハックし辛いからこなれるならNode.js版使いたいな。。。
ログレシーバみたいな高速処理が必要なのはNode.jsのほうがいいのかも。

Ameba Pico におけるMongoDB運用事例 @snamura

なぜmongoDBにしたのか?
mongoDB前は独自のDistributed Databaseを作っていた。(バックはMySQL)
しかし不安定になってきたので新DBの選定に移った。

データベース選定の条件
  • バイナリ形式に対応している
  • キーに対するRange Scanが可能
  • Horizontal Partioningによる分散
  • 開発チームがアクティブ
mongoDB
  • BSON
  • インデックスのサポート
  • Shardingによる分散。Chunkの分割の方式が既存と同じ
  • 開発チームが積極的でアクティブ

最初の計画
  • Arbitery=ReplicaSetは3台が基本で、2台でやりたい場合はArbitaryが必要
導入した構成
  • Shard x 3 x 3のReplicaSet
  • configはSecondaryに配置。SocketServer側にmongosを設置
  • mongos=ShardingされたDBがあたかも1つに見えるような形になる
  • データ移行の際はバイナリのソート方式に注意

遭遇した問題
  • ext3はextentに時間がかかる
  • 推奨されているのはext4,xfs。Picoはext4

Shardingの偏り
  • 普通に運用すると偏る。Webから各コレクションのアクセス量を確認し、多いコレクションはmoveChunkコマンドで移動する
  • ドキュメント量の少ないコレクションはRead、Writeが偏るので適宜アプリで対応

接続プール
  • ファイルオープン数を増やしてスレッド数やプール数を調整する
  • =OSのファイルオープン数を調整する必要がある。mongoDBを再起動する
Primary選択問題
  • EBSを使っているSecondaryのPriorityを0にしてPrimaryに昇格しないようにしていた
  • oplog(=リレーログ、バイナリログ)というものがあり、これが最新のものが昇格するルールもある
  • よってoplogの状況とPriority次第では両社がブロックしてPrimaryがいない→コマンドも受け付けないのでstepDownもできない
  • configサーバを直接いじって直した

→Priority:0今は危険!!!

よかったところ
  • 思ったよりずっと安定している
  • プロトコルがシンプルでノンブロッキングIOなドライバへの対応が用意
  • Atomic modifiers便利
  • Sharding+Replica Set
  • mmapを活用するので基本メモリキャッシュに乗る→すごくはやい!!!

気になるところ
  • Durability
  • Chunkが分割されるときのフリーズ
  • Shardingの安定性
  • Primaryがダウンしたときのデータロス
  • ディスク容量を結構使う

CouchDB JPからのメッセージ @yssk22

  • 1月Hackathon開催
  • 3月OSC Tokyo Spring
  • 12月MongoDB&CouchDB勉強会
  • CouchDB on Android
  • BigCouch

BigCouchの話 @yssk22

  • クライアントも視野に入れたクラスタリング
  • クラスタリングが無い=OuchDB。。。
  • CouchDBの開発者は元々Lotus
  • CouchDBの目指すクラスタはかなり方向性が違う

BigCouchとは何か
  • 構成‐CouchDBを並べたもの
  • CouchDBにクラスタ構成の機能を追加。本家とは別に作られている
  • 表側からは単一のCouchのように見える→アプリを書く側からは意識することはない
  • 分散アルゴリズムはDynamoほぼそのまま
  • Shardの数は3以上は欲しい
  • 中の人曰く、「ドキュメントはWebにあるものが全て=ソースみろ」
  • 設定ファイルがErlangの機能を前提にしているのでErlangの知識も必要
トレードオフの関係
  • q:シャード数
    増やすほどMapReduceの負荷が分散
  • n:複製するシャード数
    増やせば増やすほどディスクを食う
    r <= n w <= nでないといけない
  • w:書き込み保証数
    増やせば増やすほどレイテンシは下がるが整合性が高くなる
  • r:読み込み保証数
    増やせば増やすほどレイテンシは下がるが整合性が高くなる
運用は結構大変。仕事でやるの結構やだな。。。
BigCouchはmongoDBの真似しているような感じで面白くない。
オフラインでも使えるように端末に組み込むなどCouchならではな使い方をしたほうがいいのでは。

[LT]ECサイトでのMongoDB導入事例 @basuke

PostgreSQLからmongoDBへの移行
  • not only 規模問題
  • 複雑性への解決!
オーナーのこだわりが凄い。しかも自分でテキスト書いて写真をつけたりしている。
でも楽はしたい。通販サイトなのである程度は構造は必要。
とにかく色々と要望が多い。

どうするのか?→そこでmongoDB

元々はPHP&PostgreSQL
そこからMongoDB+PHP5.3+Lithiumへ変更予定

移行に必要なデータの変換について
MongoDBはツールが結構揃っている。しかもドキュメントがちゃんとある。
PHPで変換スクリプトを組んでmongoimportで入れた

PHPはつらい。Pythonのほうがよかったと後で思い返した。

小ネタ1:atomicについて
→オーダーレベルでatomicであれば問題ないので1つのコレクションにまとめる

小ネタ2:シリアルについて
→注文系だとシリアルな番号も欲しい。
 そういう時はファンクションを書いてevalで呼び出す。

小ネタ3:mongo on github
 JavaScriptのSnippet万歳

[LT]センサーデータストアとしてのCouchDB @motokazu

とりあえずデータをためたい人にはよい。
センサーからデータを送るにはhttpが都合がよい
relaxって何?

家の中をモニタリングブーム
  • オール電化
  • 部分的に設置
  • センサーを自分で作る(Arduino)
自分で作れば安くどうにかなる!
JSONをsprintfで生成してhttpで送ってあげる仕組み
map->reduce->rereduceで集めないといけない

何がいいのか
  • 一人、オール電化
relaxとは?
  • ストレスなくdbつめこみからWebアプリまでできる

[LT]MongoDBと位置情報 @madapaja

地理空間インデックスの使いかた
位置情報が今Hot。GPSとか色々

二次元の地理空間情報をインデックスにして検索ができる

Indexの作成
Indexを作成したいフィールドにensureIndex "2d"
ただし二つのGeoIndexははれない

保存も簡単。2つのfloatをx,yの順で入れるといい

クエリー色々
  • 完全一致→あんまり意味が無い
  • $near→指定ポイントから近い順にソート
  • $maxdistance→検索範囲を指定する
  • geoNearコマンドを使うと詳細情報を取得できる
    near→GeoHashのビット列が返る
  • $within→領域を指定して検索できる。長方形と円形が使える
普通に使うと赤道から離れるほど精度が落ちてしまう。
そこで1.7.0以降で利用できる予定の「Sphere」クエリ
これで正しい距離でのソート可能になる。
※Sphereでは距離の単位ではラジアンになる

利用の注意
  • Shardされたコレクションでは利用できない
  • (Sphereの場合)経度、緯度はX,Yが並んでいることを前提に計算する
  • (Sphereの場合)北極、南極や+-180以上は対応していない

[LT]CoucnDB on Androidでスタンドアローンアプリ @mkouhei

家計簿に割く時間が多く、負荷が高いのをなんとかしたいというところからスタート。
レシートの入力はAndroidで実行し家のMacBookで閲覧するような形に。

自分でWebアプリ作るにはなかなかいいかも。

内容以上に家の環境が凄くてそこが記憶に残った。。。
トリにふさわしい面白い話でよかった。

-------

というわけでこれから懇親会に。

2010年12月10日金曜日

mongoDBのinsert(update)で他のコレクションからfindした値を配列に入れる方法

まず、
subscribers{ 
_id:id,
theme:"theme",
subscriber:"user_id"
}
こんな感じのsubscribersコレクションがあると仮定。
これはあるテーマの購読者を持っているコレクションとします。
で、これをtheme_nameの記事が新しく追加されるとき記事の方にも持ってきたい。

ただこれをfindでそのまま持ってくるとBSON形式になってしまうので特定キーの値だけを配列として入れ込みたい。どっかにサンプルあるのかもしれないけどドキュメント見ながら色々試した感じいけそうなやりかたを発見。
ドキュメントの「集約」にあるdistinct
これを使うと
db.subscribers.distinct("subscriber",{theme:"theme"})
[ "user_id1","user_id2","user_id3" ]
と配列で返してくれるから
db.article.insert({
title:"title",body,"body",theme:"theme",
subscribers: db.subscribers.distinct("subscriber",{theme:"theme"})
})
でうまくいくはず。

既存の記事にsubscriberが増えた場合は
db.article.update({theme:"theme_name"},{$addToSet:{subscribers:"new_subscriber"}})
で、減る場合は$addToSetと$pullに変更。

インタラクティブシェルでは問題なく動いたから、後はパフォーマンスがどうなるかとScalaからどうやってアクセスするか。distinctがサポートされてなかったらストアドファンクション作るしかないのかな?

もっといい方法を知ってる方がいたらぜひ教えてください。。。

2010年12月9日木曜日

Amazon S3のファイルを国内VPS(nginx)にキャッシュして配布

今作っているサービスで、ユーザのプロフィール画像をとかを保存、配布するために作成。
サーバはほぼEC2に置いているので、画像とかは一旦S3に保管する予定。とはいえS3だと遅すぎるのでCloudFrontにするか自前で何か作るかを検討。
CloudFrontでもいいんだけど従量制だし、あれってCloudFrontのアドレスになった気がするから国内VPSに設置を。

思いついたのはdankogaiさんのこの記事から。

結果としては一応できていそうな感じで、こういった状態


http://img.catary.net/s/01_200.jpg
※画像については深く気にしないように

実体のファイルはS3のBucketにあり、nginxのReverseProxy+CacheでVPSにロードしています。
静的ファイルとプロフィール画像は基本的に同じURL=内容は変わらないという設計にしているのでサーバの力が持つ限りキャッシュを保持するように設定をする予定。(今は検証中なんで短時間の保持に)



最終的な環境ではかなり楽に構築できるけど、ここまで来るのには結構時間かかった。
  • Apache→ReverseProxy、CacheはあるがReverseProxy+Cacheができない。これに気付かなくて暫く詰まった、、、
  • Squid→動作自体はできたが、S3に繋がらない。おそらく送出ヘッダの問題なんだが設定がうまくまわらないので一旦他のを試してみることに
  • Pound→キャッシュ機能ない?
  • nginx→サクっといけた!!!!
ということでnginx採用。

手順メモ


nginxの導入はEPELからリポジトリもってきてyumで導入(のつもり)
wget http://download.fedora.redhat.com/pub/epel/5/[arch]/epel-release-[version].noarch.rpm
rpm -ivh epel-release-[version].noarch.rpm
yum install nginx
ln -s /etc/rc.d/init.d/nginx /etc/rc.d/rc3.d/S88nginx
Reverse Proxyの設定は
のサイトを参考。

これをベースにロケーション部分を
location /s {
proxy_pass http://s3-us-west-1.amazonaws.com/bucket-name; ~ }
みたいな感じにして各locationをS3のURLに置くだけ。
今のところ存在しないファイルを指定しようとするとレスポンスがそのまま帰ってきてしまうのでそこはどうにかしたい。

EC2使ってて、国内にファイル配信する場合はこういうのでもいいんじゃないでしょうか。

2010年12月8日水曜日

Advent Calenderに向けてブログ設置してみたよ!

Scala Advent Calendar jp 2010に向けて設置してみた!
1日目から@hito_asaさんが面白い記事を書いていてやばい・・・今のうちに仕込んでいかなきゃ。

このブログでは自社サービスを開発するにあたっての色々を書いていく予定。
今の開発環境は
  • Scala2.8.1+Lift2.1
  • Maven3
  • PostgreSQL9
  • mongoDB1.6.3
な感じ。

そのほか使ってるのは
  • Github
  • Amazon EC2/S3
  • nginx
とかそのへんです。

今月は引きこもって開発ばっかりしてるからちょいちょいメモ代わりに書いていきたい。