ダウンロードしたツイート CSV を Groovy + OrientDB で抽出

ワンショットではあるものの大きめのデータを処理する場合、データベースに入れたらいいなぁとおもいつつも、データベースサーバ起動するのが面倒だったり create table するのが面倒だったりすることがよくあります。

そんな時は組み込み可能な DB ってことで、こんなことに使ったら怒られそうではありますが OrientDB と Groovy の組み合わせ。タイムリーにも Twitter の自分の全ツイートが取得可能となったということで抽出をかけてみました。 🙂

OrientDB Graph-Document NoSQL dbms

OrientDB is an Open Source NoSQL DBMS with both the features of Document and Graph DBMSs. It’s written in Java and it’s amazing fast: can store up to 150,000 records per second on common hardware.

#もちろん OrientDB はサーバモードもありますです。

Twitter の全ツイートダウンロードですが、「ユーザ情報」の下の方にあるリクエストボタンをクリックするとメールリンクで CSV が送られてくるようです。

130326-0003

とりあえず Eclipse にファイルやライブラリを配置して・・・。(./file/Tweets は OrientDB によってつくられた DB の物理ファイルです)

130326-0001

まずは CSV2OrientDB のローダをかきました。 TweetsLoader.groovy

import au.com.bytecode.opencsv.CSVReader
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx
import com.orientechnologies.orient.core.record.impl.ODocument

// define CSV header (exclude expanded_urls)
def header = [
    "tweet_id"
    ,"in_reply_to_status_id"
    ,"in_reply_to_user_id"
    ,"retweeted_status_id"
    ,"retweeted_status_user_id"
    ,"timestamp"
    ,"source"
    ,"text"]

// load CSV
CSVReader reader = new CSVReader(new FileReader("./file/tweets.csv"))
List myEntries = reader.readAll()
reader.close()

// create OrientDB
ODatabaseDocumentTx db =
    new ODatabaseDocumentTx("local:./file/Tweets").create()

// import CSV
myEntries.each {
    ODocument doc = new ODocument("Tweet");
    def index = 0
    header.each { name ->
        doc.field(name, it[index])
        index++
    }
    doc.save()
}

// close OrientDB
db.close();

これを実行すると .csv が “local:./file/Tweets” DB に読み込まれます。スキーマレスなので適当にロードできます。お手軽。 🙂

でもって、できたデータベースにクエリーを発行します。 Groovy が入っているつぶやきを単純に like にて。 TweetsQuery.groogy

import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx
import com.orientechnologies.orient.core.record.impl.ODocument
import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery

// open OrientDB
ODatabaseDocumentTx db =
    new ODatabaseDocumentTx(
        "local:./file/Tweets").open("admin", "admin");

// query
List<ODocument> result = db.query(
    new OSQLSynchQuery<ODocument>(
        &quot;select * from Tweet where text like '%Groovy%'&quot;));

// output
result.each {
    println it.field(&quot;text&quot;)
}

// close OrientDB
db.close();

手抜きコード申し訳ない(笑)

という感じで実行結果です。 あとは煮るなり焼くなりできますね。

130326-0002

なんだか懐かしいツイートがいっぱいでてきました。。

ちなみに5万弱のレコード数でしたが、それぞれの処理は待つ暇なく終わっています。

ワンショットとはいえ、普通のサーバ型の RDBMS を使ってしまうとプロジェクトだけでアプリを管理できずポータブル性がなくなってしまいますので組み込みDBの形式は便利ですね。サーバの起動にやきもきすることもありません。

また、OrientDB はドキュメント型ということで、スキーマレスで扱えるのもこういった用途に気軽で良いです。 このソースでは手抜きしていますが、POJO へのマッピングもできますのできちんとかくこともできます。

なんだかものすごいプロダクトをこんなことに使っていいんだろうか、、とも思ってしまいますが、、使い方のひとつということでお許しを。。 🙂

Amazon EC2 と Groovy と QuartzScheduler と commons-daemon

2012/11/11 のポッキーの日に「AWSで動かすWordPressとその高速化」セミナーに伺いまして、Amazon Web Service を堪能して参りました。 🙂

AWSで動かすWordPressとその高速化

アマゾン ウェブ サービス(AWS) にはウェブデザイナーにとっても魅力的なサービスがたくさんあります!
今回はAmazon Web Servicesのテクニカルエバンジェリストである堀内さんをお招きし、すぐにでも使えるサービスを実際に体験しながら(ハンズオンで学びならが)その魅力たっぷりのサービスについてを学びます。

また、AWS上でWordPressを使えるようにし、さらに高速化する方法について、 株式会社デジタルキューブ の小賀さんをお招きし、実際に体験しながらその方法についてを学びます!

tenpura さんや小賀さんにも久しぶりにお会いできまして楽しいひとときでした。

仕事柄、仮想化基盤には慣れてはいるものの、超高速チューンされた WordPress である「AMI 網元」がほとんどワンクリックでデプロイされる様子や、ストレージのスナップショットバックアップはやはり魅力的なフューチャーでした。

WordPress AMI – 超高速 WordPress AMI 網元

EC2のMicro Instance(無償枠あり)を利用すれば、圧倒的にハイパフォーマンスなWordPress環境をコンパネ操作のみで誰でも簡単に構築できます。WordPressの高速化対策、スマートフォン対策に効果的です。

現在のクラウドと呼ばれるサービスの根幹をなしているのは間違いなく仮想化技術です。 そして WordPress を実用的にクラウドにのせる試みはすごく面白いものでした。:)

AMI 綱元も使える Amazon EC2 のマイクロインスタンス(アカウント登録から1年無料とのこと)。

EC2 は root がもらえる IaaS。

[tegaki]むふふ..(←脱線したようだ[/tegaki]

というわけで、Groovy と QuartzSchedulercommons-daemon を使って怪しい(?)デーモンを動かしてみることにしました。 😛

デーモンは処理(スレッド)の途中終了をつくるのが結構大変だったりしますが、QuartzScheduler にそのへんを任せると定期実行する常駐ものが簡単につくれます。

以下の例は 20秒おきにログをはくデーモンです。実際には CronScheduleBuilder.cronSchedule で時間を決め、QuartzJob::execute() に好きな処理を実装します。

QuartzDaemon.groovy

package net.maple4ever.daemon

import org.apache.commons.daemon.*
import org.quartz.*
import org.quartz.impl.*
import org.apache.log4j.Logger

class QuartzDaemon implements Daemon {

    def static logger = Logger.getLogger(QuartzDaemon.class)
    def static daemon = new QuartzDaemon()
    def static sched = (new StdSchedulerFactory()).getScheduler();

    @Override
    public void init(DaemonContext arg0)
        throws DaemonInitException, Exception {
        // every 20 seconds
        def job = JobBuilder.newJob(QuartzJob.class)
            .withIdentity("job1", "group1")
            .build();
        def trigger = TriggerBuilder.newTrigger()
            .withIdentity("trigger1", "group1")
            .withSchedule(
                CronScheduleBuilder.cronSchedule("0/20 * * * * ?"))
            .build();
        sched.scheduleJob(job, trigger);
    }

    @Override
    public void start() throws Exception {
        logger.info("START QuartzDaemon")
        sched.start()
    }

    @Override
    public void stop() throws Exception {
        logger.info("STOP QuartzDaemon")
        sched.shutdown(true)
    }

    @Override
    public void destroy() {
        logger.info("DESTROY QuartzDaemon")
    }

    static main(args) {
        daemon.init(null)
        daemon.start()
        System.in.withReader {
            print  'Hit any key to exit.'
            println it.readLine()
        }
        daemon.stop()
    }
}

class QuartzJob implements Job {

    def static logger = Logger.getLogger(QuartzJob.class)

    def QuartzJob() {
    }

    @Override
    public void execute(JobExecutionContext context)
        throws JobExecutionException {
        // execute job
        JobKey jobKey = context.getJobDetail().getKey()
        logger.info("QuartzJob says: " + jobKey + " executing at " + new Date())
    }
}

commons-daemon のインターフェースに従ってクラスを定義して、init で QuartzScheduler のジョブ定義。 commons-daemon からのの start、stop をそのままQuartzSchedulerにパスしてあげればOKです。

また、この例のように static main メソッドをつくっておくとデバッグ時にデーモンではなくそのまま実行できるので便利です。

できたら groovyc でコンパイルしたクラスファイルを、commons-daemon の jsvc を次のようなシェルから起動してあげます。

setenv.sh

#!/bin/sh

SCRIPT_HOME=/opt/daemon
JSVC_USER=hiromasa

JAVA_HOME=/usr/lib/jvm/java

GROOVY_LIB=/opt/groovy-2.0.5/embeddable/*
LIB_HOME=$SCRIPT_HOME/lib
LIB_PATH=$GROOVY_LIB:$LIB_HOME/commons-daemon/*:$LIB_HOME/log4j/*:$LIB_HOME/quartz/*
LIB_PATH=$LIB_PATH:$SCRIPT_HOME/script
LIB_PATH=$LIB_PATH:$LIB_HOME/twitter4j/*:$LIB_HOME/javamail/*

#for command line(ex. $GG Foo.groovy)
export GG="java -cp $LIB_PATH groovy.ui.GroovyMain"

# twitter4j とか javamail とかは必要ありません。(ぼくの別デーモンで使われています 🙂

export している $GG はスクリプト作成時の実行用で、シェルから . ./setenv.sh などとして環境をもってきた後に、$GG QuartzDaemon.groovy とするとコンパイルせずに static main から実行できるようなっています。(UNIX 上の vi などで直接 .groovy をかくときに便利です)

ディレクトリ構成と lib は以下のような感じです。

[hiromasa@]$ ls -laF
合計 28
drwxr-xr-x 7 hiromasa hiromasa 4096 11月 18 08:57 2012 ./
drwxr-xr-x 5 root     root     4096 11月 18 09:38 2012 ../
drwxrwxr-x 2 hiromasa hiromasa 4096 11月 18 09:45 2012 bin/
drwxrwxr-x 7 hiromasa hiromasa 4096 11月 18 01:53 2012 lib/
drwxrwxr-x 2 hiromasa hiromasa 4096 11月 18 09:48 2012 log/
drwxrwxr-x 4 hiromasa hiromasa 4096 11月 18 10:56 2012 script/
drwxrwxr-x 2 hiromasa hiromasa 4096 11月 18 09:48 2012 tmp/
[hiromasa@]$ ls -laF lib/*
lib/commons-daemon:
合計 32
drwxrwxr-x 2 hiromasa hiromasa  4096 11月 17 11:43 2012 ./
drwxrwxr-x 7 hiromasa hiromasa  4096 11月 18 01:53 2012 ../
-rw-rw-r-- 1 hiromasa hiromasa 24242  2月 23 23:22 2012 commons-daemon-1.0.10.jar

lib/log4j:
合計 488
drwxrwxr-x 2 hiromasa hiromasa   4096 11月 17 12:01 2012 ./
drwxrwxr-x 7 hiromasa hiromasa   4096 11月 18 01:53 2012 ../
-rw-rw-r-- 1 hiromasa hiromasa 489883  5月  6 11:01 2012 log4j-1.2.17.jar

lib/quartz:
合計 1212
drwxrwxr-x 2 hiromasa hiromasa   4096 11月 18 07:15 2012 ./
drwxrwxr-x 7 hiromasa hiromasa   4096 11月 18 01:53 2012 ../
-rw-r--r-- 1 hiromasa hiromasa 608376  5月 20 11:12 2011 c3p0-0.9.1.1.jar
-rw-rw-r-- 1 hiromasa hiromasa 578548  8月  6 13:28 2012 quartz-all-2.1.6.jar
-rw-r--r-- 1 hiromasa hiromasa  25496  5月 19 20:22 2011 slf4j-api-1.6.1.jar
-rw-r--r-- 1 hiromasa hiromasa   9753  5月 19 21:02 2011 slf4j-log4j12-1.6.1.jar

quartz.sh

#/bin/sh

# setup
. /opt/daemon/bin/setenv.sh

RUN=net.maple4ever.daemon.QuartzDaemon

# check args
if [ $# -ne 1 ]
then
	echo "usage: quartz.sh [start|stop]"
	exit 1
fi

ret=9
case $1 in
	start )
		echo "starting daemon..."
		$SCRIPT_HOME/bin/jsvc \
			-pidfile $SCRIPT_HOME/tmp/$RUN.pid \
			-user $JSVC_USER \
			-home $JAVA_HOME \
			-cp $LIB_PATH \
			$RUN
		ret=$?
		;;
	stop )
		echo "stoping daemon..."
		$SCRIPT_HOME/bin/jsvc \
			-stop \
			-pidfile $SCRIPT_HOME/tmp/$RUN.pid \
			$RUN
		ret=$?
		;;
esac

exit $ret

手抜きシェルですが、./quartz.sh start|stop でデーモンの常駐制御ができます。

hiromasa 24923     1  0 09:48 ?        00:00:00 jsvc.exec -pidfile /opt/daemon/tmp/net.maple4ever.daemon.QuartzDaemon.pid -user hiromasa -home /usr/lib/jvm/java -cp /opt/groovy-2.0.5/embeddable/*:/opt/daemon/lib/commons-daemon/*:/opt/daemon/lib/log4j/*:/opt/daemon/lib/quartz/*:/opt/daemon/script:/opt/daemon/lib/twitter4j/*:/opt/daemon/lib/javamail/* net.maple4ever.daemon.QuartzDaemon
hiromasa 24924 24923  0 09:48 ?        00:00:06 jsvc.exec -pidfile /opt/daemon/tmp/net.maple4ever.daemon.QuartzDaemon.pid -user hiromasa -home /usr/lib/jvm/java -cp /opt/groovy-2.0.5/embeddable/*:/opt/daemon/lib/commons-daemon/*:/opt/daemon/lib/log4j/*:/opt/daemon/lib/quartz/*:/opt/daemon/script:/opt/daemon/lib/twitter4j/*:/opt/daemon/lib/javamail/* net.maple4ever.daemon.QuartzDaemon

このように jsvc プロセスが 2つできれば成功です。 🙂

commons-daemon と QuartzScheduler を使えば、cron などを使うのと違って簡単に無理なく状態遷移を制御できますので良いのではないかと思います。

さぁ、Amazon EC2 のマイクロインスタンスでどんなデーモン動かしましょう。 いろいろアイディアがでてきますが、ネットワークの中で生き続けるプログラムってやっぱり面白いですね。 😀

VPS にデーモンを常駐させて遊ぶ

さくらインターネットさんが値段を下げてにわかに活気づく VPS。

VPS のよいところは、root 権限があることで、もう Web サーバやら PHP やら MySQL やらの束縛からは解放されて、好きな言語や環境でインターネットのアプリケーションがつくれる環境が整いました。 🙂

たぶん VPS で動かして一番面白いのは、デーモンとして常駐してリアルタイムで処理を行うアプリケーション。 じゃーってことで、Groovy でやってみることにしましょう。

まずは UNIX Daemon のつくりかた。 C でやれば fork して tty 切り離してみたいな感じになるとおもいますが、ここは Groovy ってことで、JVM 上のアプリを daemon 扱いにしてシグナルをとばしてくれる Apache Commons の commons-daemon を使うとよいです。

Daemon : Java based daemons or services

Daemon is made of 2 parts. One written in C that makes the interface to the operating system and the other in Java that provides the Daemon API.

commons-daemon には jsvc という UNIX Daemon と Java を取り持つバイナリのソースが入っています。 まずはこれを VPS の環境なりでコンパイルします。 Java のホームディレクトリを指定して configure をかけるのがポイントです。

src/unix$ ./configure --with-java=/usr/lib/jvm/java-6-sun

make すると jsvc のバイナリができます。

次に Java でデーモン用のソースをかきます。 ここでは Groovy で。

org.apache.commons.daemon.Daemon を implements してシグナルハンドラと結びつく規定のインターフェースを実装してあげればOKです。 サービスクラスはスレッドにして、ちゃんと途中でとまるようにつくる感じになるでしょう。

ここではおなじみ twitter4j のストリーミング API をサービスクラスにしています。 VPS とは関係ないですが、ストリーミング API でうけたメッセージを Ubuntu の NotifyOSD で通知するサンプルです。 (画面ならデーモンじゃなくて UI スレッドでやれって感じですが、サンプルということで…)

package net.maple4ever.daemon import java.security.MessageDigest import org.apache.commons.daemon.* import org.apache.log4j.Logger import org.gnome.gtk.Gtk import org.gnome.gdk.Pixbuf import org.gnome.notify.Notify import org.gnome.notify.Notification import twitter4j.Status import twitter4j.TwitterFactory import twitter4j.TwitterStream import twitter4j.TwitterStreamFactory import twitter4j.User import twitter4j.UserStreamAdapter import twitter4j.conf.ConfigurationBuilder class TwitterDaemon implements Daemon { private static TwitterDaemon daemon = new TwitterDaemon() private static Logger logger = Logger.getLogger(TwitterDaemon.class) private TwitterStream stream = null // twitter oAuth 設定(取得して入れます) def consumer = "" def consumerSecret = "" def accessToken = "" def accessTokenSecret = "" @Override public void init(DaemonContext arg0) throws DaemonInitException, Exception { // twitter 認証設定 def builder = new ConfigurationBuilder() builder.setOAuthConsumerKey(consumer) builder.setOAuthConsumerSecret(consumerSecret) builder.setOAuthAccessToken(accessToken) builder.setOAuthAccessTokenSecret(accessTokenSecret) def config = builder.build() // GTK 初期化 Gtk.init([] as String[]) Notify.init("notify.groovy") // Strinming API 取得 def twitter = new TwitterFactory(config).getInstance() stream = new TwitterStreamFactory(config).getInstance( twitter.getOAuthAccessToken()) // Strinming API イベント登録 stream.addListener(new UserStreamAdapter() { @Override public void onStatus(Status status) { User user = status.getUser() def url = user.getProfileImageURL() def screenname = user.getScreenName() def text = status.getText() try { // twitter アイコン取得 MessageDigest md5 = MessageDigest.getInstance("MD5") def md5str md5.with { md5str = new BigInteger( 1, digest(new File(url.file).name.bytes)) .toString(16).padLeft(32, '0') } def filename = "/tmp/" + md5str File file = new File(filename) if(!file.exists()) { file << url.getBytes() } def pixbuf = new Pixbuf( filename, 40, 40, true) // NotifyOSD 通知 def notify = new Notification( "@${screenname}", "${text}", null) notify.setIcon(pixbuf) notify.show() } catch (e) { logger.error(e.message) } } }) } @Override public void start() throws Exception { stream.user() } @Override public void stop() throws Exception { stream.cleanUp() } @Override public void destroy() { } static main(args) { daemon.init(null) daemon.start() } }

# こんなの動かす人いないと思いますが、GTK-Java のライブラリ jar は sudo apt-get install libjava-gnome-java で /usr/share/java かその辺に入ると思います。

デーモンではなく、通常実行でも動作させてデバッグしやすいように static main(args) もいれています。

で、できたら次のように jsvc にクラスを渡してデーモン化してもらいます。

/opt/twitter-daemon/bin/jsvc-ubuntu \ -pidfile /opt/twitter-daemon/bin/TwitterDaemon.pid \ -user hiromasa \ -home /usr/lib/jvm/java-6-sun/ \ -cp /opt/twitter-daemon/lib/*:/opt/twitter-daemon/classes \ -Dlog.home=/opt/twitter-daemon/logs/TwitterDaemon.log \ -Dstream.user.repliesAll=false \ net.maple4ever.daemon.TwitterDaemon

次のように Java のプロセスが 2つ常駐すれば成功です。

hiromasa 6975 1 0 23:09 ? 00:00:00 jsvc.exec -pidfile hiromasa 6976 6975 17 23:09 ? 00:00:01 jsvc.exec -pidfile

NotifyOSD で twitter の投稿がリアルタイムに通知されてきます。 🙂

moz-notify

停止は以下のようにします。 start / stop/ restart 用のシェルをつくることになるでしょう。

/opt/twitter-daemon/bin/jsvc-ubuntu \ -stop \ -pidfile /opt/twitter-daemon/bin/TwitterDaemon.pid \ net.maple4ever.daemon.TwitterDaemon

ここではソースのサンプルとして画面通知にしてしまっていますが、たとえば twitter 関係で VPS で動かすようなものなら、 bot 的なものも cron とかの無理な方法をとることなく簡単に扱いやすいアプリケーションが作れると思います。

常時インターネットにつながっている VPS でリアルタイム処理ができるのは、いろいろ夢が広がりますね。

GroovyClassLoader をうまくつかって Java と Groovy のインターフェースをつくってあげれば、スクリプトテキストファイルのまま、コンパイルすることなくデーモンをつくるようなことも可能です。

daemon

え、最後のそれ、らい子ちゃんを載せたかっただけだろって、、、。はい。 orz