Home > 開発 Archive

開発 Archive

CentOS6.3にてffmpegをビルドした時の記録 その2

前回の記事からの更新です。

今回は、AAC関係のものをビルドに含めているので再配布不可なビルドです。
ffmpegを2.1系にした事で、VP9, H265/HEVC が使えるようになっています。

前回からの変更点

  • ビルドに以下を追加
    • gmerlin (frei0rの依存)
    • frei0r
    • libaacplus
    • mp4v2
    • libfaac
    • fdk-aac
  • ffmpeg-2.0.1からffmpeg-2.1.1にバージョンアップ
  • libblurayを0.3から0.5にバージョンアップ
  • vid.stabのバージョンを 9ed9836e10567f76decfa7fac178bb70a7bb9452 に固定
    • trunkや0.98はvsLocalmotions2TransformsSimpleが消えていてffmpegのビルドでエラーになるため
    • またこのバージョンにした事でCMakeファイルの場所がsrcから直下に変更された
  • libvpxを1.2.0から1.3.0にバージョンアップ
    • vp9のため

 

Continue reading

Windows OSに関する覚え書きと訂正

以前、こちらの記事で以下の事を書いたのですが、これは間違いでした。ごめんなさい(´・ω・`)。

2. GetFileInformationByHandleで取得するBY_HANDLE_FILE_INFORMATIONのnFileIndexHigh, nFileINdexLowを参照する。こちらは再起動、ボリュームのマウントしなおしで値が変わります。

Windows2008R2で再起動、再マウントでファイルシステムがNTFSの場合、変わらないことを確認しました。ではどういう時に変わるのかというと、ファイルシステムがFAT系(FAT, FAT32)の時にRenameをし、そのRenameによって名前が長くなったり、ディレクトリ内の順番が変わったりするような場合に変わりました。
(NTFSでも別ボリュームへ移動した場合は変わります(※1)) 

また、同一性の判定については、nFileIndexHighとnFileINdexLowだけでは不十分で、dwVolumeSerialNumber も合わせて比較する必要があります。まぁ別ボリュームでnFileIndexHighとnFileINdexLowが一致するって事はレアケースだとは思いますが、完全ではないので。

MSDNのRemarkのところに記述があります。

なので、ローテーションされているログをTAILで追跡するような場合、以下の前提が必要になります。

  • NTFSファイルシステムであること
  • ローテーション先が同一ボリュームであること

※1について
NTFSで別ボリュームに移動した場合でも追跡したい場合はDeviceIOCtrlを使う必要があります。

確認したOS

確認したOSはWindows2008R2です。

2008R2はNTカーネル6.1なのでWindows7と同等のカーネルです。一応主要OSのNTバージョンを表にしておきます。

NT Version OS
NT 5.1 Windows XP
NT 5.2 Windows Server 2003
NT 5.2 Windows XP (64bit)
NT 6.0 Windows Vista
NT 6.0 Windows Server 2008
NT 6.1 Windows 7
NT 6.1 Windows Server 2008 R2
NT 6.2 Windows 8
NT 6.2 Windows Server 2012
NT 6.3 Windows 8.1
NT 6.3 Windows Server 2012 R2

手元で確認できるのは、XPとVistaとWin7、EC2上でWindowsServer2008, 2012があるのでもうちょっと別のOSで確認してみます。(-_- )ウーン・・
2003はEC2に無いので無理です。

※追記(2013/12/19 11:00) EC2にWindows Server 2003ありました。QuickStartに出てこないだけで普通にありました。ごめんなさい。

また何かわかったら書きます。

いや、全然違うよ!正しくはこうだよって指摘がありましたらツッコミよろしくお願いします。

Treasure DataのHiveクエリでハマったこと

tdを使って集計しようと思ったのですが、Long型をソートしたら文字列型としてソートされてしまい、しばらく悩んだ時のメモです。

Continue reading

sequelのmigrationで列にcharacter setを指定する方法

RubyのsequelというORMライブラリがあります。

細かい使い方はQiitaにいくつか記事があるのでそちらを参照するとして、今回はMySQLで以下のような列にcharacter setが指定してあるDDLを発行させる方法のメモ書きです。

確認したバージョンは sequel 4.2.0, adapterにmysql2 0.3.13です。 

create table example1(
  id bigint not null,
  url varchar(255) character set ascii not null,
  constraint pk primary key (id)
) engine=InnoDB, default character set='utf8mb4';

先にMySQLにテーブルを作ってdumpをしても、認識してくれません(´・ω・`)

bundle exec sequel -e development ./config/database.yml -d
Sequel.migration do
  change do
    create_table(:example1) do
      primary_key :id, :type=>Bignum
      String :url, :size=>255, :null=>false
    end
  end
end
bundle exec sequel -e development ./config/database.yml -D
Sequel.migration do
  change do
    create_table(:example1) do
      primary_key :id, :type=>"bigint(20)"
      column :url, "varchar(255)", :null=>false
    end
  end
end

これを実行しても、列に指定してあるcharacter setがおちてしまいます。あと create table のオプションに InnoDB, utf8が出ています。utf8mb4 を指定したのに・・。このへんは Sequel::MySQL.default_engine Sequel::MySQL.default_charset Sequel::MySQL.default_collate あたりで設定するみたいです。今回はcreate_tableの時に指定してみます。

mysql> show create table example1 \G;
*************************** 1. row ***************************
       Table: example1
Create Table: CREATE TABLE `example1` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `url` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

ERROR:
No query specified

というわけで、以下のように修正してみました。まず、create_table する時に明示的に engine と charset を指定しました。 また列定義で明示的に character set asciiを追加しました。オマケでchangeからup/downに変更しています。

Sequel.migration do
  up do
    create_table(:example1, :engine => 'InnoDB', :charset=>'utf8mb4') do
      primary_key :id, :type=>"bigint(20)"
      column :url, "varchar(255) character set ascii", :null=>false
    end
  end
  down do
    drop_table(:example1)
  end
end

↑を実行すると、↓のような結果にできました。

mysql> show create table example1 \G;
*************************** 1. row ***************************
       Table: example1
Create Table: CREATE TABLE `example1` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `url` varchar(255) CHARACTER SET ascii NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

ERROR:
No query specified

ただし、この方法だとRDB間の差異は吸収できません。String :url, :size=>255, :charset=>'ascii'と書けると嬉しいんですけどね・・。知ってる人いたら教えてください。

 

親ディレクトリにinstall.shがあるとlibtoolize -cでltmain.shがコピーされない

libaacplusをビルドしていて気がつきました。

親ディレクトリか親の親ディレクトリにinstall.sh が存在すると、libtoolize -cltmain.sh がコピーされません。
以下確認した履歴です。

なお、環境はCentOS6.4でlibtoolizeのバージョンはlibtoolize (GNU libtool) 2.2.6bです。

libaacplusの場合、./autogen.shを実行するだけで良いのですが、今回はlibtoolizeに原因があることを確認するために手順を変更しています。

正常な場合

mkdir -p /a/b/c/d/e
cd /a/b/c/d/e
wget http://217.20.164.161/~tipok/aacplus/libaacplus-2.0.2.tar.gz
tar zxvf libaacplus-2.0.2.tar.gz
cd libaacplus-2.0.2

libtoolize -c
libtoolize: putting auxiliary files in `.'.
libtoolize: copying file `./ltmain.sh'
libtoolize: putting macros in AC_CONFIG_MACRO_DIR, `m4'.
libtoolize: copying file `m4/libtool.m4'
libtoolize: copying file `m4/ltoptions.m4'
libtoolize: copying file `m4/ltsugar.m4'
libtoolize: copying file `m4/ltversion.m4'
libtoolize: copying file `m4/lt~obsolete.m4'

駄目な場合(親にinstall.shがある場合)

mkdir -p /a/b/c/d
cd /a/b/c/d
touch /a/b/c/d/install.sh
wget http://217.20.164.161/~tipok/aacplus/libaacplus-2.0.2.tar.gz
tar zxvf libaacplus-2.0.2.tar.gz
cd libaacplus-2.0.2

libtoolize -c
libtoolize: putting macros in AC_CONFIG_MACRO_DIR, `m4'.
libtoolize: copying file `m4/libtool.m4'
libtoolize: copying file `m4/ltoptions.m4'
libtoolize: copying file `m4/ltsugar.m4'
libtoolize: copying file `m4/ltversion.m4'
libtoolize: copying file `m4/lt~obsolete.m4'

ltmain.shがコピーされませんでした。

親2階層分まで見るようなので、以下の構造の場合は c/install.sh, d/install.sh が存在すると駄目でした。

/a/b/c/d/libaacplus-2.0.2

こ、こんなん分かるわけないよ(;´Д`)
これにハマってかなり時間を取られたので忘れても大丈夫なようにメモメモ。

 

virshのsnapshotで木構造の根っこに作る方法がわからない

CentOSでは仮想環境としてKVM+qemuの環境が使えるのですが、スナップショットの使い方がいまいちわからない。

例えば、VMを普通に作り、Diskをqcow2で作ったとします。で、スナップショットの機能を使うわけですが、どうも思った通りの動作にならないのです。

スナップショットを作っていくと、以下のように木構造で作られていきます。以下はインストール直後の状態を installed という名前でスナップショットを取り、さらにその後 yum update した状態を yum-updated というスナップショット名で取りました。

んで、こういう感じでやっていくと木構造になっていくわけですが、木構造のrootとなるスナップショットの取り方がわかりません。以下を見ると、installed と test5 が根っこになっていますが、どうしてこうなったのか分からないのです・・・。なんか色々やっていたら作れたのですが、再現ができず(´・ω・`)

誰か知っていたら教えてください・・ orz
ホスト環境のOSはCentOS6.3でvirshは0.9.10です。

# virsh snapshot-list tamsvr43-video --tree
installed
 |
  +- yum-updated
      |
      +- test1
      +- test2
      +- test3
          |
          +- test4

test5
 |
  +- test6
      |
      +- test7
          |
          +- test8
              |
              +- test9
                  |
                  +- 1378473386
                  |   |
                  |   +- 1378473474
                  |
                  +- test12
                      |
                      +- test30

 

 

CentOS6.3にてffmpegをビルドした時の記録 その1

ffmpegをCentOS6.3でビルドした時のメモを残しておきます。

なお、指定したオプションは再配布可能な範囲での指定なので、自分でビルドする場合はもっとフルにオプション指定した方が良いと思います。例えば、--enable-nonfreeとか--enable-libfdk-aacとか。

※この記事は後のビルドのためのベースとなるもので、最終的にはsharedリンクを完全に無くし、指定できるオプションは可能な限り付けることを目標としています。

 

 

Continue reading

ffmpegで綺麗にエンコードできない(゜◇゜)

ffmpegで動画をエンコードしていたら、気になる事があったのでメモ。誰か解決策を知っていたら教えてください(TxT)

困っていること

frapsで撮影したとある動画をx264に変換すると酷いことになる。

before

after

beforeの動画はこちらです。1GB以上あるので注意。

変換コマンドは以下の通り

./ffmpeg -i test.avi -c:v libx264 -preset slow test.mp4

この動画は現バージョンのffmpegを通すと、以下のエラーが発生します。

Too long VLC (31) in init_vlc
Error building tree
Error decoding plane 2
Error while decoding stream #0:0: Operation not permitted9

このエラーはlibavcodec/huffman.hFF_HUFFMAN_BITSを増やす修正でFIXできます。なお、元々は9だったものが10に修正された経緯がありますが、この動画は12くらいにしないと駄目でした。なお、16にすると出力ファイルが異常に大きくなり、24にすると12と同じサイズになります。・・・なんで?(;´Д`)
というわけで、まずはこのエラーを回避するためにFF_HUFFMAN_BITS12に変更してビルドしました。

 

次に試したのは以下のコマンド。pix_fmtを指定しました。

./ffmpeg -i test.avi -c:v -pix_fmt rgb24 libx264 -preset slow test.mp4

多少マシになったものの、やっぱりおかしな感じになります。

ちなみに、エンコードする時にサイズを変更すると大丈夫なのです。

./ffmpeg -i test.avi -s 720x480 -c:v libx264 -preset slow test.mp4

ffmpegのバージョンはこんな感じ↓です。

# ./ffmpeg -version
ffmpeg version 2.0.1
built on Aug 30 2013 16:30:36 with gcc 4.4.7 (GCC) 20120313 (Red Hat 4.4.7-3)
configuration: --enable-gpl --enable-version3 --enable-avisynth --enable-fontconfig --enable-libass --enable-libbluray --enable-libcaca --enable-libcdio --enable-libfreetype --enable-libgsm --enable-libmodplug --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-librtmp --enable-libsoxr --enable-libspeex --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvo-aacenc --enable-libvo-amrwbenc --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libx264 --enable-libxvid --disable-outdev=sdl --disable-ffprobe --disable-ffserver --optflags='-O2 -finline-functions'
libavutil      52. 38.100 / 52. 38.100
libavcodec     55. 18.102 / 55. 18.102
libavformat    55. 12.100 / 55. 12.100
libavdevice    55.  3.100 / 55.  3.100
libavfilter     3. 79.101 /  3. 79.101
libswscale      2.  3.100 /  2.  3.100
libswresample   0. 17.102 /  0. 17.102
libpostproc    52.  3.100 / 52.  3.100

ffmpegのビルドの記事は別途かきます。  → 書きました

うーん・・。わからん(´・ω・`)

CDH4でOS再起動をすると実質的にプロセスの強制終了になる件について

英語でチケットを書く元気が今ないので、日本語でメモ。最終的には私が見つけたわけではないけど。

Cent6.3での話です。

今のCDH4系のinitスクリプトだと、OS再起動時や終了時にHadoop関連プロセス(NameNode, DataNode, JobTracker, TaskTracker, 2NN等)が正しく正常終了できないという・・。rebootとか叩いた日にはプロセスは正常終了しません・・。

Redhat系の場合、再起動はランレベルが6なわけで、例えばDataNodeの場合は以下のようになっています。

hadoop-hdfs-datanode    0:off   1:off   2:off   3:on    4:on    5:on    6:off

/etc/rc.d/rc を見ると以下となっています。 

# First, run the KILL scripts.
for i in /etc/rc$runlevel.d/K* ; do

        # Check if the subsystem is already up.
        subsys=${i#/etc/rc$runlevel.d/K??}
        [ -f /var/lock/subsys/$subsys -o -f /var/lock/subsys/$subsys.init ] || continue
        check_runlevel "$i" || continue

        # Bring the subsystem down.
        [ -n "$UPSTART" ] && initctl emit --quiet stopping JOB=$subsys
        $i stop
        [ -n "$UPSTART" ] && initctl emit --quiet stopped JOB=$subsys
done

/var/lock/subsys/hadoop-hdfs-datanode というロックファイルが作られていれば、OS停止時にプロセスの停止をしてくれます。(shutdown や reboot した時にズラーーーッと緑の文字で OK OK OKって出てきますね)

DataNodeの場合は、chkconfig --add した時に以下のファイルが作られます。 

/etc/rc.d/init.d/hadoop-hdfs-datanode
/etc/rc.d/rc0.d/K15hadoop-hdfs-datanode
/etc/rc.d/rc1.d/K15hadoop-hdfs-datanode
/etc/rc.d/rc2.d/K15hadoop-hdfs-datanode
/etc/rc.d/rc3.d/S85hadoop-hdfs-datanode
/etc/rc.d/rc4.d/S85hadoop-hdfs-datanode
/etc/rc.d/rc5.d/S85hadoop-hdfs-datanode
/etc/rc.d/rc6.d/K15hadoop-hdfs-datanode

これは /etc/rc.d/init.d/hadoop-hdfs-datanode のコメントに、以下の記述があるからです。

# Starts a Hadoop datanode
#
# chkconfig: 345 85 15
# description: Hadoop datanode
#
### BEGIN INIT INFO
# Provides:          hadoop-hdfs-datanode
# Short-Description: Hadoop datanode
# Default-Start:     3 4 5
# Default-Stop:      0 1 2 6
# Required-Start:    $syslog $remote_fs
# Required-Stop:     $syslog $remote_fs
# Should-Start:
# Should-Stop:
### END INIT INFO

こーいうコメントを拾って設定しているなんて知りませんでした・・。 

datanodeのinitスクリプト、肝心のLOCKファイルは・・ 

LOCKDIR="/var/lock/subsys"
LOCKFILE="$LOCKDIR/hadoop-datanode"
....
[ $RETVAL -eq $RETVAL_SUCCESS ] && touch $LOCKFILE

う、うん・・。ファイル名が違うね。これは hadoop-hdfs-datanode じゃないとダメだよね・・。直したらOS停止時にもきちんとSTOP処理が呼ばれるようになりました。
CDH4のプロセス全部に当てはまるので、注意が必要です。(ただし、HBaseとかは入れてないので見てない)

Fluentd Casual Talksに参加して意識がちょっと高まったのでArangoDBのPluginを書いてみた

なんかタイトルがラノベっぽい。

Fluentd Casual Talks #2 に参加してきたので、その感想でも。

-- 感想 --

JRの駅からヒカリエに行く方法がわからず、20分ほどうろうろして最終的に駅員さんに聞きました。くやしくなんかないもん。(震え声)

just_do_neet さんの運用の話は、とても良かったです。とりあえず入れてみて、それっぽく動いている。ハッピー。 というわけでもなく、きちんと検証したんだなーという感じが特に。駆け足だったので、ちゃんと追えていないけど・・(´・ω・`)

そしてangostura11さんがStreamに対してクエリを発行できるものを発表されていて、それってかなり便利なんじゃ。。でもJavaかぁーとか思ったり、Spring_MTさんの話でFluentdがテスト用のドライバを用意しているという事を知り、じゃぁテスト書くの楽なのかもとか思って、会場の後ろの方でpluginをこそこそと書き始めていました。

他の発表者の方も、短い時間でテンポ良くしゃべっていて、一部の内容は理解できなかったけど、タメになったなぁという感じです。

それはそうと、oza_x86さんのデスクトップ(?)にHADOOPのパッチが沢山置いてあったのが一番気になりました(;゜ロ゜)

-- 感想終わり --

--  次は Fluentd について思っていること  --

もうあちこちで言われているので、わざわざ書く必要もないかもですが、個人的にFluentdの良いところというのは、再送処理を現実的な範囲でそれなりに担保してくれるという事と、in_tailプラグインの存在だと思っています。

Webの例なんかはあちこちに出ているので、他の・・例えば400台くらいのゲームサーバがあったとして(昔CEDECで発表されていたの、これくらいでしたよね)、それのログはローカルDISKに書き込むか、専用のログサーバに転送するか、もしくはその両方を行うとかやると思います。ここまでかっちりと作ってあるものなら、まぁ別にそれなりに動きますが、時としてローカルDiskにのみログを出力しているものがあります。

ちなみに、(自分が知っている範囲で言うと)オンラインゲーム(ソーシャルゲームじゃないよ)のログというのは、ユーザー比率的にWebのログよりも圧倒的に多く、ちょっと追加しようものなら、ものすごい勢いでDiskを食いつぶしていきます。

話はそれましたが、まぁとにかくローカルDiskに書き込むケースが多いわけです。で、これを何かしらの方法で集約するか、もしくはその各ホスト上で直接分散検索をしたりするわけです。これで困る点はホストが壊れて代替機に振り替えた場合、そのホストにあるログが・・・ という問題があるわけです。集約するにしても、バッチ処理的に吸い上げるとスパイクな負荷がかかります。まぁこれもあちこちで言われている話なので言うまでもないですね。

で、in_tailの何がすばらしいかというと、既に作ってあるアプリケーションに対して何も変更を行うことなく、外部プロセスでStreamで他所の場所に集約できるという点です。 これ、かなり嬉しかったです。昨年のCEDECで国民的ゲームのデータベースの発表をした人も、in_tail イイね!と話した記憶があります。

そんなわけで、 in_tail を標準のpluginで入れているのは実は凄く素晴らしい事なんだと思っているわけです。
(当たり前すぎるのか言及している人を見かけない・・(´・ω・`))

-- Pluginを作ってみた話 --

話を聞いて、まぁ私もPluginを書いてみるかと思うわけですが、実際に書いてみると、コピペをしない場合は最終的にソースを見る羽目になるわけです。

例えば、テストを書くにしてもドキュメントを見ると、out_fileのテストを参考にしなって書いてあるし、何を継承するかとか、親クラスは何のパラメータを持っているのかとか、ソースを見ないと分からない(分かりづらい)わけで。こんなパラメータ合ったんだとか (例えば num_threads なんかは一部のTwitterと一部のBlogにちょろっと出てくるくらいです)、新しい発見があって、fluentdに限って言えば、pluginを1回書いた方が理解が深まるなと思いました。

その肝心のpluginの作り方ですが、pluginを○時間で書いたとかいう話をPlugin製作者が言ってますが、実際そーいうレベルの時間で書くことができました。outputプラグインに関しては、驚くほど抽象化されているので、やりたい事だけをペイッと書くだけです。めんどくさい処理はMixinで提供されいるので、楽ちんです。ただ、存在を見つけるのに時間がかかりました・・(´・ω・`) このドキュメントに載っていないので、複数のPluginのソースを参考にして、fluentdやpluginのソースを検索しまくりでした。

テストもドライバが用意されているので、TDDで書くことができます。

-- fluent-plugin-arango について --

そんなわけで、ArangoDBに書き込むためのpluginを書いてみました。ソースはこちらに、Gemはこちらにあります。 ArangoDBは最新版ではbulk_importを持っているのですが、rubyのclientであるashikawa-coreがまだサポートしていないので、1件ずつ書いています。
(そういえば、Collection勝手に作るから、wait_syncのパラメータが必要かも)

使い方はこんな感じです。

<match access.**>
  type arango
  collection ut_col
  scheme http
  host localhost
  port 8529
</match>

BufferedOutputを継承し、SetTimeKeyMixin, SetTagKeyMixinをincludeしているので、それらのパラメータも指定する事ができます。 詳しくはREADMEに記載しています。

実際にArangoDBのWebUIから確認をすると、データが入っています。

何かまずい点をみつけたら、ご指摘いただけると嬉しいです。

Fluentdに負荷をかけるクライアントを作った

Fluentdに負荷をかけたいなと思いまして・・。
前回作ったapache-loggenを使っても良いのですが、あれだと一度diskに出力する事になるし、Rubyなので複数コアがあるホストだと何プロセスも起動するのは嫌だなーと。

というわけで、今回はマルチプロセスで直接Fluentdにmsgpack形式のデータを投げるものを作ってみました。
in_forwardで受け付ける形式は何個かあるみたいですが、今回は以下の形式のものを。

[ tag, [time, rec], [time, rec], [time, rec], ... ]
rec = { ... }

実際の実行画面のキャプチャです。

 

なんか、ほとんどマスクしちゃってますね(´・ω・)
parallelでforkするので複数コアを活用します。情報はUNIXSocketで親プロセスへ集めて表示はCursesを使っています。それぞれの子プロセスの情報とそれぞれを集計した情報を出力しています。

灰色でマスクしているところには設定が表示されています。上記の画面では、毎秒10リクエスト、1リクエストで50レコード、つまり毎秒500レコード。それを4プロセスで送りつけているので、毎秒2000レコードの負荷をかけているものになります。全開でまわせば、L社の初期の流量くらいは出せると思います。

使い方はこんな感じ。

$ ruby load.rb --help
Usage: load [options]
        --host         負荷をかけるFluentdのホスト名。  デフォルトはlocalhost。
        --port         負荷をかけるFluentdのポート番号。デフォルトは24224。
        --rate=RATE    1秒間に送信するリクエスト数。デフォルトは制限無し。
        --num=NUM      1回のリクエストで送信するレコード数。デフォルトは1。
                       いわゆるArrayの要素数。
        --thnum=THNUM  負荷をかけるプロセスの数。デフォルトは1。
        --mode=MODE    ログの形式を指定する。これは会社依存のオプションなので詳細は無し。
        --fixed-time   ダミーログの時間を元ファイルの時間のまま送る場合に指定する。
                       指定しなければ現在の時間に直して送信される。
                       (これも会社依存のオプション)

ログを生成する部分は apache-loggen を使って、apacheのログではなく アプリの実際のログを元に生成しています。なので、このままでは公開はできないのですが、ここを除去すれば問題ないので気が向いたらやろうかなと・・。

気が向いたら・・。

 

SELinuxがSSHの公開鍵認証を邪魔する!

CentOS6.3でSSHの公開鍵認証、パーミッションの設定大丈夫なのになんでログインできないのかなーと思ったら、SELinuxが原因でした。

ぐぐると、SELinuxをoffにする設定が多いのですが、せっかくなのでSELinuxの設定をどうすれば良いのか試してみた。

setsebool allow_ssh_keysign=on
setsebool ssh_sysadm_login=on
restorecon -R -v /root/.ssh

rootの場合は、こんな感じで良いらしい。
上2行だけだとダメだったので3行目も追加(´・ω・`)

(追記) 3行目だけで良いらしいです。なので以下だけでできるっぽい。

restorecon -R -v /root/.ssh

SELinuxわからんぷい。
※SELinuxをdisっているわけではありません!

こちらに詳細があります http://2done.org/index.php?id=76

 

Hadoop関連のきゃわいいマスコット達

Hadoopアドベントカレンダー2012の15日目の記事を書かせていただきます。
HiveServerの事を書こうと思ったのですが、ゆるふわエントリに変更してHadoop関連のロゴについて書きたいと思います。

Hadoopのロゴはまぁお察しの通りキモイきゃわわな象さんなわけですが、ちょっとグッズを作ろうと思った事もありHadoop関連の正式ロゴとボツ案のロゴなどを集めましたので紹介します。

Hadoop

elephant_rgb.jpg

これが基本形で、以下のように各コンポーネント毎にサイト用のロゴがあります。
http://svn.apache.org/repos/asf/hadoop/logos/
https://issues.apache.org/jira/browse/HADOOP-7480

hadoop.png

smallCore+elephant_cmyk.gifcommon-logo.jpghdfs-logo.jpgmapreduce-logo.jpg

HadoopのPowered By

powered by用のものです。こちらはApacheの羽有りのものでちょっといろいろと制約があります。(詳しい事はわからないけど)
https://issues.apache.org/jira/browse/HADOOP-7020

pbh-64-logos.png

Hadoop Security

1枚目のやつはHairongさんの娘が描いたそうです。
https://issues.apache.org/jira/browse/HADOOP-6721

hadoop-helmet.jpghadoop-security-logo.jpg

JobTracker

JobTrackerにもロゴがあります。・・なんで?
https://issues.apache.org/jira/browse/MAPREDUCE-484

jobtracker.png

Hive

次はみんな大好きHiveのロゴです。Hiveのキモさ可愛さは一際目立っております。
https://issues.apache.org/jira/browse/HIVE-422
https://issues.apache.org/jira/browse/MAPREDUCE-484

hive.png

こちらはAIファイルがあります。

Pig

分かりづらいエラーメッセージに定評のあるPIGです。
https://issues.apache.org/jira/browse/PIG-155

080224_logo_pig_01_rgb.jpg

「This pig looks a bit too lazy!!! I want something hardcore!!!」(ゆるキャラすぎんだろ。もっと硬派なやつをキボンヌ)
「No it is not lazy, just relaxed, since it was so super fast done with all the map reduce development and calculation. Much faster than the elephant itself.」

とかいうやりとりが。いや、ゆるキャラでもねーだろとか思うんですが、海外のセンスはわからん。

そしてなぜかCLOSEされた後にこっそりと添付されてた。↓

pig.png

きゃわわ!!

まぁ最終的にコレなんですがね(^^;

pig_medium.png

ないわー(゜Д゜;)

ちなみに、こちらはラスタ画像しかないのでVectorイメージが必要ですってTicketがあります。
https://issues.apache.org/jira/browse/PIG-2323

Mahout

Mahout使えば簡単にできるよねとか言った人出てきなさい。先生怒らないけど椅子ぶつけます。
https://issues.apache.org/jira/browse/MAHOUT-57
https://issues.apache.org/jira/browse/MAHOUT-335

最初のロゴは以下のようです。

Mahout-logo-164x200.png

今はこっちですね。良いですね!!

mahout-logo-200.png

mahoutはSVGファイルがあります。

ZooKeeper

スケッチがあったので載せておきます。
https://issues.apache.org/jira/browse/ZOOKEEPER-69

zookeeper-sketch.jpg

これが、こうなりました。

zk_logo_use.pngzk_logo_use2.png

衣服のせいでもうウホッの人にしか見えない。

Chukwa

すいません。チケットみつけられませんでした。ロゴだけ載せておきます。

chukwa_logo_small.jpg

HBase

暴れん坊将軍と定評があるHBase。
この前のカンファレンスのOKWaveのHBase発表、かなり手なずけてましたよね・・。すごい。
https://issues.apache.org/jira/browse/HBASE-4312
https://issues.apache.org/jira/browse/HBASE-5115
https://issues.apache.org/jira/browse/HBASE-4920

01.jpg

hbase.jpg

マスコットはこちら。すっごいいっぱいあります。まだ決まってないのかな?

apache_logo_Proof_8.jpgapache_hbase_ orca_logo_Proof 3.jpgplus_orca.png

HBase_Orca _Logo.jpg

オルカ推しです(他にも沢山あるのですが、貼るのがめんどくさくなってきました・・)。

なぜかタコもありました。こちらは参考資料なのかな?

krake2l14x.jpgkrake-copy.jpgKrake-online-b.jpg

Hama

知ってる人、あまり居なそう。「a Bulk Synchronous Parallel computing framework on top of Hadoop」れっきとしたHadoop関連プロジェクトです。Hadoopの公式サイトからRelated Projectsのメニューにもきちんと書いてあります。
https://issues.apache.org/jira/browse/HAMA-8
https://issues.apache.org/jira/browse/HAMA-43

最初のドラフトです。レベル高い!!!

hama_draft_1.jpg

色が付いて現在のロゴになっています。クォリティ高い!!

hama_paint_logo.png

ちなみに、これはなんぞ?

hama_1.JPG

Giraph

Giraph。この前初めて聞いたって呟いたらつけ麺おじさんにDisられそうになっt
https://issues.apache.org/jira/browse/GIRAPH-4

これが今のロゴなのかな?

transparent-giraph-icon.png

こんなのもありました。

3rd Giraph Logo.pngGiraph Logo v3 copy.jpg

Hadoop-Ruby

どこ発祥なのかわかりません・・。Clouderaのページで見つけました。
http://blog.cloudera.com/blog/2011/01/map-reduce-with-ruby-using-apache-hadoop/

hadoop-ruby.png

その他

http://gigaom.com/cloud/my-hadoop-is-bigger-than-yours/

img-myhadoop-bigger4.jpg

「ワシの象さんは貴様のより大きいぞ!」 

シモネタじゃねーかっ!

 

Huahin

fig_logo_huahin_small.png

Impala

http://blog.father.gedow.net/2012/11/27/cloudera-impala-presentation/

ImpalaQL.png

嘘です。ごめんなさい。

HadoopとHiveに関してはベクターデータがあるのですが、他は見つかりませんでした。

AvroとCassandraは今回調べてません。BigtopもJIRAを検索してみましたが関連チケットはありませんでした。

あれ・・そーいえば ヒィーヒィーと汗かいている象がありませんね・・。あれってどこにあるのでしょう。。

ClouderaManagerに間違ったホストを登録した場合の対処法

ClouderaManagerで2つのバージョン(CDH3, CDH4)のクラスタを認識させようと作業していたのですが、FREE版ではマルチクラスタの管理はサポートしていないようです。

というわけで、ClouderaManagerを別々にインストールして、1クラスタ=1ClouderaManagerという感じにしようと思ったのですが、最初のManagerに認識させたホストを消せなくてちょっと四苦八苦してました。図にすると以下のような感じです。

最初の状態。

この状態から以下のようにしようとしてた。

でも、FREE版はマルチクラスタをサポートしていないっぽい。ので、以下のようにしようとして、Manager1からCDH4用に追加したHostB群を削除しようとした。

 

ClouderaManagerに登録したホストを削除する時は、以下の画面で削除したいホストにチェックを入れて「削除」をすれば良いのですが、すぐに復活してきます。以下の画面は赤枠のtamsvr5[1-5]vmが最初に入れたホスト(CDH3)、緑枠のtamsvr6[1-5]vmは違うCDHのバージョンを入れて別クラスタとして認識させようとしたホスト(CDH4)です。

消しても消しても復活してくるのは、各ホストで動いているClouderaManagerのAgentがHeartBeatを送っているからです。これを止めないと、別のホストでClouderaManagerを入れてtamsvr6[1-5]vmのホストを追加しても、ホストインスペクタが失敗します。
実際、tamsvr61vmにClouderaManagerを入れてtamsvr6[1-5]vmを追加しましたが、失敗しました。

なので以下の手順を行う必要があります。

1. 各ホストで動いているAgentを止める。

ssh root@tamsvr61vm /etc/rc.d/init.d/cloudera-scm-agent stop
ssh root@tamsvr62vm /etc/rc.d/init.d/cloudera-scm-agent stop
ssh root@tamsvr63vm /etc/rc.d/init.d/cloudera-scm-agent stop
ssh root@tamsvr64vm /etc/rc.d/init.d/cloudera-scm-agent stop
ssh root@tamsvr65vm /etc/rc.d/init.d/cloudera-scm-agent stop

2. ClouderaManager(上記の画面)から削除する。

これでひとまず消えます。ただし、再度Agentを動かすと復活してきます。

3. 新しいClouderaManagerからホストを追加する。

これで成功します。

きちんと別々に認識されました。

 

 

こんな事やってる人、誰も居ないと思いますが・・(´・ω・`)

 

RealtimeViewerを作ったらREADMEを書くのがとても捗る件について

READMEを書くときはMarkdown形式で書くと思うのですが、四六時中その記法で書いているわけでは無いので、なかなか覚えられないわけです。Rubyの場合はRDoc形式で書くのが普通なのかな?まぁWikiにしろ何にしろ似たような記法増えすぎだと思うのです。

わたしのようなゆとりには、プレビューしながら確認できるととても幸せになれます。とは言っても、ブラウザ上で入力なんてしたくないし、自分のお気に入りのエディタを使いたいわけです。

こちらの記事にあるツールを使っていたのですが、どうも自分の環境ではファイルを保存しなおすと、高確率でreadでこけるようで・・。

というわけで(上記ツールを参考にして)自分で作ってみました。

 

 

Continue reading

Apacheのサンプルログを生成し続けるスクリプトをRubyGemsに登録してみた

こちらの記事で書いた、Apacheのログを生成し続けるスクリプトをRubyGemsに登録してみました。

なので、今後は

gem install apache-loggen

とインストールするだけで、

apache-loggen

というコマンドが使えるようになります。使い方は今までと同じです。(以下のような感じです) 

apache-loggen [--rate=0] [--limit=0] [--rotate=0] [--progress] [--json] [outfile]
  --rate     毎秒何レコード生成するかの指定。0以下は制限無し。
  --limit    最大何件出力するか。0以下は延々と出力し続ける。
  --rotate   ローテーションの間隔。単位は秒指定。0以下はローテーションしない。
  --progress STDERRに経過情報を出力する
  --json     apacheのログ形式ではなくJSON形式で出力する

  outfile    指定するとファイルに出力します。省略するとSTDOUTに出力します。

毎秒10件をaccess.logに出力。60秒毎にローテーション。経過をSTDERRに表示。

apache-loggen --rate=10 --rotate=60 --progress access.log

また、この機構を使って独自のログ形式を出力できるようにしました。 以下のように使います。

require 'apache-loggen/base'
LogGenerator.generate(conf=nil, gen_obj=nil, &block)

confはnilを渡すとARGVの内容をパースします。プログラムで直接指定する場合は、Hashを渡す事でデフォルトの設定を上書きする事ができます。実際にログを出力する処理は、gen_objにLogGenerator::Baseを継承しgenerate(context, config)メソッドを定義したクラスのインスタンスを渡すか、ブロックを与えます。両方指定した場合は、gen_objが先に実行され、blockが評価されます。(blockではgen_objで出力したrecordを受け取る事ができます)

いくつかサンプルを置いておきます。

独自のログ形式を出力したい場合。

require 'apache-loggen/base'
class MyGen < LogGenerator::Base
  def generate(context, config)
    return "#{Time.now.to_s} #{context.inspect}\n"
  end
end
LogGenerator.generate(nil, MyGen.new)

もしくは

require 'apache-loggen/base'
LogGenerator.generate do | context, config, record |
  "#{Time.now.to_s} #{context.inspect}\n"
end

上記の2つは同じ処理です。

Apacheログに新しく情報を追加したい場合。

require 'apache-loggen/base'
class MyGen < LogGenerator::Apache
  # オリジナル実装はhashをJSONか1行の文字列にしているが
  # 今回はそれに情報を追加する
  def format(record, config)
    record["process_time"] = grand(1000000) + 1000
    if config[:json] then
      return record.to_json + "\n"
    else
      return %[#{record['host']} - #{record['user']} [#{Time.now.strftime('%d/%b/%Y:%H:%M:%S %z')}] "#{record['method']} #{record['path']} HTTP/1.1" #{record['code']} #{record['size']} "#{record['referer']}" "#{record['agent']}" #{record['process_time']}\n]
    end
  end
end
LogGenerator.generate(nil, MyGen.new)

LogGenerator::ApacheはLogGenerator::Baseを継承しており、generateの中でformatを呼び出しています。このformatの実装が、1行分のレコードをhashとしてうけとりJSONか1行の文字列に変換するという処理をしているので、これをOverrideするとApacheログの生成ロジックを使いつつ書式を変更する事ができます。

いくつかのサンプルはREADMEにも書いてあります。

(∩´∀`)∩

 

JavaのImageIOで読み込めないJPEGファイルがある

画像分類の学習モデルを作るツールをJavaで作っていたのですが、あるJPEGファイルをImageIOで読もうとすると以下のエラーが出ました。

Exception in thread "main" java.awt.color.CMMException: Invalid image format
    at sun.awt.color.CMM.checkStatus(CMM.java:131)
    at sun.awt.color.ICC_Transform.(ICC_Transform.java:89)
    at java.awt.image.ColorConvertOp.filter(ColorConvertOp.java:516)
    at com.sun.imageio.plugins.jpeg.JPEGImageReader.acceptPixels(JPEGImageReader.java:1169)
    at com.sun.imageio.plugins.jpeg.JPEGImageReader.readImage(Native Method)
    at com.sun.imageio.plugins.jpeg.JPEGImageReader.readInternal(JPEGImageReader.java:1137)
    at com.sun.imageio.plugins.jpeg.JPEGImageReader.read(JPEGImageReader.java:948)
    at javax.imageio.ImageIO.read(ImageIO.java:1422)
    at javax.imageio.ImageIO.read(ImageIO.java:1282)
    at Test.main(Test.java:31

JAIだと読み込めるというので、処理速度が落ちるけどそっちを使うようにしてみた。

BufferedImage img = null;
try {
    img = ImageIO.read(file);
} catch (java.awt.color.CMMException e) {
    img = JAI.create("fileload", file.getAbsolutePath()).getAsBufferedImage();
} catch (IOException e) {
    // ちゃんと処理しようね!
}

正しい使い方としては、JAIをきちんとセットアップしてImageIO経由で使えるようにし、ImageReaderを列挙して順番に試すというのが正統な方法なのかなと。今回は手抜き処理。

JAIだけでも良いんだけど、速度が・・。

参考サイト

 

Apacheのサンプルログを生成し続けるスクリプト

最新の記事はこちらです。

Fluentdのテストをする過程で欲しかったので、Apacheのログを生成し続けるスクリプトを書きました。

と言っても生成部分はTreasureDataのスクリプトを丸ぱくりで流量制限をするオプションを追加して延々と出力するようにしただけです。

frsyuki++。

手元でサクッとproductionで確認できる人はうらやましす。

使い方は以下のような感じです。詳細はREADMEを見てください。

ruby sample_apache_gen.rb [--rate=0] [--limit=0] [--rotate=0] [--progress] [--json] [outfile]
  --rate     毎秒何レコード生成するかの指定。0以下は制限無し。
  --limit    最大何件出力するか。0以下は延々と出力し続ける。
  --rotate   ローテーションの間隔。単位は秒指定。0以下はローテーションしない。
  --progress STDERRに経過情報を出力する
  --json     apacheのログ形式ではなくJSON形式で出力する

  outfile    指定するとファイルに出力します。省略するとSTDOUTに出力します。

ローテーションについては以下のルールでローテーションします。

  hoge.log --> hoge.[yyyy-MM-dd_HHmmss].log

例えば、こんな感じで使います。
毎秒10件をaccess.logに出力。60秒毎にローテーション。経過をSTDERRに表示。

ruby sample_apache_gen.rb --rate=10 --rotate=60 --progress access.log

普通はクラス化して、出力内容を決める部分をBlockで渡すようにして再利用性を高めると思うのですが、ごにょごにょ(´・ω・`)

ソースはここに置いておきます。
https://github.com/tamtam180/apache_log_gen 

これでWeb屋じゃなくてもそれっぽいログが出来るのでFluentdで遊べますね(∩´∀`)∩

FluentdをWindowsで動かそうとして挫折しなかった記録。その1

WindowsでFluentdを動かそうとして調べていった記録のメモです。
きちんとまとめていないので起こった順に書いています。

同じような事を試みて挫折した人が居たので、この意思を勝手に引き継いで3秒以上頑張ってみました。

概要としては、fluentdのRuby実装Python実装(fluenpy)、fluent-agent-liteのPerl実装F#実装を確認した事と、Windowsでのファイル処理の排他制御について分かったことをまとめています。Windowsのファイルについては別の記事でまとめる事にして、この記事では簡単に触れる程度にします。

Continue reading

Mecabのdefファイル調整メモ

Mecab/IPA辞書を使って記号を食わせると、記号(&とか)がサ変接続と認識されます。

$ echo "P&!G" |  mecab
P    名詞,固有名詞,組織,*,*,*,*
&!   名詞,サ変接続,*,*,*,*,*
G    名詞,固有名詞,組織,*,*,*,*
EOS

なんでこうなるかと言うと、char.defとunk.defの設定でそうなっているからです。
(ところで、unknownの略のunkってなんて読むんと良いんでしょうね。うんこ?(´・ω・`))

char.defでは以下のように記述されています。 SYMBOLのINVOKEが1になっているので、常にunknown wordとして扱われます。そしてGROUPが1なので、同じ種別でまとめられます。

SYMBOL         1 1 0
# ASCII
0x0021..0x002F SYMBOL
0x0030..0x0039 NUMERIC
0x003A..0x0040 SYMBOL
0x0041..0x005A ALPHA
0x005B..0x0060 SYMBOL
0x0061..0x007A ALPHA
0x007B..0x007E SYMBOL

unk.defでは以下のように記述されています。

SYMBOL,1283,1283,17585,名詞,サ変接続,*,*,*,*,*

このunk.defを以下のように書き換えます。

SYMBOL,1283,1283,17585,記号,一般,*,*,*,*,*

これで以下の出力に変わります。 

$ echo 'P&!G' | mecab
P   名詞,固有名詞,組織,*,*,*,*
&!  記号,一般,*,*,*,*,*
G   名詞,固有名詞,組織,*,*,*,*
EOS

一応、記号一般になりましたが。が、どうせなら半角ASCII文字は全部繋げてほしいなーとか思ったり、固有名詞・組織?えぇーとか思ったり、思わなかったりするわけです。というわけで、もうちょっといじくってみます。

char.defを以下のようにします。ASCIIという区分を作って細かく分類していた部分をならしてしまいます。 文字コード0x0021 - 0x007Eの連続文字は問答無用でひとくくりにしてASCIIとする設定です。

ASCII          1 1 0
# ASCII
#0x0021..0x002F SYMBOL
#0x0030..0x0039 NUMERIC
#0x003A..0x0040 SYMBOL
#0x0041..0x005A ALPHA
#0x005B..0x0060 SYMBOL
#0x0061..0x007A ALPHA
#0x007B..0x007E SYMBOL
0x0021..0x007E ASCII

unk.defに以下を追加します。 ひとくくりにしたASCIIを名詞・一般にする設定です。 コスト値は1,1と優先度が一番高くなるようにします。

ASCII,1,1,0,名詞,一般,*,*,*,*,*

再度実行してみます。(あ、辞書はビルドしなおす必要があります)

$ echo 'P&!G' |  ../../bin/mecab
P&!G    名詞,一般,*,*,*,*,*
EOS

$ echo 'xxx-yyy' |  ../../bin/mecab
xxx-yyy 名詞,一般,*,*,*,*,*
EOS

こんな感じで調整する事ができますという紹介でした。
詳細は作者様のページにありますので、そちらを参照するのが良いと思います。

HiveServerでファイルディスクリプタがリークする件について

@tagomorisさんがこんな事を言っていたので、調べてみました。

まずは、.pipeoutファイルについて。
結論から言うと、Hiveの不具合です。
((追記 2012/06/23 14:00)Hiveのプログラムの書き方は良くないんだけど、Leakはしていないと思われます.)

この.pipeoutファイルはSessionStateのstartメソッドでOpenされます。startメソッドはHiveServerの場合、クライアントがコネクションをはった時にコールされます。(HiveSourceCodeReadingの発表資料にもちょっとだけ書いてあります。)

.pipeoutファイルはHiveServerではpipeInという変数によって、BufferedReader -> FileReaderを通して参照されます。 ↓のような感じ。
pipeIn = new BufferedReader(new FileReader( .pipeoutファイル ))
後始末の処理として、pipeIn = null としています。これがNGです。closeしていません。 

GCによってcloseされないのか?と思うかもしれません。が、されません。
GCによってcloseされるのはFileInputStreamだけ(あとはFileOutputStream)です。
FileInputStreamはfinalizeでcloseする事を仕様として定めていますが、FileReaderは仕様として定めていません。FileReaderの親クラスであるInputStreamReader, Readerともに仕様にありません。
JDK6のソース(OracleJDK, OpenJDK)を見ても、finalize処理は実装されていません。
※ちょっと嘘書きました。末尾に追記しました。(FileReaderはコンストラクタでnew FileInputStreamしていますのでGCでcloseされます)

ちょっと横道にずれますが、.pipeoutファイルってdeleteOnExitしているんですよね。このファイルは毎回異なるファイル名で作成しているので、コネクションをはる度に解放されないオブジェクトが溜まっていきます(まぁメモリリークしているわけですが、定義的にメモリリークと言って良いのかわかんない)。 HiveServerのclean処理で.pipeoutファイルをdeleteしても、deleteOnExitのリスト(正確にはLinkedHashSet)からは消えないままです。

というか、openしたままdeleteできるかどうかってプラットフォーム依存だった気がしますが、もう直ってるんでしたっけ? BugID忘れた&調べる気ない(・x・)

次に.txtファイルについて。
これってLog4jで出力しているファイルの事なのかな?(デフォの設定だと、.logなので違うファイル?)
こっちはまだ調べていませんが、Log4jのFileAppenderの場合、ファイル名を設定する度に1つ前に開いていたFile(というかWriter)をcloseする処理が入っているところまでは確認済み。HiveのExevDriverで以下のように書いていて、これってFileAppenderのreset処理呼ばれるっけ・・。と疑問に思ったところで、こっちはいったん調査終了。

    System.setProperty(HiveConf.ConfVars.HIVEQUERYID.toString(), HiveConf.getVar(conf,
        HiveConf.ConfVars.HIVEQUERYID));
    LogManager.resetConfiguration();
    PropertyConfigurator.configure(hive_l4j);

たぶんこっちの事かな・・。 hive_job_log_(sessionId)_(乱数).txt
こっちの場合は、HiveHistoryというクラスが担当で、PrintWriterを介してファイルをOpenしており、close処理がfinalizeで呼ばれています。このHiveHistoryはSessionStateのstartメソッドでインスタンス化されます。

SessionStateはThreadLocalに保存されます。HiveHistoryはSessionStateが保持しています。HiveHistoryは様々な場所から参照されていますが、他のどこにも保持はしていません(インスタンス生成時にSessionStateを受け取っているので、一見循環参照か!? と思ったけど保持はしていないです。)。Thriftがどういうモデルなのか分からないですが、仮に接続毎にスレッドを生成しているのであれば、接続がきれた時にSessionStateがThreadLocalから消えて他のどこからも参照がない場合(これはまだ確認してない)、SessionStateはGCの対象になります。その時にHiveHistoryへの参照が全て無くなるので、GC対象となるはずです。

仮定が多くて調査になっていないですね(=w=;)
jmapとjhatでどういう状態になっているか確認すれば良いだけなのですけどね。

お腹すいたのでいったん調査終了(・ω・)

※(追記)
当たり前の事なのでわざわざ書いてませんでしたが、一応捕捉しておきます。
(少なくともJavaでは)openしたものはきちんとcloseすることを明示すべきですし、
別にGCまかせにすることを推奨とも良しともしていないです。(そんな事一言も書いてないですよね(>ω<))
原因となりうるかと、それを良しとするかは軸が違うと思うのです。

(さらに追記)

FindBugsで解析をすると分かるらしいです。
今度試してみます。

ふむふむ。tagomorisさんのtweetによると、CDH3u2との事なので丁度ぶちあたってしまうのですね。
あと、確認したソースのバージョンを明記してなかったです。trunkとCDHu4で見てました。

(追記)2012/06/23 13:00

FileReader(File)はコンストラクタでsuper(new FileInputStream(file)) としていました。
という事で、GC走ればcloseされます。 
簡単な確認テスト: https://gist.github.com/2976972 

とあるサービスの技術資料まとめ の目次

せっかくなのでとあるサービスの技術的な事をまとめていこうかなと思ってます。
(作り直しているはずだし、消えるものなら片鱗を残しておこうかなと)

抜け漏れがまだまだあるので、思い出したら随時こっそりと更新していきます。画像も多くなるし、BlogじゃなくてGoogleDocとかのが良いのかな(自分のWikiにまとめる事にしました)。こういう内容は書かないの?とか、このサービス使って書くと良いよとかあったら教えてください。お願いします。
基本的にまったり進行。公開が遅かったらお尻をペシペシと叩いてください。

部署やプロジェクトを兼任していたので、やった事はこれ以外にも沢山ありますが、そのうちの1つとして記録を残します。併せて、なぜそうしたのかという所も覚えている範囲で書いていきます。また、数年前に開発が始まったものですので、今だったらこうするなという事も。
ぼかして書くので抽象的になるかも。
こういう事を去年のCEDECに公募してみようと思ったのですが、作業の遅い自分は間に合いませんでした。

なお、以下の内容はほぼ一人でやった事になります(´;ω;`)ウッ・・

書かないこと

  • 数字に関するもの
  • 内部的な作業フローなど
  • 日々の運用作業について
  • 課金やロビーサーバ、ゲームサーバ、パッチなどの話
    Webから外れるところもばんばん書くことはできますが、このBlogを見ている人はMMOGに興味なさそうだし。興味があれば直接会ったときにでも聞いてください。(言えない事は話しませんけど)
  • 所謂ビジネスロジックと言われるところ
  • 毒・悪口

目次

  • 開発環境について
    • プログラマとプログラマ以外の作業環境
    • レポジトリについて
    • 構成図
    • 継続的インテグレーションについて
    • 開発系のツールについて
    • 通知の話
  • 環境の種類
    • 所謂ステージングとかプロダクション環境とか。全部で5種類。
    • データの共有など。
  • サーバ構成について
  • ミドルウェアについて
    • ミドルウェア一覧と採用した理由など
  • コンポーネントについて
    • HTTPD, APS, Cache, KVS, RPC, Scheduler, MQ, Solr, RDB, API, Watch, ...
  • アプリケーションについて
    • 設計について
    • フレームワークについて
      • どういう構成になっていて、どういうフローで実行されていくか。
    • 機能
      • フレームワークとしての機能
        • 各種ユーティリティ系クラス
        • 多言語、共通基盤、
        • その他
          • BBCodeの実装
          • キーワードハイライタ
      • アプリケーションの機能
        • アカウント(ID/課金、キャラクタ周り)
        • マイページ、通知、日記、グループ機能、フォーラム、画像、イイネ、タグ、フォロー、アクティビティ、プレイログ、検索(4種類)、ランキング
        • バナーやメニュー制御については書かない
      • ニュース・トピックスの話
      • ゲーム連動
      • デバッグの機能
    • テンプレートエンジン
      • 機能の紹介
      • 2,30個以上はあるので他所のエンジンにないものを重点的に。
  • キャラクタのアバター画像について
    • 仕組みについて
    • 夏にやった企画について
  • 検索について
    • 構成の話
    • 拡張した機能について
    • その他
  • i18N対応について
    • ロケールについて
    • リージョンについて
    • タイムゾーンについて
    • アプリ本体
      • 3(4)リージョン
      • 5(6,7)言語
      • Viewレイヤーの話
    • ツール
      • テキスト管理のツールについて
      • 変換ツール
  • JavaScriptについて
  • CDNについて
  • ログについて
    • ログの種類
    • トレースログについて
    • ユーザーログについて
    • その他
  • ログの解析について
  • 監視について
    • 監視の仕組み
    • 通知について
  • RPCサーバについて
    • プロジェクト間共有の仕様でトランスポート層とかの仕様まできっちり決めてあるが、細かい事は言えないので概要。
    • プロトコルの概要
    • 実装について
  • APIサーバについて
  • IRCDの活用について
  • 管理ツールについて
    • 運用をするためのツール
    • モデレーション
  • 運用ツールについて
    • FTPを使わない話
    • NFSを使わない話
    • 作ったツールの話
  • デプロイの話
  • メンテナンスについて
  • 各種障害の内容について(書ける範囲で)

むかしむかし、とある会社で3日で作ったツールについて

むかしむかし、とある会社で3日で作ったツールについてのお話。

昔の社内勉強会用の発表資料が出てきたので、せっかくなので晒してみようと思います。(健全な男子が喜ぶ系のシステムの資料も出てきたのですが、これはまたの機会に・・・)
3年くらい前のもの(ソースみたら2009年でした)なので、そのあたりを考慮していただけると助かります。

なお、Web系の企業では動画とか全然だと思いますが、コンシューマ系の開発だと動画でやりとりできると便利ですよね。

さすがに資料をそのまま載せるわけにはいかないので、所々塗りつぶして、末尾にページを何枚か追加しました。
そういえば、CEDECである人が発表でちょこっと触れたみたいで、4gamerにもスライドが載っていたのですが、名前伏せられてるし(ツール名にプロジェクト名が入っていたので広報チェックでNGになったんだと思います)、モザイクかかってるしでちょっと悲しかった記憶を思い出しました。

(追記 2012/06/28 10:00)
・資料中のRAIDの説明がなんかおかしいです(RAID""二重化)。二重化+パリティのRAIDでNetAppだったらおそらくRAID4の事だと思いますが、詳細は私にはわかりません。
・資料の31ページが見れないという事でしたが、今見たら見れるようになってました。うーん・・?

ツールの概要を一言で言うと、ニコニコ動画のぱくりです。
プロトタイプを1日で、その後2日で作り直したものです。その後細かい修正はちょこちょこ入れてましたが、基本的には放置運用でした。(ごめんなさい・・)

本来の業務を放り出して、これのために3日みっちり作業しました。
一番時間がかかったのは、デザインをニコニコ動画っぽくする事とメンテ画面のトロがナイスボートって言ってる画像を探す時間です。単体テストのコードがほとんどありませんし、正直ソースコードはかなりひどいです。
いくつかのプロジェクトから、欲しいクレクレと来ましたがソースコードを見て絶望したと思います。

作った理由は、スライドにもありますが、
・単純にそこらへんに動画を置かれるとファイルサーバが死んでしまう事
それと、
・ローカルで動画をエンコードするとその間他の作業が出来なくなってしまう
・その動画、他の人全員が見れる形式とは限らない&codecによってはライセンスの購入が必要
という事です。

当時の心境を思い出すと、現実逃避も含まれていたと思います。(煮詰まっているときに他の作業すると楽しいですよね:p)

データセンターに置かなかった理由は、
・ネットワーク帯域がやばい事になる
・データセンターに置く機材の話とか発注とか予算とか
それを正攻法で解決しようとすると、いろんな人といろんな調整をしないといけなくなり、それには膨大な時間がかかります。(これは組織の問題かもしれませんけどね)
余ってるPCなら稟議とか要らないし、ソフマップでHDD買えば普通に経費で落とせるし。
※こんなやり方、真似しちゃダメですよ。いろんな人のヘイトも上がるし、後々面倒な事になります。(当時の状況として大人の事情ってやつがいっぱいありまして.. アルヨネ?そーいうこと。)

MP4をコンテナにしたのは、その当時FlashでMP4/H264を再生できるようになったからです。みんなが見れる環境(つまりブラウザで再生できるもの)であれば何でも良かったのですが、H264画質がすばらしいですし。(画質にうるさい人が多いというわけではなく、画質が悪いと動画中の文字がつぶれてしまう)

一番問い合わせが多かったのが動画がエンコードできないというもの。
みんなFrapsで撮影しているものかとてっきり・・。

アップロード部分をFlashにしているのは、アップロードの進捗状況を分かりやすくするため(アップするファイルが未圧縮なので巨大ですし)と、ブラウザのアップロード2GBの制限(これはブラウザによりけり)を避けるためです。
それなのに、Apacheのビルドをミスって2GB以上アップロードするとContentTypeがオーバーフローして、アップロード出来ない状態が残念です・・。(ajp_proxyもtomcatも確認しましたが、apacheのレイヤーでNGになってました。)

というわけで、いくつか画面をキャプチャしました。私の家で動かしたものになりますので、サンプル動画として私のコレクションをアップしています。当たり前の事ですが会社でこういう動画を共有していたわけではありません。(と、当たり前の事を書いておかないと時々勘違いをした人が騒いだりするので)

TOPページ。
社内(プロジェクト)共通のLDAPがあったので、それを使ってユーザー識別してました。

アップロード画面。
アップロードするファイルのサイズが大きいので、簡単なプログレスを付けてあります。解像度は縮小無し~HD、SD等のサイズを選択できます。有効期限は無制限、1年などなど。これはエンコードした動画を削除する期限ではなく、オリジナルの期限です。基本的にエンコードした動画は削除しない方針でした。

アップロード完了
すると、こんな感じの画面になります。

アップ後
動画をアップすると、キューもどきに入ってサムネイル生成待ちの状態になります。

サムネイル生成
エンコードサーバで動いているデーモンがキューもどきを見ていて、最初にサムネイルを作ります。マウスカーソルを合わせるとパラパラと表示されます。海外の動画サイトに良くあるアレです。

エンコード
サムネイルの生成が終わったら動画のエンコードを開始します。
エンコードの状況をヘッダで表示できます。今どれくらいまで変換しているのか確認する事ができます。
なお、失敗しても(しなくても)リテイクしたい場合はフラグを立てるだけです。

動画の再生ページ。
全画面表示とかもできます。右上のアイコンからメタ情報の編集とか、ダウンロード(MP4 or オリジナル)ができます。

動画の編集画面。
まぁだからなんだって話ですが。。。スライドにも書きましたが、ブラケットで括るとタグとして認識します。

検索画面。
タグを押すとこの画面に飛びます。画像だと1件しかなくて見栄えがあれですね。

簡単なログ画面。
何が起こっているのか知りたい人向け。

RSSは、サイト全体のもの、ユーザー毎や動画毎にあるので、自分が投稿した動画にコメントがついたみたいなものが取れます(たぶん、つけた気がする)

何かを導入したり、使って欲しいものがある場合、一番手っ取り早い方法は本来の趣旨を主張するのではなく、メリットを主張するのが良いと思っています。例えば、Subversionのようなものを使って欲しい場合に、リソースを集中管理して履歴が追えて~とかじゃなくて、レポジトリに入れる事で裏で自動でリソースを変換します、HTMLファイルだったら文法チェックしてくれます、誰が何をやっているのかわかるようになります。とか。このツールもHDDが~とかじゃなくて、ローカルで変換しなくてもアップロードするだけで、あとは良い感じに全部やってくれます。とかとか。
まぁ管理者の幸せとユーザーの幸せはまったく異なる事がほとんどですので、それを押しつけても説得なんてできませんよね。

ツールを作るときは、他のシステムとも連携できるようにAPIを用意したり、何かのプロダクトにべったり依存しないようにするのが理想だと思いますが、当時の私はまだまだ未熟でそこまで気配り出来ませんでした。今なら似たようなプロダクトがあると思うのでそれを使うのが良いと思います。

世の中、他社には面白いツールがいっぱいあるんだろうなぁとイロイロ気になります。
こっそり教えてください(・ω・)

GrowthForecastが便利な件について

定期的に処理を実行するものがあって、その結果や中間結果を可視化したいなーと思うときがあるわけです。表示したい値は簡単に出せますが、それを見やすい形で出すのってけっこう面倒なものです。ログファイルをgrepしろやーはちょっと嫌です。グラフ表示する場合はCactiあたりが有名だと思いますが、グラフの追加に手間がかかるのがツライのです。とりあえずサクッと表示したいのです。

噂によるとkazeburoさんのGrowthForecastというものが便利という事なのでさっそく試してみました。

今回cpanmも初めて入れました。これも便利です。

まずはcpanmのインストール。Fedora17くらいだとyumにあります。CentOSだと無かったので手動で入れます。

cd ~/
mkdir bin && cd bin
curl -LOk http://xrl.us/cpanm
chmod +x cpanm

環境変数の設定。みんなのBLOGを見ると~/perl5を指定するのがお作法みたいですが、home直下にlsで見えるディレクトリが増えるの嫌だったので適当に変更。

export PERL_CPANM_OPT="--local-lib=/home/libido/.perl5_extlib"
export PERL5LIB=/home/libido/.perl5_extlib/lib/perl5:$PERL5LIB
export PATH="/home/libido/.perl5_extlib/bin:$PATH";

RRDToolのインストール

wget http://search.cpan.org/CPAN/authors/id/G/GF/GFUJI/Alien-RRDtool-0.03.tar.gz
cpanm Alien-RRDtool-0.03.tar.gz

GrowthForecastのインストール

git clone git://github.com/kazeburo/GrowthForecast.git
cd GrowthForecast
cpanm --installdeps .

GrowthForecastの起動

# ForeGroundで起動
perl ./growthforecast.pl
# BackGroundで起動する場合はこんな感じ?
perl ./growthforecast.pl > /home/libido/log/growthforecast/gf.log 2>&1 &

後は以下のようにHTTPのPOSTで値を送りつけます。実際はURLのservice_nameとsection_nameとgraph_nameの部分をそれっぽい名前で指定してあげます。
これだけです。
事前にグラフの設定とかは要りません。POSTすると勝手に作ってくれます。
パラメータのmodeは、count, gauge(省略するとこれになる), modifiedを指定できます。numberが送りつける実際の値です。

$ curl -X POST -F number=10 -F mode=count "http://tamsvr03:5125/api/service_name/section_name/graph_name"
{
  "data": {
    "adjust": "*",
    "adjustval": "1",
    "color": "#9933cc",
    "created_at": "2012/06/05 17:06:10",
    "description": "",
    "gmode": "gauge",
    "graph_name": "graph_name",
    "id": 31,
    "llimit": -1000000000,
    "md5": "c16a5320fa475530d9583c34fd356ef5",
    "meta": "",
    "mode": "count",
    "number": 10,
    "section_name": "section_name",
    "service_name": "service_name",
    "sllimit": -100000,
    "sort": 0,
    "stype": "AREA",
    "sulimit": 100000,
    "type": "AREA",
    "ulimit": 1000000000,
    "unit": "",
    "updated_at": "2012/06/05 17:06:10"
  },
  "error": 0
}

これでブラウザから見えるようになります。↓こんな感じ。クローラーのログを可視化してみました。一部ぼかしてあります。

綺麗な構成としては、ログファイルをFluentで~みたいになるのかもしれませんが、今回はプログラムが小さかったのでクローラーが定期的にGrowthForecastに直接投げるようにしました。

APIの注意点としては、numberは整数じゃないとダメです。floatはNG。
ちなみに、時間を指定(後から集計して○時のデータはこれだぜーみたいな)してグラフを描画したい場合は、HRForecast というものがあって、そっちを使うと良いみたいです。

インストールも、使い方も簡単なので、とりあえずさくっと見えるようにする場合にすごく使えるツールです。

参考にしたページ
http://blog.nomadscafe.jp/2011/12/growthforecast.html
http://d.hatena.ne.jp/oranie/20120517/1337225732
http://www.omakase.org/perl/cpanm.html

RubyのURIがホスト名にアンダースコアがあるとparseできない件

Ruby(1.9.3)のURI.parseはホスト名にアンダースコアが入っているとURI::InvalidURIErrorが発生します。

require 'uri'
p URI.parse('http://hoge_fuga.example.com/page?id=xxx#yyy')
`initialize': the scheme http does not accept registry part: hoge_fuga.example.com (or bad hostname?) (URI::InvalidURIError)

仕様として、URIについては、RFC-2396とRFC-3986, IRIとしてRFC-3987があります。
アンダースコアはRFC-2396ではNGだったけど、RFC-3986ではOKになっているのですが、どうやらRubyのURIライブラリの対応が追いついていない(?)ようです。ちょっとRuby界隈には疎いのでどういう状況なのかは正確には把握していません。

代替案を探していたら、addressableというライブラリが対応しているという事なので、さっそく使ってみました。

さくっとgemでインストールします。addressableのバージョンは2.2.8でした。

gem install addressable

簡単な確認。

require "addressable/uri"
uri = Addressable::URI.parse("http://hoge_fuga.example.com/page?id=xxx#yyy")
p uri.host
p uri.port
p uri.request_uri
"hoge_fuga.example.com"
nil
"/page?id=xxx"

パースできました。
ただ、標準のURIだと上記の場合でもportは80を返してくれるのですが、addressableは返してくれないみたいです。しょんぼり。

参考:
http://d.hatena.ne.jp/kitamomonga/20100316/ruby_gem_addressable_howto

 

Hadoop SourceCode Reading 9で発表しました

先週熱が39.3度まで上がって、夜と朝方に未だに咳が止まらない虚弱なタムタムです。

5/30にHadoop Source CodeReading#9がありまして、「Introduce to Hive undocumented feature」というタイトルで発表させていただきました。

完全にネタセッションになってしまいました。
とはいえ、それなりにHiveは使っていたつもりなので何か困ったことがありましたらご相談ください。

HTTPのサーバは、いったん書き直してそのうちGitHubに公開するようにします。

質問があったtimestampの話ですが、
使わない方が良い理由として、
・Timezoneの扱いがちょっと微妙
・UDFのfrom_utc_timestmap, to_utc_timestampがバグってるってレベルじゃない
・一部の処理でメタ情報が正しく扱えない(JDBCからMeta情報がStringになってしまう等)
あたりがあります。

ユースケース的に上記を回避できるのであれば使っても問題ないと思います。
気がついたものはパッチを投げてありますが、コミュニティ純正の0.8, 0.8.1, 0.9, 0.9.1ではひとまず避けた方が良いです。CDH4ではHive0.8系ベースにいくつかのパッチを当てたものを提供してくれるので、Clouderaさんに期待&素直にCDH使った方が良いと思います。

発表スライドは日本語です。英語じゃないです。ごめんなしあ。。
自己紹介の部分と、見せられないよのページは抜いて、ご指摘があった部分は一部資料を修正してあります。
Slideshareにアップしました。

ここから感想

Hive Tools in NHN Japan (@tagomoris)
NHNでHiveをどういう構成、用途で使っているかのお話。Hiveは集計に特化して使っている感じで、そのためのフロントエンドツールは自作している。GitHubにもそのツールは公開されていて、やっぱりどこも同じようなものを作るよねと思いました。(LD界隈のツールのクォリティが高くて恐れおののきます・・)
ゲームのようにキャンペーンを頻繁にうつサービスは別ですが、そうでないサービスの内容が安定している場合、アドホッククエリを流す事ってほとんどないし一度流したものは定常的に流す事が多いので、そういう意味では理にかなっているツールだなと思いました。

Hive Source Code Reading (@wyukawa)
Hiveのソースコードを読んで、最適化まわりについてのお話です。Hiveの内部処理がどうなっているかの説明といくつかの最適化のポイントの解説でした。結論から言うと、Hiveでの最適化はあまりする必要がないという感じ。というのも、HiveのM/R処理は実はMapReduceデザインパターンに書いてあるような事はだいたい実装されていて、それなりに最適なM/R処理を行うようになっています。逆を言えばHiveで処理できるような単純な集計はM/Rを直接書かないでHiveで処理させた方が効率が良い場合が多いです。
個人的には、Explainを流してStage数がいくつになるのかを確認する程度です。

あとがき
勉強会を主催するということ、会場を提供するということは、とても大変な事だと思います。
自分だったら同じように出来ないです。
@shiumachiさん、@hamakenさん をはじめ、素敵な発表をしていただいた@tagomorisさん, @wyukawaさん、そして会場の準備をしていただいた方々にとても感謝しています。
(挨拶回りしなくてごめんなさい。対面で咳こんじゃうの嫌だったので...)

自分の発表は、内容、スライド、発表の仕方、もろもろ反省点が多いのでみんなの内容を参考にお勉強しますです。

AvocadoDBの概要説明とJavaからの扱い方

今回はAvocadoDBの簡単な概念とJavaからの扱い方を説明します。そういえばAvocadoDBはmrubyの人柱に名乗りを上げています。mrubyを使ってみる理由は彼らのBlogに書いてあります。前回の記事でマルチスレッドですと書きましたが、V8をマルチスレッドで扱うのめんどくね?とちょっと思っていたのですが、彼らも思っていたようですw またAvocadoDBのQueryLanguageはRFC化を目指しているようです。 とっても意欲的ですね。

※Version0.3.12を元に記載していますので、今後変わる可能性があります。

さて、まずはAvocadoDBの用語説明をします。全部は解説しません。一部だけです。

用語 説明
Collection RDBで言うところのテーブルです
Collection Identifier コレクションを識別するための内部ID(整数型です)
Collection Name コレクションを識別するための名前です(文字列です)。Collection Identifierと内部的に1:1に対応しています。命名規則は先頭が英字で他は英数かアンダーバー、ハイフンで構成されます。ハイフンを除けば常識的なプログラミング言語の変数名の規則と同じです。
Document RDBで言うところのレコードです
Document Identifier Documentを識別するための内部ID(整数型です)。これを直接使うことはほとんどありません。↓のDocumentHandleを通常は使います。
Document Handle Documentを識別するためのHandle名(文字列です)。書式はCollectionID + "/" + DocumentIDとなります。
Document Revision MVCCのMV(Multi Version)のバージョン番号にあたるものです。整数型です。これとDocument Etagを併せて説明しないといけないのですが、今回は省略します・・。

HTTP-RESTのInterfaceではCollectionNameもCollectionIDも両方サポートしています。またDocumentの実際の値はJSONになります。JSONObjectでも、JsonArrayでも大丈夫ですし、AvocadoDBはKVSでもあるのでJSONの要素(文字列、整数、Boolean、NULL)も受け付けます。

ちょっとだけGraphDBとしてのAvocadoDBも説明します。
Graph構造はVertexとEdgeで構成されます。VertexはDocumentに相当します。Edgeは専用で作るAPIが存在します。有向グラフか無向グラフ(要はエッジの向きがあるか無いか)はEdgeを作るときには指定できません。基本は有効グラフとしてEdgeを作ります。Edgeを取得する時にany, in, outを指定して取ります。VertexはDocumentなので任意の属性を保存する事ができ、Edgeも同様に任意の属性を保存する事ができます。 

Indexの種類は今は3つあります。Hash Index, Geo Index, Skip Listsです。(B-treeが無い理由はVideo見ればわかったりするのかな?)。複合インデックスに出来ますが、Geo Indexは要素は2つまでの制約があります。Indexはユニーク制約を付けることができます。HashとSkipListsの特性の違いの説明は必要ありませんよね。

CollectionやDocumentを作るときに、Diskへの書き込みを同期化するかのオプションがあります。InnoDBのFlushパラメータみたいなのですが、これは基本的にAsyncにしないと性能が出ません。(※性能テストはRoadMapの1.0betaで行う事になっているので、今の段階で性能云々を言うのはちょっとお門違いです)

さて、次はJavaで扱う方法です。Javaのドライバはこちらにあります。JARファイル作っていません。作った方がいいかな?
※prototypeですので、サーバが落ちている時のハンドリングを入れていません。

今できる事は、HTTP-RESTを叩いてCollection, Document, Cursor, Index, Edgeの操作ができます。

使い方はREADMEかExampleに書いてあります。単体テストを見るのが良いと思います。
以下はDocumentを1000個適当に作ってクエリで検索するサンプルです。Cursorを扱いやすくするためにResultSetを作ってみました。

public class Example1 {

  public static class ExampleEntity {
    public String name;
    public String gender;
    public int age;
  }

  public static void main(String[] args) {

    AvocadoConfigure configure = new AvocadoConfigure();
    AvocadoDriver driver = new AvocadoDriver(configure);

    try {
      for (int i = 0; i < 1000; i++) {
        ExampleEntity value = new ExampleEntity();
        value.name = "TestUser" + i;
        switch (i % 3) {
        case 0: value.gender = "MAN"; break;
        case 1: value.gender = "WOMAN"; break;
        case 2: value.gender = "OTHER"; break;
        }
        value.age = (int) (Math.random() * 100) + 10;
        driver.createDocument("example_collection1", value, true, null, null);
      }

      HashMap<String, Object> bindVars = new HashMap<String, Object>();
      bindVars.put("gender", "WOMAN");

      CursorResultSet<ExampleEntity> rs = driver.executeQueryWithResultSet(
          "select t from example_collection1 t where t.age >= 20 && t.age < 30 && t.gender == @gender@",
          bindVars, ExampleEntity.class, true, 10);

      System.out.println(rs.getTotalCount());
      for (ExampleEntity obj: rs) {
        System.out.printf("  %15s(%5s): %d%n", obj.name, obj.gender, obj.age);
      }

    } catch (AvocadoException e) {
      e.printStackTrace();
    } finally {
      driver.shutdown();
    }

  }

}

グラフも扱えます。 グラフの扱い方は以下のようになります。ただし効果的にトラバーサルする方法がまだ提供されていません。TinkerPopのBluePrintsとGremlinに対応するらしいので、そのあたりで出来ることは将来的にできるようになると思います。(私はGremlinでGraphを操作した事がないので詳細はわかりません。)
以下のサンプルは、Vertexを10個作って、エッジを3つ(e1=v0->v1, e2=v0->v2, e3=v2->v3)作り、v0のエッジを求めています。 

public class Example2 {

  public static class TestEdgeAttribute {
    public String a;
    public int b;
    public TestEdgeAttribute(){}
    public TestEdgeAttribute(String a, int b) {
      this.a = a;
      this.b = b;
    }
  }
  public static class TestVertex {
    public String name;
  }

  public static void main(String[] args) {

    AvocadoConfigure configure = new AvocadoConfigure();
    AvocadoDriver driver = new AvocadoDriver(configure);

    final String collectionName = "example";
    try {

      // CreateVertex
      ArrayList<DocumentEntity<TestVertex>> docs = new ArrayList<DocumentEntity<TestVertex>>();
      for (int i = 0; i < 10; i++) {
        TestVertex value = new TestVertex();
        value.name = "vvv" + i;
        DocumentEntity<TestVertex> doc = driver.createDocument(collectionName, value, true, false, null);
        docs.add(doc);
      }

      // 0 -> 1
      // 0 -> 2
      // 2 -> 3

      EdgeEntity<TestEdgeAttribute> edge1 = driver.createEdge(
          collectionName, docs.get(0).getDocumentHandle(), docs.get(1).getDocumentHandle(),
          new TestEdgeAttribute("edge1", 100));

      EdgeEntity<TestEdgeAttribute> edge2 = driver.createEdge(
          collectionName, docs.get(0).getDocumentHandle(), docs.get(2).getDocumentHandle(),
          new TestEdgeAttribute("edge2", 200));

      EdgeEntity<TestEdgeAttribute> edge3 = driver.createEdge(
          collectionName, docs.get(2).getDocumentHandle(), docs.get(3).getDocumentHandle(),
          new TestEdgeAttribute("edge3", 300));

      EdgesEntity<TestEdgeAttribute> edges = driver.getEdges(collectionName, docs.get(0).getDocumentHandle(), Direction.ANY, TestEdgeAttribute.class);
      System.out.println(edges.size());
      System.out.println(edges.get(0).getAttributes().a);
      System.out.println(edges.get(1).getAttributes().a);

    } catch (AvocadoException e) {
      e.printStackTrace();
    } finally {
      driver.shutdown();
    }

  }

}

Hiveのパッチ投稿のやりかた

こんにちは、ポテトサラダを極めようと心に決めたタムタムです。
(昨日作って食べたら満足しました(・w・))

今日はHiveのパッチの書き方をまとめます。
Hadoop本体とはちょっと作法が違います。Hadoop本体はパッチを投げると自動でJenkinsがテストしてくれたりするらしいのですが、Hiveはそんなことやってくれません。またパッチもJIRAに添付するだけではダメで、Phabricator(Reviewボードみたいなもの)というツールを使ってDiffをアップしてReviewしてもらう必要があります。

今回は実際にHIVE-2703のパッチを投げるまでの手順を書いていきます。
http://mt.orz.at/archives/2012/02/hivejira.html こちらの内容と被っていますが、こっちでは説明しきれなかったところも書いていきます。
なお、手順はsubversionの手順になっています。ぶっちゃけgitの方をオススメします。subversionだと複数のパッチを書くときにローカルでブランチを切れないのでとってもとってもつらいです・・。(私がsubversionを使っているのは、gitだとphabricatorのarcコマンドがうまく動作せずにarc diff --jira HIVE-xxxでパッチをあげることができなかったからです)

なお私はFedora16をVirtualBoxの上で動かして開発しています。(MacbookとかオシャレなPC持っていません・・)
あと、Hadoopの環境は普通に作っておいてください。ローカルモードで良いです。これも省略。

今やいろんな本が出ていますが、Hadoop徹底入門がオススメです。

まずざっくりとした手順は以下のようになります。

  • ソースを落とす
  • ソースをいじる
  • テストする
  • (JIRAで適宜コメント書いたり、やりとりしたり)
  • Phabricatorでreviewして貰う
  • 最終版のDIFFをJIRAに添付する(ASFライセンス付ける)

細かい手順を説明していきます。

まず、ソースを落としてEclipseのプロジェクトを作ります。(Eclipse使わなくてもパッチは投稿できます。ただJavaのソースをIDE無しで書くのは結構つらいと思います。)
なお、ソースを落としてきたら、HIVE-2902, HIVE-2904のパッチを当てましょう。今のバージョンだと正しいEclipseのテンプレートを作ってくれません。

# ソースを落としてくる
svn checkout http://svn.apache.org/repos/asf/hive/trunk hive-trunk
cd hive-trunk
# パッチを当てる(※1)
wget https://issues.apache.org/jira/secure/attachment/12523301/HIVE-2902.2.patch.txt
wget https://issues.apache.org/jira/secure/attachment/12522738/HIVE-2904.D2487.3.patch
patch -p0 < HIVE-2902.2.patch.txt
patch -p0 < HIVE-2904.D2487.3.patch
# ビルドする
ant clean package
# Eclipse用のプロジェクトを生成する
ant eclipse-files
# テストドライバとか生成する
cd metastore
ant model-jar
cd ../ql
ant gen-test

※1 追記(2012/04/19 13:30) パッチのバージョンを変更しました。最新のソースだとDDLTaskがjson形式で出力できるようになっていて、JsonMetaDataFormatterがjacksonに依存するようになっていました。

これでhive-trunkにeclipseのprojectファイルが出来ています。あとはEclipseからインポートするだけです。なお、Eclipseの使い方は今更説明するようなものではないので省略します。注意点としては、ワークスペースの文字コードをUTF-8に、JDKは6を使いましょう。

また、プロジェクトの設定で「Javaエディタ → 保管アクション」の設定でインポートの編成が行われるようになっています。なのでパッチを書いた時にdiffを取ったらいじっていないところも変更されている事があります。これは気にしたら負けです。
もう1つ、プロジェクトの設定の「ビルダー」で「Hive_Ant_Builder」のチェックは外した方が良いです。保存する度にAntが走って重めのビルドが走ることになってちょっとイラッとします。

ここまでで、開発環境は整いました。
というわけで、普通に開発をしましょう。なお、パッチを出す時は基本的にテストケースも一緒に追加します。(パッチの内容にもよりますけど)。

次にテストの方法です。単体テストはJUnitで書かれています。Eclipse用のプロジェクトを生成した時に、実行構成も既に設定されています。例えばJdbcDriverTestという単体テストの実行構成を見ると、以下のように既に必要な設定(VM引数、環境変数、作業ディレクトリなど)が入っています。


どのクラスがどの単体テストが担当しているのかは、クラスの参照を調べればなんとなく推測できます。まずは関連する単体テストがピンポイントで通るようにするのが良いです、なお、UDF関数やクエリのテストはまたちょっと違います。後者のテストの方法は以下のようにします。 このテストは、「ql/src/test/queries/clientpositive/timestamp_udf.q」を実行した時に「ql/src/test/results/clientpositive/timestamp_udf.q.out」の結果になるかというテストです。前回の記事にも書いてあります。 -Dqfileを省略すると全部のテストを実行します。

# ポジティブテスト
ant -Dtestcase=TestCliDriver -Dqfile=timestamp_udf.q -Dtest.silent=false test
# ネガティブテスト
ant -Dtestcase=TestNegativeCliDriver -Dqfile=timestamp_udf.q -Dtest.silent=false test

このあたりをピンポイントで実行してから、最後に全体のテストを実行します。非情に時間がかかります。4,5時間は覚悟してください。寝る前に実行しましょう。

ant package test
ant testreport

これで build/test/junit-noframes.html というファイルが生成されます。このファイルにテストの結果が書いてあります。

パッチを投げる前にsvn diffを取って差分を確認してください。(※最初に環境構築をするためにパッチを当てたやつは除外する必要があります。)

# LANGが日本語になっていると「リビジョン」という日本語が入るので
export LANG=C
svn diff > HIVE-xxxx.1.patch.txt

gitの場合は、以下のような感じです。

git diff --no-prefix > HIVE-xxxx.1.patch.txt

これでパッチを投げる準備ができました。(後述するPhabricatorを使わない場合は、このDIFFをJIRAにファイル添付すれば良いです。)

チケットの開き方は前回の記事を参考にしてください。
※こんな事を書くと怒られるかもしれませんが、ぶっちゃけ英語なんて適当で通じます(たぶん)。英語が出来なくてウジウジするくらいならコードを書いてパッチを投げましょう。(もしくは知り合いに英訳して貰いましょう)。自分たちが外国人のニンジャ語を理解できるように向こうの人もきっと理解してくれます。たぶん(・3・)。

パッチの投稿の仕方です。Phabricatorをインストールする必要があります。以下のURLを参考にします。
http://www.phabricator.com/docs/phabricator/article/Arcanist_User_Guide.html
https://cwiki.apache.org/confluence/display/Hive/PhabricatorCodeReview

# どこかのディレクトリで以下を実行
git clone git://github.com/facebook/libphutil.git
git clone git://github.com/facebook/arcanist.git
# BashのTab Completion(arc[TAB]で引数を表示してくれる)を有効にする
source arcanist/resources/shell/bash-completion

その後、以下のコマンドを叩いて準備をします。
arcanist/bin を環境変数PATHに通しておくと良いです。

ant arc-setup
arc install-certificate

Phabricatorにパッチを投げる手順は以下になります。これは実際にsvnかgitでdiffを取ってPhabricatorに投稿します。これでJIRAにパッチが添付されます。

# svnの場合
arc diff --jira HIVE-xxxx
# gitの場合(gitの場合はarc diffで--previewオプションが使えます)
git checkout HIVE-xxxx-dev-branch
ソース編集
git commit -a --amend --reset-author -C HEAD
arc diff

以上でJIRAにパッチが投稿されるので、「Submit Patch」を押します。するとステータスがPatch Availableになります。 パッチがNGな場合は、レビューの内容やコメントの内容を見て修正しましょう。

レビューが終わったら最終バージョンのパッチをJIRAに添付します。添付する時はASFライセンスを付与しましょう。この時に新機能の追加や互換性が無くなる変更の場合はReleaseNoteを各必要があります。 

私はHiveにはそんなに詳しくない初級者ですが、何か困ったことがあれば協力できるかも。

なお、Hadoop Hackathonというものがあります。
http://www.zusaar.com/event/250002

私が参加した時は、JIRAの使い方とパッチの書き方を最初に教えていただきました。基本は黙々とハッカソンするわけですが、
お菓子を食べながらプロジェクターにソースを映してアレやコレやと楽しかったです。
開発環境とJavaさえ書ければ、英語は出来なくてもみんな英語が出来るので誰かが助けてくれます。

Hadoopのここが気にくわないわー。Hiveのここが気にくわないわー。あのBug放置されてるわー、直したいわー。という人は参加を考えてみると良いかも。

AvocadoDBについて

こんにちは、某所で豆腐ハンバーグを食べないといけないくらい貧乏ということになっているタムタムです。(なんか面白かったのでこのネタしばらく使います)

English translation is here. Very thanks to @makoto_inoue!!
https://gist.github.com/2359195

今回は AvocadoDB というものを紹介したいと思います。
間違いや勘違いがあったらご指摘くださいませ。

一言でいうと、MongoDB と OrientDB を足したようなものです。Webサイトに掲載されている特徴を列挙すると以下のようになります。

  • スキーマレスです
  • クライアント・サーバモデル(アプリケーションサーバとして動作)です。そしてRESTインターフェースを提供しています。
  • JavaScriptエンジン(V8)を内蔵していて、それでデータを操作できます(もっと言えば、自分でJS追加すると機能を拡張できる!! のでKTのLuaバインディングっぽい事がもっと簡単にできます)
  • マルチスレッドで動作します
  • MVCCモデルです。ドキュメントは(内部的には追加オンリー)でバージョン管理していて、古いバージョンは自動でガベコレされます。(PostgreSQLみたいなやつ)
  • 柔軟なデータ構造を提供します。単純なKVS、DocumentDB、グラフDB(これはまだRESTで扱えないけど、シェルからはJSを介して使えます)
  • インデックスを選択できます。スキップリストやn-gramインデックスとか。(将来的にはHashIndexやGeoIndexも?)
  • 永続性の設定ができます。
  • モダンなストレージに対応しています。(※意味が良くわからない・・SSDとかに最適化してるってこと?誰か教えてエロい人)
  • オープンソースです(Apache Licence 2.0)

(追記)
※MVCCについて、Frank Celler(@fceller) さんからコメントがあったので記載しておきます。
@fceller said:

currently we have implemented Multi-Versions (aka Revisions) of documents. CC will come, when we have replication.

ちょっと英語とMVCCについての理解が浅いので勉強してくる(( T_T)
(追記ここまで)

AvocadoDB の何が熱いかと言うと、開発者のレスポンスの早さと開発速度がちょっとクレイジーなくらいはやいところ。今だとAvocadoDBについてTweetすれば関係者がレスポンスくれます。日本語でもだいたい反応してくれます。(というか、TwitterをAvocadoDBで検索すると、開発者か日本人しかつぶやいていません・・) 
あと、絶賛開発中なのにドキュメントが綺麗・豊富に揃っています。なので、Wiki を見ればだいたいの事は載っています。(目次からたどれないページがいくつかあるように感じるので、ページ一覧から行く方が早いです)
常識的なC++で書かれているので(一部Cのコードもあります)ソースコードは読みやすいです。コメントもきちんと書いてあります。RESTに関してはコアな機能がC++で実装されていて、ほとんどがJSで実装されておりコアな機能を呼び出しているという形式になっています。

開発者がどれくらい熱いかと言うと、アレな会社で一般人なイケメンをやっている人のTweetに対してVideoで返信してくるとか。



http://www.avocadodb.org/2012/04/03/skip-list-indices-in-avocadodb

各言語のDriverというかClientというかバインディングみたいなものは、公式ではPHPのものがあります。他の言語については、皆様が絶賛開発中です(なぜかみんな日本人)。現在の対応状況がロードマップに記載されています。

なお、JavaのDriverは私が絶賛開発中でございます。

進捗としては、DocumentDBとして使えるところまでは出来ています。今ユニットテストをいっぱい書いてます・・。じゃないとサーバの挙動が変わった時にregression testが出来なくて死ぬので。
概要だけ書いておくと、AvocadoClient, AvocadoDriver, AvocadoJDBCDriverという構成になります。AvocadoClientはオブジェクト指向的に扱うためのインターフェースでDocumentクラスがcreateとかを持つようになります。AvocadoDriverはREST-APIとほぼ1:1に対応した低レイヤーのインターフェースになります。AvocadoJDBCDriverはJDBCドライバとして実装しているものになります。今作っているのはAvocadoDriverです。Client/Driverと分けたのは、CommonsHttpClient4やsvnkitのような感じの低レイヤーのものと高レイヤーのものとで分離したかったからです。

こんな感じで使いますというのをGistに簡単なサンプルを載せました。(検証コードがあちこちにあって見づらいですが、使い方は至ってシンプルだと思います。)
まだgithubにはpushしてません。もうしばらくお待ちくださいませ。

AvocadoDBのインストールの方法や簡単な使い方、グロッサリーの説明は別の記事に書きます。

MurmurHash Java版

リハビリのためにMurmurHashをJavaに移植してみました。
Version1,2,3全てありますので、32bit,64bit,128bitのハッシュを求めることができます。

ソースはこちらです。
https://github.com/tamtam180/MurmurHash-For-Java

オリジナルと同じ結果を返すことは確認済みです。

MurmurHashってなんぞという人はオリジナルのサイトをどうぞ。
https://sites.google.com/site/murmurhash/
http://code.google.com/p/smhasher/

github便利だけど、バージョン管理して配布するような場合ってGoogle Projectの方が良いような気がしてる。パッケージをダウンロードさせたい場合、Githubではどうやるのかわかんないや。

HiveをEclipseを使って開発する時の手順で躓いた時の対処法

HiveをEclipse使って開発しようとするとき、こちらのページには以下のようにしましょうねと書いてあるのですが、

$ ant clean package eclipse-files
$ cd metastore
$ ant model-jar
$ cd ../ql
$ ant gen-test

最後の ant gen-test を実行すると、以下のエラーが発生します。(trunkのrev 1293919で確認)

BUILD FAILED
/home/tamtam/work/hive-trunk/ql/build.xml:116: Problem: failed to create task or type if
Cause: The name is undefined.
Action: Check the spelling.
Action: Check that any custom tasks/types have been declared.
Action: Check that any <presetdef>/<macrodef> declarations have taken place.

<if>というタスクが無いよーというエラーです。 ${hive.root}/testlibs/ant-contrib-1.0b3.jar このJARファイルのIfTaskは中に入っており、build.xml中でtaskdefしてあげないといけません。以下の定義を追加してあげる必要があります。${hive.root}/build.xmlには定義されているんですけどね。。

<taskdef resource="net/sf/antcontrib/antcontrib.properties">
  <classpath>
    <pathelement location="${hive.root}/testlibs/ant-contrib-1.0b3.jar"/>
  </classpath>
</taskdef>

この定義をql/build.xmlのgen-testのtarget配下に書いてもいいのですが、いろんなbuild.xmlがbuild-common.xmlをimportしていて、さらにbuild-common.xmlでも<if>タスクを使っているので、build-common.xmlに定義するのが良いかと思います。

diffはこんな感じです。

Index: build-common.xml
===================================================================
--- build-common.xml    (リビジョン 1293919)
+++ build-common.xml    (作業コピー)
@@ -89,6 +89,12 @@
   </condition>
   <import file="build-offline.xml"/>

+  <taskdef resource="net/sf/antcontrib/antcontrib.properties">
+    <classpath>
+      <pathelement location="${hive.root}/testlibs/ant-contrib-1.0b3.jar"/>
+    </classpath>
+  </taskdef>
+
   <!--this is the naming policy for artifacts we want pulled down-->
   <property name="ivy.artifact.retrieve.pattern" value="[conf]/[artifact]-[revision](-[classifier]).[ext]"/>

つか、誰も困って無いのかな・・。
それっぽいチケットも無いんだけど。

HiveのUDF一覧を自動生成してみた

発熱でDOWNしているタムタムです。
インフルエンザのようなすごい高熱というわけでもなく、プリン食べて家でしょぼーんとしてます。

今回はHiveのUDF一覧を自動生成してみました。(Hive0.8)
http://mt.orz.at/archives/hive_udf_0.8.html
(追記) Hive0.8用はURLを変更して、こちらにtrunk(rev1293519)のバージョンを起きました。

reflect関数とかWikiには載っていないと思います。
builtinと書いてあるやつはFunctionRegistryのstaticイニシャライザで関数登録しているやつです。Alias名がある関数はそれぞれ1つの項目に作ってあります。

作り方は至ってシンプルで、UDF関数はDescriptionというアノテーションが定義されています。desc function xxxxという構文で表示される説明は、このアノテーションの情報を表示しているだけです。というわけで、SpringFrameworkのClassPathScanningCandidateComponentProviderを使ってClassPath中のDescriptionアノテーションが定義してあるクラスを取得して情報を拾っているだけです。

こんな感じです。

ClassPathScanningCandidateComponentProvider scanner =
        new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(Description.class));
Set<BeanDefinition> beans = scanner.findCandidateComponents("");

取得したBean定義を回して、リフレクション使ってAnnotation情報を取得します。
nameをカンマで区切っているのは、Aliasがある場合はカンマ区切りで入っているからです。あとはdescから情報を普通に引っこ抜けます。

for (BeanDefinition def : beans) {
    Class<?> clazz = Class.forName(def.getBeanClassName());

    Description desc = AnnotationUtils.findAnnotation(clazz, Description.class);
    String[] funcNames = desc.name().split(",");
}

簡単ですね (=ω=)b

Hiveのテスト方法とJIRAチケットの開き方

Hiveのチケットを開いてBug報告をしてみたのでその手順をメモメモ。せっかくなのでテストの方法とかも書いておきます。
(チケットの内容はほとんどshiumachiさんに書いたもらったものですけどね(ノ∀`))
shiumachiさん、丁寧に教えていただきましてありがとうございます。

https://cwiki.apache.org/confluence/display/Hive/HowToContribute
Wikiにも書いてあるので、こちらを参考にすると良いと思います。

■ trunkで再現するかの確認について
Eclipseを使ってやる場合は、こちらのページが参考になると思います。 今回は簡単な修正だったのでコンソール上だけで行いました。 trunkを落としてきてビルド、Hiveを起動する手順を以下に書きました。設定ファイルを特に作っていないのでMetastoreはカレントディレクトリに作られます。MetastoreをMySQLに入れるところがーとかを修正する場合は、ちゃんとconfの下に設定ファイルを作って設定しましょう。 (あ、HADOOP系の環境変数は設定済みの前提です)

$ git clone git://git.apache.org/hive.git
$ cd hive
$ ant clean package
$ cd build/dist
$ export HIVE_HOME=`pwd`
$ cd bin
$ ./hive

今回初めてgitを使いました。 Hiveの規模であれば、--depthオプション無くてもいけます。 svnの場合はwikiにある通りに、以下のようにソースを持って来ます。

svn co http://svn.apache.org/repos/asf/hive/trunk hive-trunk

■ソースを修正したらテストをします。
私のPCではテスト全部通すと4時間程度かかりました。

全てのテストを実行する場合は以下のように実行します。テストが終わったらテストレポートも作ります。 テストレポートはbuild/testディレクトリにjunit-noframes.htmlというファイルが作られます。このレポートファイルを見ると、何のテストが失敗したのかと、簡単な情報(例えばExceptionが出ているとか)がわかります。

$ ant test
$ ant testreport

個別にテストをする場合は、以下のようにします。qファイルはフルパスではなくファイル名を指定します。(以下の場合はql/src/test/queries/clientpositive/timestamp_udf.qのテストをしています)

$ ant test -Dtestcase=TestCliDriver -Dqfile=timestamp_udf.q -Dtest.silent=false

なお指定したqueryのテストに対応する正解データは.q.outファイルです。上記の場合は、ql/src/test/results/clientpositive/timestamp_udf.q.outのファイルが正解データとなります。なお、正解データを実行結果で上書きする事ができます。まぁこれ手で正解データ作るのは無理ですw 以下のコマンドで上書きできます(-Doverwrite=trueを追加)。

$ ant test -Dtestcase=TestCliDriver -Dqfile=timestamp_udf.q -Doverwrite=true -Dtest.silent=false

私は今回上記のテストケースファイルに追加する形で対応しました。
テストする時は、上記のように影響があるテストをピンポイントで実行して最後にテストを全部通すと良いと思います。
全体テストは時間がかかるので、並列実行する方法があります。(直列のまま実行する場合は、寝る前に実行すれば良いと思うよ!!)

テストに関する情報は、こちらのWikiのページのTestingの項目が参考になります。
(パッチを投稿する場合)テストが通ったらパッチを作ります。

■パッチの作り方について

パッチの作り方はもHow To Contributeのページに書いてあるとおり、以下のように作ります。 パッチファイルはテキストファイルとして作った方がブラウザで見れるのでその方が良いです。「Appending '.txt' to the patch file name makes it easy to quickly view the contents of the patch in a web browser.」とWikiに書いてありますし、shiumachiさんからもその方がいいよーとの事です。

git diff --no-prefix > HIVE-xxxx.1.patch.txt

■JIRAのチケットをOPENします。

ちなみに、 https://gist.github.com/1797071 これを参考にすると良いよ!
shiumachiさんの添削が載っているので、とっても参考になると思います。
(githubのアカウントもこの時に作ったので、今度からちょこちょこ何かソースアップしていきます。)

実際にOPENする方法は、まずはJIRAのアカウントを作ります。(アカウントの設定でLanguageを日本語に出来ますが、逆にわかりづらくなって発狂するのでEnglishのままのが良いです。(HiveのJIRAのページはここです)
右上のCreateIssueを押して「Hive」「Bug」を選択して「Create」を押すか、その下のCreate: Bugを押します。(下の絵を参照)

20120213_jira_ticket_01.png

Issueの内容ですが、IssueTypeにはBugが設定されていると思うので、他の項目を入力していきます。

Summary: いわゆるタイトルです。
Priority: この項目は以下を参考にします。

Blocker: Blocks development and/or testing work, production could not run
Critical: Crashes, loss of data, severe memory leak.
Major: Major loss of function.
Minor: Minor loss of function, or other problem where easy workaround is present.
Trivial: Cosmetic problem like misspelt words or misaligned text.

Component/s: 対象のコンポーネントを選択します。今回は「UDF」を選択しました。
Affects Version/s: 発生するバージョンを選択します。今回はHive0.8を選択しました。Trunkの場合は選択しないようです。選択されているチケットが少ない気がするのですが、リリースされているバージョンで発生するので、ちゃんと選択してみました。
Fix Version/s: 選択しませんでした。(これはコミッターが後で選択するもの?)
Environment: なんか書いている人が少ない。。Hadoopのバージョンとか書くのかな・・。空欄にしちゃいました。
Description: チケットの内容を書きます。

あとは空欄にしちゃいました。(ノ∀`)

パッチを添付する場合は一度チケットを作ってから添付します。
More ActionsのAttach Filesを選択すると添付できます。

実際に作ったチケットはこれです。
https://issues.apache.org/jira/browse/HIVE-2803

何か作法が間違っていたら追記していきます(・x・;)

Hive0.8のfrom_utc_timestamp, to_utc_timestampの不具合を修正する

HadoopアドベントカレンダーでTimestampの機能について書いた時に見つけた不具合を調査してみました。 自分の備忘録用にメモメモ。

from_utc_timestampまたはto_utc_timestampをSELECT句に複数書くと値がおかしくなるというものです。正確には、1つ書いても壊れます。まずは再現コード。

create table ts1(t1 timestamp);
load data local inpath '/mnt/iscsi/test1/data4.txt' overwrite into table ts1;
select t1 from ts1;
2011-12-25 09:00:00
select from_utc_timestamp(t1, 'JST') from ts1 limit 1;
2011-12-25 18:00:00

うまくいっているように見えますが、、

select t1, from_utc_timestamp(t1, 'JST'), t1 from ts1 limit 1;
2011-12-25 18:00:00     2011-12-25 18:00:00     2011-12-25 18:00:00

おや、全部の値が変わってしまいました。さらに、from_utc_timestamp(t1, 'JST')を追加してみます。

select t1, from_utc_timestamp(t1, 'JST'), t1, from_utc_timestamp(t1, 'JST') from ts1 limit 1;
2011-12-26 03:00:00     2011-12-26 03:00:00     2011-12-26 03:00:00     2011-12-26 03:00:00

SELECT句で書いたt1が全て同じ値になります。
次に実装クラスを見てみます。org.apache.hadoop.hive.ql.udf.genericパッケージのGenericUDFFromUtcTimestampとGenericUDFToUtcTimestampです。GenericUDFToUtcTimestampはGenericUDFFromUtcTimestampを継承していて、フラグに応じてOffset値の正負を反転させているだけです。で、問題はGenericUDFFromUtcTimestampのapplyOffsetメソッドです。ソースを下に書きました。

  @Override
  public Object evaluate(DeferredObject[] arguments) throws HiveException {
    Object o0 = arguments[0].get();
    TimeZone timezone = null;
    if (o0 == null) {
      return null;
    }

    if (arguments.length > 1 && arguments[1] != null) {
      Text text = textConverter.convert(arguments[1].get());
      if (text != null) {
        timezone = TimeZone.getTimeZone(text.toString());
      }
    } else {
      return null;
    }

    Timestamp timestamp = ((TimestampWritable) timestampConverter.convert(o0))
        .getTimestamp();

    int offset = timezone.getOffset(timestamp.getTime());
    if (invert()) {
      offset = -offset;
    }
    return applyOffset(offset, timestamp);
  }

  protected Timestamp applyOffset(long offset, Timestamp t) {
    long newTime = t.getTime() + offset;
    int nanos = (int) (t.getNanos() + (newTime % 1000) * 1000000);
    t.setTime(newTime);
    t.setNanos(nanos);

    return t;
  }

行を評価中、timestamp変数は同じインスタンスを毎回返してきます。そのインスタンスに対してapplyOffsetでsetTimeしちゃってます。つまり、その行を評価中はt1は同じインスタンスを使い回してしまいます。なのでSELECT句に書いたOffset値全てが計算された結果が出力されるわけです。そして、1列評価→出力→1列評価→出力ではなく、全ての列を評価→出力なので、出力時には全て同じtimestampインスタンスを指すことになって、全て同じ値が表示されるわけです。(ここで言う全てはt1を参照している列という意味です)

修正方法ですが、結局のところは同じインスタンスを指せないという事で、インスタンスを生成する必要が出てきます。というわけで、手っ取り早く修正するのであればapplyOffsetを修正します。以下が修正版です。

  protected Timestamp applyOffset(long offset, Timestamp t) {
    long newTime = t.getTime() + offset;
    int nanos = (int) (t.getNanos() + (newTime % 1000) * 1000000);
    Timestamp t2 = new Timestamp(newTime);
    t2.setNanos(nanos);

    return t2;
  }

(追記)上記のコードはダメでした。nano時間のところがそもそもバグっていて秒未満の値がずれます。以下のコードの方が良いです。(timestamp_udf.q.outの秒未満部分もずれてました・・)

  protected Timestamp applyOffset(long offset, Timestamp t) {
    long newTime = t.getTime() + offset;
    Timestamp t2 = new Timestamp(newTime);
    t2.setNanos(t.getNanos());

    return t2;
  }

hiveをビルドしなおして再度実行してみます。 

select t1, from_utc_timestamp(t1, 'JST'), t1, from_utc_timestamp(t1, 'JST') from ts1 limit 1;
2011-12-25 09:00:00     2011-12-25 18:00:00     2011-12-25 09:00:00     2011-12-25 18:00:00

直りました。
さて、Hadoop、Hiveのコミュニティっていきなりチケットを開いてもいいのかな。ちょっとMLとJIRAを漁って作法を調べよう。

 

チケット開く前にパッチを置いておこう。Javaソースとテスト用のqファイル、q.outファイルのパッチは分離して2つに分けた方が良かったかな・・。
http://mt.orz.at/archives/HIVE-xxxx.1.patch.txt

 

HiveでJSON形式のログを構造化する

fluentの勉強会に参加したら、まわりがモヒカンばっかりで消毒されそうになったタムタムです。

先日のMixi Engineers' Seminar #3でMixiがJSONデータをHiveで集計しているよーという発表をしていて、Fluentのtail + apacheもjsonとしてデータが飛んでいるとの事で、相性は悪くないよねーというわけでサンプルを作ってみました。(スライドを見ていた感じではこんな感じだったはず)

まずサンプルデータを用意します。
適当に100件ほど作りました。 (nameが名前でexam{1|2|3}は何かのテストの点数です)

{"name" : "Irma", "exam1": 58, "exam2": 73, "exam3":85}
{"name" : "Dorothy", "exam1": 50, "exam2": 39, "exam3":11}
{"name" : "Echo", "exam1": 95, "exam2": 47, "exam3":96}
{"name" : "Ina", "exam1": 50, "exam2": 68, "exam3":38}
{"name" : "Kay", "exam1": 15, "exam2": 11, "exam3":46}
....

まずはデータをそのまま1つの列に入れます。 今回はサンプルなのでPARTITIONは切っていません。

create table raw_data(line string);
load data local inpath '/mnt/iscsi/test1/exam_data1.txt' overwrite into table raw_data;

このJSONデータをテーブル化するクエリは以下のように書けます。

select
  get_json_object(raw_data.line, '$.name'),
  get_json_object(raw_data.line, '$.exam1'),
  get_json_object(raw_data.line, '$.exam2'),
  get_json_object(raw_data.line, '$.exam3')
from raw_data;

もしくは、json_tupleとlateral viewを使います。 (Hiveのマニュアルには、上記のようなクエリを書くなら下記のようなクエリを書きましょうねと書いてあります。)

SELECT t1.*
FROM raw_data LATERAL VIEW json_tuple(raw_data.line, 'name', 'exam1', 'exam2', 'exam3') t1 as nm, exam1, exam2, exam3;

json_tupleはstring型として返ってくるので、これを整数型にします。SELECT句でCastしているだけです。

SELECT t1.name, cast(t1.exam1 as int), cast(t1.exam2 as int), cast(t1.exam3 as int)
FROM raw_data LATERAL VIEW json_tuple(raw_data.line, 'name', 'exam1', 'exam2', 'exam3') t1 as name, exam1, exam2, exam3;

これをView化します。

DROP VIEW IF EXISTS exam_table;
CREATE VIEW IF NOT EXISTS exam_table (
  name,
  exam1,
  exam2,
  exam3
)
AS
SELECT t1.name, cast(t1.exam1 as int), cast(t1.exam2 as int), cast(t1.exam3 as int)
FROM raw_data LATERAL VIEW json_tuple(raw_data.line, 'name', 'exam1', 'exam2', 'exam3') t1 as name, exam1, exam2, exam3;

簡単な集計をしてみます。

SELECT
  count(*),
  avg(exam1),
  avg(exam2),
  avg(exam3)
FROM
  exam_table;
100     50.72   52.71   50.43

集計できました。めでたし、めでたし。
ちなみにHive0.5までは(私は0.6から使い始めたのでわからんですが・・)Where句を書くとクエリがコンパイルできないらしくて、そのときは、「set hive.optimize.ppd=false;」と設定するみたいです。
https://issues.apache.org/jira/browse/HIVE-1056

ちなみにJSON用のSerdeもあります。
http://code.google.com/p/hive-json-serde/
ぶっちゃけ、こっち使った方がスマートに書けますw

というわけで、hiveはfluentとの相性も良さそうですね。
(ただし今回は統計情報を見ていないので、上記のサンプルが効率良いのかは調べていません(=ω=))

JavaのsubListの罠

本当は食べ物の写真とか載せたいんですけどね。。
ついこの前やらかしちゃった事を備忘録としてメモメモ。

Javaでは基本的にSerializableを実装していないと(Externalizableでもいいけど)、Objectをシリアライズできません。例えばMemcachedにそのままObjectをまるっと保存する場合は、ObjectOutputStreamを介して保存する事が多いと思います。(MsgPackとかJSONICとかを使う方が良いかもしれませんが)。

で、この前やらかしてしまったのは、JavaのListインターフェースにあるsubListで返ってくるリストはSerializableインターフェースを実装していないListなので、キャッシュに保存できないでエラーが出まくりでした。と書くと語弊があるので正確に書くと、ArrayListとLinkedListはAbstractListを継承しており、subListの実装はAbstractListが持っています。AbstractList#subListの実装は、RandomAccessを実装しているクラスの場合はRandomAccessSubListを、実装していない場合はSubListのObjectを返します。RandomAccessSubListもSubListもSerializableは実装されていません。

public List<E> subList(int fromIndex, int toIndex) {
    return (this instanceof RandomAccess ?
            new RandomAccessSubList<E>(this, fromIndex, toIndex) :
            new SubList<E>(this, fromIndex, toIndex));
}

ちなみに、subListは要素をコピーして作っているわけでは無く、単純に元リストのラッパークラスのように振る舞っているだけなので、そこも注意なのです。subListで返ってきたリストに対して操作するとどうなるかのサンプルを書きました。

import java.util.ArrayList;
import java.util.List;

public class Test1 {
	public static void main(String[] args) throws Exception {

		// サンプルデータを作る
		ArrayList<Integer> srcList = new ArrayList<Integer>();
		for (int i = 0; i < 10; i++) {
			srcList.add(i);
		}
		System.out.println("src=" + srcList);
		// サブリストを作ってみる
		List subList = srcList.subList(5, 8);
		System.out.println("sub=" + subList);
		// サブリストを操作する
		subList.add(100);

		System.out.println("=====after=====");
		System.out.println("sub=" + subList);
		System.out.println("src=" + srcList);

		System.out.println(subList instanceof Serializable);
	}
}

実行結果

src=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
sub=[5, 6, 7]
=====after=====
sub=[5, 6, 7, 100]
src=[0, 1, 2, 3, 4, 5, 6, 7, 100, 8, 9]
false

 

Hiveで整形されていないログを集計する方法

タムタムです。Hadoop アドベントカレンダーの12/24分 を書かせていただきます。
それと、時々ログ解析飲み会というものをやっているという噂があるのですが、わたしも混ぜてください>< 

さて、まずはじめに・・。 ログが整形されているなんて都市伝説です。

自分が作るアプリは最初からログ設計をして整形して出力しているのですが、世の中そんなものばかりではありません。Hiveで集計するためにはある程度書式が整っていないとスマートに処理できません。
適当なスクリプトで処理するのも手ですが、もともと分散しないと処理できないほどの量なのに、それを分散環境で処理しないとか無いと思います・・。

となると、スクリプトを書いてHadoop Streamingでログを処理すればいいよねーとなるわけです。が、用途はある程度限られてしまいますが実はHiveでも出来ます。

例えば、以下のようなログがあるとします。
どこかで似たようなものを見たことある人も居るかもしれませんが気のせいです(=ω=)

2011-10-14 00:00:14  INFO  - [XXX][MONS] (charaId:123456, tmpId:56789) start attack monster(MonsterAAA) at (85.9,199.2,-590.6)
2011-10-14 00:00:14  INFO  - [XXX][MONS] (charaId:123456, tmpId:56789) start attack monster(MonsterAAA) at (85.9,199.2,-590.6)
2011-10-14 00:00:14  INFO  - [XXX][MONS] (charaId:123456, tmpId:56789) start attack monster(MonsterAAA) at (85.9,199.2,-590.6)
2011-10-14 00:00:18  INFO  - [XXX][ITEM] (charaId:123456, tmpId:56789) item can add *******
2011-10-14 00:00:18  INFO  - [XXX][ITEM] (charaId:123456, tmpId:56789) item add ******
2011-10-14 00:00:18  INFO  - [XXX][XXXX] *************
2011-10-14 00:00:18  INFO  - [XXX][MONS] (charaId:123456, tmpId:56789) kill monster(MonsterAAA) at (85.9,199.2,-590.6)

さて、このファイルにはモンスターの討伐記録のログがあります。 一番下のログです。
このログファイルからモンスター討伐数を集計したい場合、一番下の行には、誰が(charaId)いつ(時間)どこで(ワールド、エリア、XYZ座標)なにをした(MonsterAAAを倒した) が記録されており、まずはこの形式のログだけを抽出してテーブルに入れたいわけです。 (ワールドとエリアはログファイルの単位なので記録はされていないです。)

まずは元のgzログをtmp2テーブルに入れます。ログファイルはHDFS上にあります。
パーティションを適当に切ります。idは適当なIDを入れて作業がかぶらないようにする用です。OracleのPLAN_TABLEみたいなイメージです。別に無くてもいいです(=ω=)

create table tmp2(line string)
  partitioned by (id string, world string, area string);

load data inpath '/test/log/neko/2011-10-14-dungeon01.txt.gz'
overwrite into table tmp2
partition(id='aaa111', world='neko', area='dungeon01');

次にMap用のスクリプトを書きます。 (map2.pl)

#!/usr/bin/perl

use warnings;
use strict;

while (<>) {
  my ($world, $area, $line) = split("\t", $_);
  if ($line =~ /^((\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})).*\(charaId:(\d+).*tmpId:(\d+)\).*kill monster\s*\((\w+)\)\s*at\s*\((.*),(.*),(.*)\)/) {
    my @array = (
      $world,
      $area,
      $1,        # datetime
      $4,        # charaid
      $5,        # tmpId
      $6,        # monster-name
      $7, $8, $9 # x,y,z
    );
    print join("\t", @array), "\n";
  }
}

わざわざ配列に入れてjoinする必要なんてありませんよね。ごめんなさい。コメントを見やすくする関係でこう書きました。
ちょっと確認したい場合は、以下のようにやればスクリプトの確認はできます。

zcat hogefuga.log.gz | awk '{print "world01\tdungeon01\t"$0}' | ./map2.pl
world01  dungeon01  2011-10-14 00:00:18    123456  56789   MonsterAAA  85.9    199.2   -590.6

試しにSELECTしてみます。

SELECT TRANSFORM(line)
USING '/home/hadoop/map2.pl' AS (
  dt timestamp,
  charaId bigint,
  tmpId bigint,
  monster_name string,
  x double,
  y double,
  z double
)
FROM tmp2 WHERE id = 'aaa111'
LIMIT 10;
2011-10-14 00:00:18     12345678        00000000        MonsterAAA     85.9    199.2   -590.6
2011-10-14 00:00:18     12345678        00000000        MonsterBBB    -41.7    184.7   -261.3
2011-10-14 00:00:28     12345678        00000000        MonsterAAA     85.9    199.2   -590.6
2011-10-14 00:00:42     78901234        11111111        MonsterCCC     85.9    199.2   -590.6
2011-10-14 00:00:53     12345678        00000000        MonsterCCC     85.9    199.2   -590.6
2011-10-14 00:01:07     12345678        00000000        MonsterBBB     63.5    200.5   -550.9
2011-10-14 00:01:11     78901234        11111111        MonsterCCC     85.9    199.2   -590.6
2011-10-14 00:01:20     12345678        00000000        MonsterCCC     85.9    199.2   -590.6
2011-10-14 00:01:30     12345678        00000000        MonsterBBB    -61.7    184.0   -258.4
2011-10-14 00:01:37     12345678        00000000        MonsterBBB     13.5    200.1   -570.1

(結果をダミーのものに書き換えるのめんどくさい・・・。でも本物出すわけにはいかないし・・。)

集計してみます。

SELECT world, area, monster_name, count(*) as cnt FROM (
  SELECT TRANSFORM(world, area, line)
  USING '/home/hadoop/map2.pl' AS (
    world string,
    area string,
    dt timestamp,
    charaId bigint,
    tmpId bigint,
    monster_name string,
    x double,
    y double,
    z double
  )
  FROM tmp2 WHERE id = 'aaa111'
) tbl GROUP BY world, area, monster_name ORDER BY cnt desc;
neko    dungeon01       Monster001      3215
neko    dungeon01       Monster002      1733
neko    dungeon01       Monster003      945
neko    dungeon01       Monster004      736
neko    dungeon01       Monster005      476
neko    dungeon01       Monster006      471
neko    dungeon01       Monster007      455
neko    dungeon01       Monster008      407
neko    dungeon01       Monster009      181
neko    dungeon01       Monster010      168
neko    dungeon01       Monster011      154
neko    dungeon01       Monster012      22
neko    dungeon01       Monster013      16
neko    dungeon01       Monster014      8
neko    dungeon01       Monster015      8

集計できました(・ω・)ノ
Tmpテーブルを経由しないといけないのがアレですが、externalなのでログの複製は行われないし、中間テーブル的な整形されたテーブルの実体も持たなくて良いのです。集計前にテーブルに1回入れる場合は普通に末尾にINSERT構文を書けばできます。まぁこういう方法もありますよと言うことで。

今回はMapしか書いてませんが、Reduceもスクリプトで書くことができます。
https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Transform

 

Hadoopアドベントカレンダー2011 17日目 - Hive 0.8 について -

こんにちは、タムタムです。
久しぶりに記事を書きます。 Hadoopアドベントカレンダー2011の17日目という事で書かせていただきます。

私のような底辺エンジニアでも集合論の知識のみで簡単にデータ集計処理が書けるのがHiveの良いところだと思います。
試しに某ゲームのモンスター討伐のログを、数日分、全ワールド、Zoneエリア、モンスター単位で集計したら100秒くらいで結果が出ました。(その時はレコードが1億程度と少ないという事もありますが)
※ちなみに私はマイニングエンジニアではありません。

というわけでHive0.8に関する事を書きたいと思います。
Hive0.8ではBitmapインデックスやtimestamp型が追加されたというのが大きなトピックらしいです。さっそく新機能を試してみたいと思います。また細かい構文が追加されているようなので、それもおいおい試してみたいと思います。今回はtimestamp型について書きます。
ちなみに、まだリリースされていないのでソースを持ってきてビルドする必要があります。ビルドの方法は次の通りです。

svn co http://svn.apache.org/repos/asf/hive/branches/branch-0.8/ hive-0.8
cd hive-0.8
ant clean package

build/dist配下にパッケージが出来ていますので、build/distをHIVE_HOMEに設定してbin/hiveを実行します。

※(追記)Hive 0.8は12/16にリリースされてましたw

今まで日付データをhiveで扱うにはstringとして保持してWHERE句にUDFで日付に戻して計算をするとか、bigintでunixtimeとして保持したりしていました。timestamp型についてまずはどんな操作ができるのか調べてみます。

hive> show functions;

の結果を0.7と0.8で比較し、0.8で追加されたUDFを列挙しました。 (descriptionはコマンド打っても出てこなかったものがあるので書くのをやめましたw)

UDF Description
ewah_bitmap
ewah_bitmap_and
ewah_bitmap_empty
ewah_bitmap_or
from_utc_timestamp
map_keys
map_values
named_struct
timestamp
to_utc_timestamp

またJIRAのチケットによると、datetimeに関する様々なUDFがtimestampをサポートしたようです。

例えば以下のような操作ができます。
サンプルデータを1件用意します。書式はyyyy-MM-dd HH:mm:ss か yyyy-MM-dd HH:mm:ss.fffffffffのみです。

2011-12-25 09:00:00
create table ts1(t1 timestamp);
load data local inpath '/mnt/iscsi/test1/data4.txt' overwrite into table ts1;

select cast(t1 as float), cast(t1 as double) from ts1 limit 1;
1.3247712E9     1.3247712E9

select cast(t1 as bigint), cast(t1 as int), cast(t1 as boolean), cast(t1 as tinyint), cast(t1 as smallint) from ts1 limit 1;
1324771200      1324771200      true    -128    26496

select cast(t1 as string) from ts1 limit 1;
2011-12-25 09:00:00

select cast('2011-12-25 9:00:00.000000000' as timestamp) from ts1 limit1;
2011-12-25 09:00:00

select
  unix_timestamp(t1),
  year(t1),
  month(t1),
  day(t1),
  dayofmonth(t1),
  weekofyear(t1),
  hour(t1),
  minute(t1),
  second(t1),
  to_date(t1)
from ts1;
1324771200      2011    12      25      25      51      9       0       0       2011-12-25

select date_add(t1, 5), date_sub(t1, 5) from ts1 limit 1;
2011-12-30      2011-12-20

select datediff(t1, t1), datediff(t1, '2011-12-10'), datediff('2011-12-20 9:00:00', t1) from ts1 limit 1;
0       15      -5

新しいUDFも試してみましょう。

select from_utc_timestamp(t1, 'JST') from ts1 limit 1;
2011-12-25 18:00:00

select from_utc_timestamp(t1, 'UTC') from ts1 limit 1;
2011-12-25 09:00:00

select from_utc_timestamp(t1, 'PST') from ts1 limit 1;
2011-12-25 01:00:00

select to_utc_timestamp(t1, 'JST') from ts1 limit 1;
2011-12-25 00:00:00

select to_utc_timestamp(t1, 'UTC') from ts1 limit 1;
2011-12-25 09:00:00

select to_utc_timestamp(t1, 'PST') from ts1 limit 1;
2011-12-25 17:00:00

うまく動いているように見えます。が、以下のクエリを発行すると正しい結果が得られません。
SELECT句に複数記述するのはNGなのかな?

select from_utc_timestamp(t1, 'JST'), from_utc_timestamp(t1, 'UTC'), from_utc_timestamp(t1, 'PST') from ts1 limit 1;
2011-12-25 10:00:00     2011-12-25 10:00:00     2011-12-25 10:00:00

select to_utc_timestamp(t1, 'JST'), to_utc_timestamp(t1, 'UTC'), to_utc_timestamp(t1, 'PST') from ts1 limit 1;
2011-12-25 08:00:00     2011-12-25 08:00:00     2011-12-25 08:00:00

ちなみに、2011-12-25 09:00:00はどのTimeZoneの9時なのか?という事が気になると思います。チケットを見ると、「hive.time.default.timezone」こういう設定が一瞬だけあったようですが、「Removed references to a "default" timezone. All times are treated as UTC」という事で無かったことになっています。実際は上の結果を見る限り(castとか)TimeZone.getDefault()が使われているように見えます。(すいませんソース追っていません)。なのでOracleVMの場合はVMプロパティのuser.timezoneで指定できますが、システムプロパティなので他に影響を及ぼしそうで怖いです。

Javaのコードで確認したら似たような動作したので、たぶんDefaultのTimeZoneを使っていると思います。grepしたらUTCとかいっぱい出てきましたけどw
(ていうか、Hiveのソース読めよって話ですよね)

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("JST"));
System.out.println(sdf.parse("2011-12-25 09:00:00").getTime());

sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(sdf.parse("2011-12-25 09:00:00").getTime());
1324771200000
1324803600000

個人的には、時間に関する型をサポートするならSimpleDateFormatの書式をtableの設定で出来ると嬉しいんですけどね。

というわけで、timestamp型に関する事を書いてみました。
もしもネタが足りなければ列指向のRCFileとSequenceFileの性能の違いやRCFileをJavaののコードで作る方法あたりを書きたいと思います。

間違いを見つけたらどんどん指摘してくれると嬉しいです(・ω・)ノ

Postgresをインストールしてみた

MySQLはJDBCからアクセスする時に全件取得するのでバッチ処理が書きづらいという理由でPostgreSQLをインストールしてみました。(フェッチサイズをInteger.MIN_VALUEに設定すれば1件づつの取得になりますが、この間に他のトランザクション走らせることができないので。)(あれ・・・コネクションを2本使えばいいのでは・・?)

インストールしたバージョンは8.4.2です。

ビルドします。

./configure --prefix=/lunar
make
make check
make install

DB初期化します。

initdb --encoding=utf-8 -U totoro --pgdata=/lunar/data/postgres

起動します。

pg_ctl -w -o "-i" -D /lunar/data/postgres -l /lunar/var/postgres.log start

スキーマを作ります。

createdb -E utf-8 -O totoro -W

 外部から接続できるように/lunar/data/postgres/pg_hba.confに設定を追加します。 allとか本当は良くないけど(・ε・)キニシナイ!!

host    all         all         192.168.0.0/24        md5

パスワードを設定しなおしました。

ALTER USER totoro with password 'xxxxxxxx';

他のホストから接続してみる。

psql -h 192.168.1.202 -U totoro postgres

手間はMySQLよりも1つ多いくらい。
やばい。。Postgresの設定パラメータの意味がわからない(´・ω・)
メモリ設定とかDISKのフラッシュ設定とかログ設定とか運用設定とか。

調べるか(´・ω・)

 

ClassLoaderを使って実行時にクラスを更新する方法(リロード処理)

JavaのClassLoaderの話をちょっとだけ。意外にClassLoaderを知らない人が居るようです。

ざっくりと説明をしますと、
クラスローダーにはBootstrapクラスローダーやシステムクラスローダー、コンテキストクラスローダーがあります。(あと拡張クラスローダーですかね)。そんでもって、クラスローダーは親子関係を持っています。委譲モデルと言って、あるクラスローダーにクラスをロードする依頼があると、親に依頼を委譲します。最初に発見されたクラスが実際にロードされるわけです。

1. Bootstrap (rt.jar)
2. 親クラスローダーA
3. 子クラスローダーB

こんなクラスローダーがあるとしてまだロードされていないクラスXをロードしようとした場合は次のように動作します。

  1. B.loadClass(X)
  2. Aに委譲
  3. Bootstrapに委譲
  4. BoostrapにXは存在しない
  5. AにXは存在しない
  6. BにXが存在するのでロードする。

ちなみにClassLoader#findClassをオーバーライドして自前の処理で上書きしてしまえば、この委譲モデルを破壊する事ができます。まぁそんな事は非推奨ですけど(´・ω・)

JVMプロセス内においては異なるClassLoaderで読み込んだクラスは例え同じバイナリでも異なるクラスとして扱われます。ClassLoaderAによってロードされたクラスXと、ClassLoaderBによってロードされたクラスXは別物です。
※クラスAとBが親子関係を持っていて、AがXをロードしている時にBでロードしても委譲モデルでAが既にロードしてあるXを使うので、こういう場合は同じ定義を指します。
TomcatのWebApps1とWebApps2の関係を想像すると理解しやすいと思います。

この特性を生かすとリロード処理が実現できるわけです。

ClassLoaderの細かい話はDeveloperWorksの記事が丁寧なのでそちらを参照した方が良いです。今回はTomcatにも実装されているリロード処理のサンプルを作ってみたいと思います。TomcatのドキュメントにTomcatのClassLoaderの戦略が書いてあるのでそれを参考にすると分かりやすいと思います。

例えばRPCサーバやスケジューラーを考えます。これらを設計する時には大きく二つに分けることができ、RPCサーバではサーバ本体と各命令、スケジューラーでも本体と各タスクという感じです。Tomcatの場合はServletエンジンとビジネスロジックを書いたServlet。(ビジネスロジックはServletに書くものではありませんけどね。)
RPCの命令、スケジューラーのタスク、ServletコンテナのServletは抽象化する事ができ、インターフェースを切ることが多いです。(HttpServletとかね。)

擬似コードだとこんな感じですね。

class MyServer {
  void run() {
    ITask task = getTask();
    task.execute();
  }
}

このMyServerとITaskを本体側のClassLoaderに、ITaskを実装したクラスを別のClassLoaderに読み込ませます。
例えば本体はCLASSPATHを指定してシステムクラスローダーに読み込ませます。(TomcatはBoostrap用のクラスを挟んでいますけどね。)
タスクは独自のクラスローダーをインスタンス化(このオブジェクトをc1とする)して(URLClassLoaderを直接でも良い)、そのClassLoaderを使ってクラス定義を読み込ませます。C言語だとdlopenみたいなイメージです。

異なるクラスローダーから読み込んだクラスは別のクラスになるので、リロードしたい場合はClassLoaderをもう1回インスタンス化(c2)してあげればいいのです。

文章だけで説明してもアレなので簡単なサンプルを書いてみました。
Thread#setContextClassLoaderを使うともっと簡単に書けると思います。

サンプルの内容は定期的にタスクを実行するだけのものです。Reload処理はJMX経由で行うようにしました。
Eclipse上で作る時はタスククラスは別プロジェクトで作るのが良いです。同じプロジェクトに入れてしまうとシステムクラスローダーによってタスクもロードされてしまうので。色々適当です。Sleepの代わりにSemaphoreを使っている理由はsleepの場合Future#cancelで割り込みが発生しなかったからです。Semaphoreだと割り込みが入ってすぐに終了できます。
tasklist.propertiesに登録したいタスクのクラス名を書く必要があります。まぁこんな事をしなくてもLunarClassLoaderに手を加えて特定のインターフェースを実装しているクラスを検索(列挙)すれば良いだけです。今回はそこまで余力がありませんでした。 タスク用のクラスパスはソースに直書きしています。自分の環境に合わせて修正してください。VMの引数に-Dhogehogeを指定してシステムプロパティとして読み込むのが一般的な方法かと思います。

package at.orz.lunar;

import java.io.File;
import java.lang.management.ManagementFactory;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javax.management.MBeanServer;
import javax.management.ObjectName;

public class LunarBootstrap {

    public static void main(String[] args) throws Exception {

        URL[] urls = new URL[]{
            new File("C:/usr/local/eclipse3.5/workspace/lunar-subsystem/lunar-task.jar").toURI().toURL(),
            new File("C:/usr/local/eclipse3.5/workspace/lunar-subsystem/bin").toURI().toURL()
        };

        final ExecutorService service = Executors.newCachedThreadPool();
        final LunarServer lunarServer = LunarServer.newInstance(urls, Thread.currentThread().getContextClassLoader());
        final Future<?> future = service.submit(lunarServer);
        service.shutdown(); // 新規追加STOP

        MBeanServer mbServer = ManagementFactory.getPlatformMBeanServer();
        mbServer.registerMBean(new ServerOperationMXBean() {
            @Override
            public void stop() {
                lunarServer.stop();
                future.cancel(true);
                if (!service.isShutdown()) {
                    service.shutdown();
                }
            }
            @Override
            public void reload() {
                lunarServer.reload();
            }
        },  new ObjectName("at.orz.lunar.LunarBootstrap:name=LunarServer"));

    }

}

シングルインスタンスモデルにするか、プロトタイプモデルにするか迷ったのでclsMapとかでClass定義を保持しているわけです。今回はシングルインスタンスモデルにしたので実はclsMapは必要ありません。

package at.orz.lunar;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Properties;

public class LunarClassLoader extends URLClassLoader {

    private HashMap<String, Class<? extends LunarTask>> clsMap = new HashMap<String, Class<? extends LunarTask>>();
    private HashMap<String, LunarTask> insMap = new HashMap<String, LunarTask>();

    public LunarClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
        init();
    }

    @SuppressWarnings("unchecked")
    private void init() {

        try {

            Enumeration<URL> en = getResources("tasklist.properties");
            while (en.hasMoreElements()) {
                URL url =  en.nextElement();

                Properties prop = new Properties();
                InputStream is = url.openStream();
                prop.load(is);
                is.close();

                for (String clsName: prop.stringPropertyNames()) {
                    try {
                        Class<?> c = loadClass(clsName);
                        if (LunarTask.class.isAssignableFrom(c)) {
                            Class<? extends LunarTask> cls = (Class<? extends LunarTask>) c;
                            clsMap.put(clsName, cls);
                            System.out.printf("register class:%s%n", clsName);

                            LunarTask task = cls.newInstance();
                            task.init();
                            insMap.put(clsName, task);
                            System.out.printf("init class:%s%n", clsName);
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }

            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    void destroy() {
        for (LunarTask task : insMap.values()) {
            try {
                task.destroy();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        insMap.clear();
        clsMap.clear();
    }

    public LunarTask getTask(String className) {
        return insMap.get(className);
    }

    public Iterable<LunarTask> taskIterator() {
        return new HashMap<String, LunarTask>(insMap).values();
    }

}
package at.orz.lunar;

import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class LunarServer implements Runnable, ServerOperationMXBean {

    private LunarClassLoader loader;
    private volatile boolean running;
    private LunarServer() {

    }

    @Override
    public void run() {

        ExecutorService service = Executors.newFixedThreadPool(3);

        running = true;
        while (running) {
            for (final LunarTask task : loader.taskIterator()) {
                service.submit(new Runnable() {
                    @Override
                    public void run() {
                        task.execute();
                    }
                });
            }
            try {
                Semaphore sema = new Semaphore(0);
                sema.tryAcquire(10, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        service.shutdown();

    }

    @Override
    public void reload() {
        LunarClassLoader curLoader = loader;
        LunarClassLoader newLoader = new LunarClassLoader(curLoader.getURLs(), curLoader.getParent());
        this.loader = newLoader;
        curLoader.destroy();
    }

    @Override
    public void stop() {
        running = false;
    }

    public static LunarServer newInstance(URL[] urls, ClassLoader parent) {
        LunarServer server = new LunarServer();
        server.loader = new LunarClassLoader(urls, parent);
        return server;
    }

}
package at.orz.lunar;

public interface LunarTask {
    public void init();
    public void destroy();
    public void execute();
}
package at.orz.lunar;

public interface ServerOperationMXBean {
    public void stop();
    public void reload();
}

ここからタスクです。Eclipse上では別プロジェクトにした方が良いです。今回は2つのクラスを登録します。

package at.orz.lunar.task;

import at.orz.lunar.LunarTask;

public class SampleTask1 implements LunarTask {
    @Override
    public void destroy() {
        System.out.println("SampleTask1.destroy()");
    }
    @Override
    public void execute() {
        System.out.println("☆SampleTask1.execute()");
    }
    @Override
    public void init() {
        System.out.println("SampleTask1.init()");
    }
}
package at.orz.lunar.task;

import at.orz.lunar.LunarTask;

public class SampleTask2 implements LunarTask {
    @Override
    public void destroy() {
        System.out.println("SampleTask2.destroy()");
    }
    @Override
    public void execute() {
        System.out.println("★SampleTask2.execute()");
    }
    @Override
    public void init() {
        System.out.println("SampleTask2.init()");
    }
}

クラスパスのルートにtasklist.propertiesを作成します。

at.orz.lunar.task.SampleTask1
at.orz.lunar.task.SampleTask2

実行した後にSampleTask2の内容を変更(★を★★★★★に変更)した後にjconsoleからreloadを呼び出した結果が↓です。

register class:at.orz.lunar.task.SampleTask2
SampleTask2.init()
init class:at.orz.lunar.task.SampleTask2
register class:at.orz.lunar.task.SampleTask1
SampleTask1.init()
init class:at.orz.lunar.task.SampleTask1
★SampleTask2.execute()
☆SampleTask1.execute()
★SampleTask2.execute()
☆SampleTask1.execute()
☆SampleTask1.execute()
★SampleTask2.execute()
☆SampleTask1.execute()
★SampleTask2.execute()
register class:at.orz.lunar.task.SampleTask2
SampleTask2.init()
init class:at.orz.lunar.task.SampleTask2
register class:at.orz.lunar.task.SampleTask1
SampleTask1.init()
init class:at.orz.lunar.task.SampleTask1
SampleTask1.destroy()
SampleTask2.destroy()
☆SampleTask1.execute()
★★★★★SampleTask2.execute()
☆SampleTask1.execute()
★★★★★SampleTask2.execute()

リロード処理の所にログ仕込むの忘れたのでリロードが発端になっているかは確認できませんが、リロード処理は呼ばれています(; ・`д・´)
そしてリ新しいクラスの内容が読み込まれているのも確認できます。

※途中から書くのが面倒になったのがソースからわかっちゃうと思いますが、サンプルなので気にしない(・ε・)

ただし、このような処理はちょっとミスするとPermGenが解放されずにOutOfMemoryErrorを引き起こす原因になりやすいので気をつけましょー。(IBMのVM使えばいいじゃんとかそういう野暮な事は言わないの。)

 

Solr1.4のbackupのバグ

Solr1.4ではとあるURLにアクセスする事でバックアップ(スナップショット)を取れるようになっています。

http://wiki.apache.org/solr/SolrReplication

↑のSolrのWikiにもあるようにReplicationの機能をONにする必要がありますが、スレーブの設定をしなくても動作するのでマスタのみでもきちんと動作します。
以下のURLにアクセスするとデータディレクトリと同列に「snapshot.日時」というディレクトリを作成します。

http://master_host:port/solr/replication?command=backup

この時、日付はyyyyMMddHHmmssという書式が指定されているはずなのですが、実はHHがhhになっており、午後の18時に実行しても18にならずに06になってしまいます。

SnapShooterというクラスのDATE_FMTの指定を見ると思いっきり小文字で指定されています。 

むむー。バグだよね。これは。

まぁだいたいが早朝にバックアップをすると思うから気がつかないんだと思うけど。。

Amazonの関連商品

AmazonにはProduct Advertising APIというサービスがあります。ECSとかAWSとか言われていたやつです。

関連商品の取得方法にはいくつか方法があり大きく分けると、サーチ系のAPIのResponseGroupにSimilaritiesを指定するかSimilarityLookupというAPIを使うかです。

ItemLookupのResponseGroupに指定すると5件、SimilarityLookupを使うと10件取得できます。SimilarityLookupのResponseGroupにSimilaritiesを指定するとさらにその10件の関連商品を取得できます。(まぁこの場合は殆どが被っていますが・・・。)

ただしAmazonのホームページを見ると70件近くの関連商品が表示されています。
SimilarityLookupで複数のItemIdを指定しRandom抽出(和集合)を取得するようにしても、同じ集合が取れません(´・ω・`)
商品Aの関連商品A1~A10のASINがあって、A-A1の和集合、A-A2の和集合.... をマージ等、色々試してみましたがやっぱり公式サイトと同じリストを再現できません。あそこのリストどうやって作っているのでしょう(´・ω・)

煮詰まってしまったので公式サイトのHTMLから引っこ抜いてみました。

http://www.amazon.co.jp/dp/{ASIN}

このページにアクセスしますが、User-AgentをきちんとAjaxをサポートしているブラウザのやつに指定する必要があります。適当なUser-Agentつけて送ったらJavaScriptのコードが出力されませんでした。Firefox3.5のUser-Agentを使ったらJavaScbriptのコードも出力されました。

var purchaseAsinList = new Array("aaa","bbb","ccc");

この様なコードが出力されているのでパースするだけです。Javaなら↓のようなコードでぱぱっと。

int beginIndex = html.indexOf("var purchaseAsinList");
int endIndex = html.indexOf("\n", beginIndex);
String pscript = (html.substring(beginIndex, endIndex));

Matcher m = Pattern.compile("\"(\\w+)\"").matcher(pscript);
while (m.find()) {
    System.out.println(m.group(1));
}

微妙・・。きちんとAPIを叩いて処理したいです(´・ω・)
というわけで情報募集です。

Mecab辞書の作成~GOOキーワードランキング編~

  • Posted by: タムタム
  • 2009年8月 3日 00:15
  • 開発

  • Tags: Mecab

まだ不完全ですがGOOのキーワードランキングを取得して自動でMecabの辞書を作るプログラムを書いています。とりあえずはキーワードの取得と、そのキーワードのフリガナを取得してTSVファイルにまとめるところまで作りました。

あとはMecabの辞書作成スクリプトの入力になるCSVファイルを作成するだけです。
あとで日付毎にどれだけ単語が増えているのかといったデータも取ろうと思います。

perlでさくっと書いたのですが、perlは色々と便利ですねー。関数の仕様が気に入りませんがそこは目をつぶります。なによりも自分のサーバだとモジュールが無い!とかそういう事を気にしないで気兼ねなくinstallしまくれるのが良いです。HTTP通信とかperlでも書けるのですがcurlとnkf使っていたり、xmlはdomで処理しないで正規表現で処理していたりと無茶していますが、さくっとtry and errorができるのが良いですね!

あ、TSVファイルはこちらで公開中です。
http://tech.orz.at/labs/

↑随時更新しているので、時々「おや?」と思うことがあるかもしれませんが気にしない方向で。

Solaris10でmemcached1.4をビルドする

http://memcached.googlecode.com/files/memcached-1.4.0.tar.gz

-は削除、+は追加という意味です。

memcached.h 467行目を修正

-extern void drop_privileges();
+extern void drop_privileges(void);

solaris_priv.cの11行目を修正

+extern void drop_privileges(void);

testapp.cの236行目を修正

-sprintf(pidfile, "/tmp/memcached.%d", getpid());
+sprintf(pidfile, "/tmp/memcached.%ld", getpid());

あとは普通にビルドをすれば通ります。今回リンクしたlibeventは「libevent-1.4.11-stable」です。

参考までにuname -aとgcc -vの結果を。

[tamtam@tamsvr11 work]$ uname -a
SunOS tamsvr11 5.10 Generic_137138-09 i86pc i386 i86pc Solaris
tamtam@tamsvr11 work]$ gcc -v
usr/local/lib/gcc/i386-pc-solaris2.10/3.4.6/specs から spec を読み込み中
コンフィグオプション: ../configure --with-as=/usr/ccs/bin/as --with-ld=/usr/ccs/bin/ld --enable-shared --enable-languages=c,c++,f77
スレッドモデル: posix
gcc バージョン 3.4.6

参考情報
http://groups.google.com/group/memcached/browse_thread/thread/a8f346a0084d9828/11428cd7bb11a374?lnk=gst#11428cd7bb11a374

情報を教えてくれたtmaesaka氏に感謝(`・ω・)

MySQLのTimestamp

MySQLのTimestampを勘違いしていました。
(そしてkazeburoさん素早い突っ込みありがとう( `・ω・)) 

OracleのTimestampとは全然違って、そういう場合はDateTime型を使うのが正しいらしい。Timestampのサイズが妙に小さいので気に入っていたのですが(´・ω・) で、何が違うのかと言うと、

  • データ追加時、または更新時、現在日時を自動で設定する。
  • データ更新時で更新前後で値の変化がない場合、TIMESTAMP 型の項目も更新しない。
  • NULL 値を設定した場合、現在日時が設定される。

(引用元)http://minazuki.cocolog-nifty.com/kaimemo/2008/01/mysql_timestamp_6d3b.html

というわけで、基本的にはレコードの更新時間が入るみたいです。

危ない危ない。
この前の内製アプリにおもいっきりTimestamp使っていました(;´Д`)

他にも色々勘違いしているものがありそうだ(((( ;゚д゚)))

OracleでCLOBに高速アクセスする方法

ちょっと釣りみたいなタイトルです。

LOBに関してはOracleが分厚いPDFファイルを用意しているので、そちらを参照して理解するのが良いと思います。今回はCLOBへアクセスする速度が速くなるわけではなく、いかにCLOBへアクセスさせないようにする方法です。アクセス速度自体はchunkのサイズとか気にすれば良いと思います。

そもそもなぜCLOBを使うのか?

これはVARCHAR2が4000バイトまでのため、AL32UTF8のデータベースの場合、日本語は最大4バイト消費します。(マニュアルによると、4バイトのUTF8は内部で6バイト使うみたいな事が書いてありましたので、ちょっと検証する必要はありそうです。)
ちなみに全角文字が2バイトという神話はSJISやEUC(一部3バイトですが)の時代の話です。UTF8では半角カナや漢字は3バイト、一部の漢字は4バイト使います。

さて、CLOBに入れるデータは常に4000バイト以上とは限りません。4000バイトで収まる場合はVARCHAR2のカラムに入れて収まらない場合はCLOBのデータに入れれば良いと思います。つまり1000バイトの文字列であればCLOBに入れる必要は無いわけです。今回はぱぱっと対応する策(松竹梅で言う梅コース)でどれほど効果があるか確認してみました。

COMMENT_TEXTがCLOBの列です。梅コースのためVARCHAR2の列はありません。LENGTH関数はCLOBに対しては文字数を返します。(LENGTHBはCLOBには適用できません)。CLOB中にはマルチバイト文字が2バイトで入っているという噂なので(UCS2で保存してるの??) 2000で判定しています。(サロゲート文字については未検証)。TO_CHARでVARCHAR2に変換しています。

SELECT
    CASE
        WHEN LENGTH(COMMENT_TEXT) > 2000 THEN NULL
        ELSE TO_CHAR(COMMENT_TEXT)
    END AS COMMENT_TEXT_A,
    CASE
        WHEN LENGTH(COMMENT_TEXT) > 2000 THEN COMMENT_TEXT
        ELSE NULL
    END AS COMMENT_TEXT_B
FROM DIARY_COMMENT

上記のSQLを実行すると1014レコード帰ってきます。
全部CLOBとして取得する場合は3120msかかりますが、上記のSQLでは125ms程度になりました。
※CLOBとしては2レコードしかありませんでした

ものによりますが、4000バイト以上使う割合が少ないものではこういうやり方もありなのではないかと思います。

最速はおそらくVARCHAR2(4000)の列をいくつか作って分割するという方法とかあると思います。設計が美しくないですけど。。。

Solaris10にlua-5.1.4をインストール

Solaris10にLua-5.1.4をインストールしてみます。

事前にgcc4をインストールしないと警告がいっぱい出ます。gcc4のインストールは別の記事に書いてあるのでそちらを参照。

wget http://www.lua.org/ftp/lua-5.1.4.tar.gz
gtar -zxvf lua-5.1.4.tar.gz
cd lua-5.1.4
gmake solaris
gmake install INSTALL_TOP=/usr/local

無事にビルドできました。

まぁ・・・Luaはパッケージがあるのでpkgaddで入れれば良いと思います。

Solaris10でgcc-4.4.0をインストールする

Lua5.1.4をSolaris10の素のままでビルドしようとすると警告が沢山でるのでgcc-4.4.0をインストールしてみます。ちなみに警告は「visibility attribute not supported in this configuration」といった感じのものでgcc4からサポートされたやつみたいです。staticな関数をライブラリの外に出す/出さないを指定できるgcc拡張のようです。

またgcc4.1以降はlibgmpとlibmpfrが必要との事なので、これらを事前にビルドします。

まずはlibgmpをwgetで取得してgtarで展開します。その後以下の手順でビルド。

cd gmp-4.2.4
./configure --prefix=/usr/local/gcc4 --build=i386-sun-solaris2.10
gmake
gmake check
gmake install

ちなみに、--buildオプションを指定しないとビルドに失敗しました。どうやら「core2-pc-solaris2.10」と認識されていたのが原因のようです。こちらのページを参考にさせていただきました。

次にlibmpfrをビルドします。こちらもwgetで取得してgtarで展開します。その後以下の手順でビルド。

cd mpfr-2.4.0
./configure --prefix=/usr/local/gcc4 --with-gmp=/usr/local/gcc4
gmake
gmake check
gmake install

これでgccをビルドする準備が整いました。
その前に、LD_LIBRARY_PATHに/usr/local/gcc4/libを入れておかないとlibgccのコンパイルでこけます。
gcc-core-4.4.0.tar.gzとgcc-g++-4.4.0.tar.gzを取得してgtarで展開します。

cd gcc-4.4.0

./configure \
--prefix=/usr/local/gcc4 \
--with-ar=/usr/ccs/bin/ar \
--with-as=/usr/ccs/bin/as \
--with-ld=/usr/ccs/bin/ld \
--enable-shared \
--enable-languages=c,c++ \
--with-gmp=/usr/local/gcc4 \
--with-mpfr=/usr/local/gcc4

gmake bootstrap
gmake install

一応確認。

/usr/local/gcc4/bin/gcc -v
Using built-in specs.
Target: i386-pc-solaris2.10
コンフィグオプション: ./configure --prefix=/usr/local/gcc4 --with-ar=/usr/ccs/bin/ar --with-as=/usr/ccs/bin/as --with-ld=/usr/ccs/bin/ld --enable-shared --enable-languages=c,c++ --with-gmp=/usr/local/gcc4 --with-mpfr=/usr/local/gcc4
スレッドモデル: posix
gcc version 4.4.0 (GCC)

そして環境変数のPATHにも/usr/local/gcc4/binを入れます。 
Luaのビルドは別の記事で。

MySQLのLIMIT

知り合いよりMySQLの使い方がひどいんですって話があって、その中でLIMITの話があったのでこういう事なのかなーと推測してみた。

その話によるとなんでもかんでもLIMITをつけている。SELECT * FROM XXXX LIMIT 1 みたいな事をいろんなところでやっているという事でした。MySQLはそんなに詳しくないのでDB2のOPTIMIZE FOR 1 ROWみたいなおまじないでつけてるんじゃないのかなーと思いつつ、実際に実行計画をとってみました。

テーブルを専用に用意するのは面倒なので、家のテーブルから適当にデータが入っているやつをチョイス。

mysql> describe BC2_ST_POSTLIST;
+------------+---------------+------+-----+-------------------+-----------------------------+
| Field      | Type          | Null | Key | Default           | Extra                       |
+------------+---------------+------+-----+-------------------+-----------------------------+
| HASH       | bigint(20)    | NO   | MUL | NULL              |                             |
| LINK       | varchar(1024) | NO   |     | NULL              |                             |
| STATUS     | int(11)       | NO   |     | 0                 |                             |
| REGISTDATE | timestamp     | NO   | MUL | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+------------+---------------+------+-----+-------------------+-----------------------------+
4 rows in set (0.00 sec)
mysql> show index from BC2_ST_POSTLIST;
+-----------------+------------+-----------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table           | Non_unique | Key_name              | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-----------------+------------+-----------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| BC2_ST_POSTLIST |          1 | IDX_BC2_ST_POSTLIST   |            1 | HASH        | A         |     4166553 |     NULL | NULL   |      | BTREE      |         |
| BC2_ST_POSTLIST |          1 | IDX_BC2_ST_POSTLIST_2 |            1 | REGISTDATE  | A         |       41253 |     NULL | NULL   |      | BTREE      |         |
+-----------------+------------+-----------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
2 rows in set (0.02 sec)
mysql> select count(*) from BC2_ST_POSTLIST;
+----------+
| count(*) |
+----------+
|  4164791 |
+----------+
1 row in set (0.00 sec)

これにSQLを発行してみる。

mysql> explain select * from BC2_ST_POSTLIST LIMIT 1;
+----+-------------+-----------------+------+---------------+------+---------+------+---------+-------+
| id | select_type | table           | type | possible_keys | key  | key_len | ref  | rows    | Extra |
+----+-------------+-----------------+------+---------------+------+---------+------+---------+-------+
|  1 | SIMPLE      | BC2_ST_POSTLIST | ALL  | NULL          | NULL | NULL    | NULL | 4166649 |       |
+----+-------------+-----------------+------+---------------+------+---------+------+---------+-------+
1 row in set (0.00 sec)

おや?rowsがひどいことになってます。(件数が上のCOUNTと違うのはオンラインで稼働中のテーブルなので・・)。これではLIMITをつけない場合と同じですね。OracleのCount Stopのような実行計画になると思っていました。

MySQLのリファレンスによると、色々かかれていますね。
http://dev.mysql.com/doc/refman/5.1/ja/limit-optimization.html

試しにORDER BYをINDEXをつけている列につけてみました。

mysql> explain select * from BC2_ST_POSTLIST ORDER BY REGISTDATE LIMIT 1;
+----+-------------+-----------------+-------+---------------+-----------------------+---------+------+------+-------+
| id | select_type | table           | type  | possible_keys | key                   | key_len | ref  | rows | Extra |
+----+-------------+-----------------+-------+---------------+-----------------------+---------+------+------+-------+
|  1 | SIMPLE      | BC2_ST_POSTLIST | index | NULL          | IDX_BC2_ST_POSTLIST_2 | 4       | NULL |    1 |       |
+----+-------------+-----------------+-------+---------------+-----------------------+---------+------+------+-------+
1 row in set (0.00 sec)

rowsが1件になりました。
というか、そもそもWHEREやORDER BYを指定しないクエリにLIMITをつける意味が私にはわかりませんでした(´・ω・)
意味というか、それで要件を満たせるユースケースってどんなのがあるかなーと。
ただし、

MySQL が要求された行数をクライアントに送信すると、クエリが中止されます(SQL_CALC_FOUND_ROWSを使用していない場合)。

とあるので、この場合は1件フェッチした場合にクエリが中断されるんだと思います。それを確認してみました。

mysql> explain select SQL_CALC_FOUND_ROWS * from BC2_ST_POSTLIST ORDER BY REGISTDATE LIMIT 1;
+----+-------------+-----------------+------+---------------+------+---------+------+---------+----------------+
| id | select_type | table           | type | possible_keys | key  | key_len | ref  | rows    | Extra          |
+----+-------------+-----------------+------+---------------+------+---------+------+---------+----------------+
|  1 | SIMPLE      | BC2_ST_POSTLIST | ALL  | NULL          | NULL | NULL    | NULL | 4174728 | Using filesort |
+----+-------------+-----------------+------+---------------+------+---------+------+---------+----------------+
1 row in set (0.00 sec)

ふむふむ。実際にEXPLAINじゃなくて実行してみた。
データの中身は見せられないので切り取り。

mysql> select SQL_NO_CACHE SQL_CALC_FOUND_ROWS * from BC2_ST_POSTLIST LIMIT 1;
1 row in set (5.20 sec)
mysql> select SQL_NO_CACHE * from BC2_ST_POSTLIST LIMIT 1;
1 row in set (0.00 sec)

ふむふむ。ということはやっぱり中断はされているっぽいですね。
MySQLってOracleのように何バイト読み込んだかみたいなものって見れないのかな・・。

いいお勉強になりました。
うーん。これくらいじゃ目くじら立てる原因にはならなそうなので他の要因がありそうです(まぁ積もりにつもった様々なイライラだと思いますがw)。テーブルの全データをメモリに載せようとしちゃうのかな。実行計画上は全件スキャンするようになっているし。
これ以上は知識が無いのでソース読んだ方が早そう(´・ω・)

まぁテーブル構造知らないしどんな要件でどんなSQLを発行しているのか分からないので、なんとも言えませんでした(;´Д`)

 

LD_RUN_PATHの確認方法

忘れちゃうのでφ(`д´)メモメモ...

dump -Lv xxxx | grep RPATH

xxxxには実行ファイルを指定。

例えば一個前の記事のttserverに対して実行すると、

[tamtam@tamsvr11 bin]$ dump -Lv ./ttserver  | grep RPATH
[16]    RPATH           /export/home/tamtam/service/tc-1.4.17/lib:/export/home/tamtam/service/tt-1.1.23/lib

 

Solaris10でTCとTTをビルドするメモ

Solaris10でTCとTTが動作するようになってからというもの、最新版をずっと試していなかったので久しぶりにビルドしてみました。ちなみに会社にもインストールしていますが、会社ではLua拡張も組み込んでいます。社内用に構文や機能が追加してあるLua拡張版があるのですが、私は標準のままのLuaを使っています。

今回はLuaをサポートしない場合のビルドメモ。

Luaを組み込んでビルドします。他の記事にLuaにビルドの記事がありますので、Luaについてはそちらを参照。

wget http://tokyocabinet.sourceforge.net/tokyocabinet-1.4.17.tar.gz
gtar -zxvf tokyocabinet-1.4.17.tar.gz
cd tokyocabinet-1.4.17
./configure \
  CC=gcc \
  --prefix=/export/home/tamtam/service/tc-1.4.17
gmake
gmake install
wget http://tokyocabinet.sourceforge.net/tyrantpkg/tokyotyrant-1.1.23.tar.gz
gtar -zxvf tokyotyrant-1.1.23.tar.gz
cd tokyotyrant-1.1.23
./configure \
  CC=gcc \
  LDFLAGS="-R/export/home/tamtam/service/tc-1.4.17/lib -R/export/home/tamtam/service/tt-1.1.23/lib" \
  --prefix=/export/home/tamtam/service/tt-1.1.23 \
  --with-tc=/export/home/tamtam/service/tc-1.4.17 \
  --enable-lua \
  --with-lua=/usr/local
gmake
gmake install

無事にビルドできました。

これはひどい

http://www.atmarkit.co.jp/fjava/rensai4/webjousiki08/webjousiki08_3.html

@ITってSI業界だと結構参考にされるサイトだと思うのですが(違っていたらごめんなさい)、こういうコードを鵜呑みのにして業務システムが作られていると思うとぞっとします。

つっこみどころとしては、

・PreparedStatement/ResultSetをCloseしていない

ConnectionをCloseした段階でPreparedStatementもCloseされるって?
コネクションプールをしている場合も同じ事が言えますか?
PreparedStatementCacheをしている場合も同じ事が言えますか?
「データベースに接続したら後始末は確実に」という記事を参照しろと書いてあるにもかかわらず、掲載されているコードは参照してかつ参考しているとは思えない。

 

・while(rs.next())の部分

これWhileである必要ないですよね。
Whileで回すって事はIdはユニークではないのでしょうか。
でもCount==1の時のみ処理しています。
何がしたいのか意味がわかりません。こんな冗長で分かりづらいコード書かないでIfで良いのでは。
if(rs.next()){ 一致データを取得 } else{ 認証失敗 }

 

こんなコード出してきたらグーパンチですよヽ(`Д´)ノ

Struts2のJspTemplateEngineで文字化けを起こさないようにする初期設定

前の記事でIncludeクラスのエンコード設定がおかしいため、JspTemplateEngineで文字化けが発生する問題を取り上げましたが、今回はそれを回避するコードを書いてみました。
IncludeクラスのInstanceを1回作成し、setDefaultEncodingを実行すればstaticなフィールドにデフォルトエンコードが設定されるのでContextListenerの中でそういったコードを書きます。

ContextListenerの中でDispatcherへListenerを追加します。そしてそのListenerの中で初期化処理を行います。なぜこういう風にするのかと言うと、Struts2のコントローラーはFilterで実装されています。ContextListenerで直接初期化処理を書いてしまうと、Filterの初期化よりも前に実行されてしまい、Struts2の初期化処理が行われていない状態で実行されてしまう事になるからです。

 

public class DefaultContextListener implements ServletContextListener {
	private DispatcherListener dispatchListener = new DispatcherListener() {

		private ValueStackFactory valueStackFactory;
		private String defaultEncoding;

		@SuppressWarnings("unused")
		@Inject
		public void setValueStackFactory(ValueStackFactory valueStackFactory) {
			this.valueStackFactory = valueStackFactory;
		}

		@Inject(StrutsConstants.STRUTS_I18N_ENCODING)
		@SuppressWarnings("unused")
		public void setDefaultEncoding(String val) {
			defaultEncoding = val;
		}

		@Override
		public void dispatcherInitialized(Dispatcher du) {
			du.getContainer().inject(this);
			ValueStack stack = valueStackFactory.createValueStack();
			new Include(stack, null, null).setDefaultEncoding(defaultEncoding);
		}

		@Override
		public void dispatcherDestroyed(Dispatcher du) {

		}
	};

	@Override
	public void contextInitialized(ServletContextEvent sce) {

		Dispatcher.addDispatcherListener(dispatchListener);

	}

	@Override
	public void contextDestroyed(ServletContextEvent sce) {

		Dispatcher.removeDispatcherListener(dispatchListener);

	}

}

 

月末日を求めるプログラム

検索すると結構見つかるものですが、JavaのCalendar#getActualMaximumを使った間違ったサンプルを多く見かけるので正しいやり方をメモメモ。相手の力量をはかるためのクイズとして使っても良いかもしれませんね。

カレンダーがgetInstance()じゃない事は言及しません。
以下は2006年2月の月末日を求めるコードです。

●とりあえず間違ったコード

GregorianCalendar c = new GregorianCalendar(Locale.JAPAN);
c.set(Calendar.YEAR, 2006);
c.set(Calendar.MONTH, 2 - 1);

System.out.println(c.getActualMaximum(Calendar.DAY_OF_MONTH));

何がいけないか。

月末に実行してみるとわかると思います。
仮に今日が2009年3月31日だとします。

まず1行目でCの値は2009年3月31日になります。(時間は省略)
次に2行目でCの値は2006年3月31日になります。
3行目でCの値は2006年2月31日になります。※
→2006年3月3日になります。
2006年3月の月末日は31日のため、31が表示されます。

というわけで、

●正しいコード

GregorianCalendar c = new GregorianCalendar(Locale.JAPAN);
c.set(Calendar.YEAR, 2006);
c.set(Calendar.MONTH, 2 - 1);
c.set(Calendar.DAY_OF_MONTH, 1);

System.out.println(c.getActualMaximum(Calendar.DAY_OF_MONTH));

Subversionのコミットメールをpythonで書いてみた

なぜpythonかと言うと、rubyは入っていないしperlは5.6でNet::SMTP使えないしsendmail行方不明だし・・・。pythonはcvs2svnで必要だったのでインストールしてもらっていたのでした。PERLも5.10をインストールして貰えばいいんですが、依頼をして待つ時間を考えるとpythonで書いた方が早いだろうと判断したからです。

動けばいいやーレベルで書いたので穴があるかもしれないです。ちなみにバージョンは2.6で確認しました。

●post-commit

#!/bin/bash

REPOSITORY="$1"
REVISION="$2"
MAILER="/data/svn/proj1/hooks/mail.py"

export LANG=ja_JP.UTF-8
export LC_ALL=ja_JP.UTF-8

$MAILER $REPOSITORY $REVISION

●mail.py

#!/usr/local/python/bin/python
# -*- coding: utf-8 -*-

import re
import sys
import os
import subprocess
import smtplib
from email.MIMEText import MIMEText
from email.Header import Header
from email.Utils import formatdate

SMTP_HOST = 'xxx.xxx.xxx.xxx'
SMTP_PORT = 25
SVNLOOK = '/usr/local/svn/bin/svnlook'
subject_prefix = "[svn][proj1]"
from_addr = 'no-reply@example.com'
to_addr = ["user1@example.com", "user2@example.com"]


def send(from_addr, to_addr, msg):
    s = smtplib.SMTP(SMTP_HOST, SMTP_PORT)
    s.sendmail(from_addr, to_addr, msg.as_string())
    s.close()

def create_message(from_addr, to_addr, subject, body, encoding):
    msg = MIMEText(body, 'plain', encoding)
    msg['Subject'] = Header(subject, encoding)
    msg['From'] = from_addr
    msg['To'] = ', '.join(to_addr)
    msg['Date'] = formatdate()
    return msg

if __name__ == '__main__':

    REPOS = sys.argv[1]
    REV = sys.argv[2]

    # exec svnlook
    p = subprocess.Popen(["%(SVNLOOK)s info %(REPOS)s -r %(REV)s" % locals()], shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    lines1 = p.stdout.read().splitlines()
    AUTHOR = lines1.pop(0)
    DATE = lines1.pop(0)
    lines1.pop(0)
    LOG = "\n".join(lines1)

    # exec dirs-changes
    p = subprocess.Popen(["%(SVNLOOK)s dirs-changed %(REPOS)s -r %(REV)s" % locals()], shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    dirchanged = ""
    for dir in sorted(p.stdout.read().splitlines(True)):
        dirchanged += "    " + dir

    # exec changed
    p = subprocess.Popen(["%(SVNLOOK)s changed %(REPOS)s -r %(REV)s" % locals()], shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    r = re.compile('^(.).  (.*)$')
    adds = []
    dels = []
    mods = []
    for file in (p.stdout.read().splitlines(True)):
        (tmp, code, path, tmp) = r.split(file)
        if tmp == 'A':
            adds.append("    " + path)
        elif tmp == 'D':
            dels.append("    " + path)
        else:
            mods.append("    " + path)

    body  = "Author: " + AUTHOR + "\n"
    body += "Date: " + DATE + "\n"
    body += "New Revision: " + REV + "\n"
    body += "\n"
    body += "Log:\n"
    body += LOG
    body += "\n\n"
    body += "Direcoties:\n"
    body +=  dirchanged
    body += "\n"
    if len(adds) != 0:
        body += "Added:\n"
        body += "\n".join(sorted(adds))
    if len(dels) != 0:
        body += "Removed:\n"
        body += "\n".join(sorted(dels))
    if len(mods) != 0:
        body += "Modified:\n"
        body += "\n".join(sorted(mods))

    subject = subject_prefix + " r" + REV + " - " + AUTHOR + " -"
    msg = create_message(from_addr, to_addr, subject, body, 'UTF-8')
    send(from_addr, to_addr, msg)

Tokyo Tyrantが公式にSolaris対応しました

Tokyo CabinetもTokyo TyrantもSolaris10で動作するようになりました。ビルドもすんなり通ります。

\(^o^)/ヤター!

Luaをくっつけて色々試してます。

 

Solaris8やSolaris9では動かないですって・・・?
知らない!!!

Solarisは8から/dev/poll使えるのでそれで・・・(´∩ω∩`)

tokyo tyrantのSolaris対応(event ports)を書き直してみた

前回の記事の対応ではスピードが出ません。
というわけでちょっとport_getnの所を修正しました。
make checkでそこそこの速度で全部通るようになりました。

あ、補足ですがtokyo cabinetの最新版はSolaris10で何も指定しなくても普通に通るようになりました。

今回対象にしたのは、tokyotyrant-1.1.14です。(今日付で最新版が出ていますが対応は基本的に同じでいけると思います)

開発環境はSolaris10 x86_64です。uname -aの結果は、
「SunOS tamsvr11 5.10 Generic_137138-09 i86pc i386 i86pc Solaris」です。

というわけで以下修正ソースです。

 

Continue reading

桁の読み間違い

0の数が多いと読み間違える事ってありますよね(´・ω・)
そんな恥ずかしいお話。

ソースを斜め読みしたら、たまたま↓のコードが目に入りました。

  if(gettimeofday(&tv, NULL) == 0){
    ts.tv_sec = tv.tv_sec;
    ts.tv_nsec = tv.tv_usec * 1000 + TTWAITREQUEST * 1000000;
    if(ts.tv_nsec >= 1000000000){
      ts.tv_nsec -= 1000000000;
      ts.tv_sec++;
    }
  } else {
    ts.tv_sec = (1ULL << (sizeof(time_t) * 8 - 1)) - 1;
    ts.tv_nsec = 0;
  }

あれ。
→ 桁上げ処理の所がwhileじゃなくてifだ。
→ 1回しか処理していないね。
→ TTWAITREQUESTの値ってなんだっけ
→ defineで200固定なのね
→ あれ。じゃぁwhileで何回か繰り上げないとダメじゃん!
→ ・・・。
→ ( ゚ ρ ゚ )ボー
→ ・・・
→ 0の数1個間違いました。ごめんなさい。

 

寝ぼけ眼でソース見るのは危険です。もう1回二度寝します( ・д⊂

Tokyo TyrantをSolaris10で使えるようにする eventports編

※書き直しました。(2009/2/16)
http://mt.orz.at/archives/2009/02/tokyo-tyrantsol-2.html こちらの記事をどうぞ。

 

最初に断っておきますが、このやり方はとてもよろしくない方法で対応しています。
本来ならばEPOLL関連の関数に準ずる形でエミュレートするのが正しい方法だと思うのですが、力不足によりttutl.cの中に#ifdefでSolaris用に処理を追加するという暴挙に出てしまいました(´・д・`)
ですので、参考にしてはいけません。

言い訳をするとですね、waitの中でONESHOTフラグでない場合は再登録(port_assosiate)するという方法が動かなかったのです。後できちんと調べてmyconfの修正だけでいけるようにガンバリマス(*・ε・*)

・・・今書いてて思うのですが、ttの場合エミュレートしないといけないepoll系の関数は3つしかなくて、epoll_create()は最初の初期化、epoll_ctlは監視の登録/解除、epoll_waitは読み込み可能なFDを返す、という至極単純な動作なので自前である程度のフラグを管理すればどんな方法でも動くはずだと思いました。

というわけでソースを載せていきます。

Continue reading

tokyo cabinet1.4.4をsolaris10でコンパイルする

最新版をSolaris10でコンパイルしてみたです。
いつの間にかSunOSの定義でごにょごにょと分岐が追加されていました!ありがとうmikioさん!(もしかしてこのBLOG見てるのかな(*´∀`))

これでSolaris10でもすんなりビルドが・・・通りませんでした(´・ω・)
堅牢なOSなのは認めるし最近のコンテナやZFS、Dtraceはすばらしい技術だと思うけど、正直なところビルドくらい何も考えないで通って欲しい。。゚(゚´Д`゚)゚。  お願いしますよ。Sunマイクロシステムズ様。

で、やっぱりrealpath関係で怒られました。

Solaris10でrealpathが使えるようになるには、以下の条件を満たす必要があるみたいです。

#if defined(__EXTENSIONS__) || \
        (!defined(_STRICT_STDC) && !defined(__XOPEN_OR_POSIX)) || \
        defined(_XPG4_2)

std=c99をつけると、 __STRICT_ANSI__マクロが定義されます。
/usr/include/sys/feature_tests.hをひもといていくと、_STRICT_STDCが定義されるので上記の条件(の2行目のやつ)を満たさなくなってしまうようです。ここ(manpage)を見ると色々書いてあります。

これを回避するには以下の2つの方法があります。(正確にはもっとありますけど、試していません)

  • __EXTENSIONS__を定義する
  • std=c99をstd=gnu99にする

どれくらい影響範囲があるのか分からないので自己責任で好きな方を選択すれば良いと思います。

前者の場合は、以下のようにconfigureで指定します。

./configure CFLAGS="-D__EXTENSIONS__"

後者の場合は、configureとconfigure.inに記述されている内容を置換します。(configureした後にMakefileを修正しても良いですけど)

perl -i -p -e "s/c99/gnu99/g;" configure*

あとは普通にビルドをします。

gmake >/dev/null
tcutil.c:7401: 警告: 初期化子のまわりのブレースを欠いています
tcutil.c:7401: 警告: (`tcglobalmutexonce.__pthread_once_pad' の初期化は不完全です)
tcutil.c: In function `tcbwtdecode':
tcutil.c:7717: 警告: statement with no effect
tcutil.c: In function `tcbwtencode':
tcutil.c:7677: 警告: statement with no effect
ar: creating libtokyocabinet.a

出ている警告は全て動作に対して影響はありません。最初の2個の警告は前回の記事に回避策が書いてあります。(でも、わざわざ修正するような内容ではありません(*・ε・*))

ちなみにマクロの定義一覧を出したい場合は以下のような感じで出せます(2個ほどマクロ定義してあるのはTokyo Cabinetビルドしたらこのマクロももれなくついてきたので。)。実際のソースに対してやると何千行も出てきてしまいますが、以下の場合はその環境で最低限定義されるものが出てきます。(100行もないはずです)

 gcc -std=gnu99 -D_GNU_SOURCE=1 -D_REENTRANT -E -dM -x c /dev/null

 

ちょっと気になった点について。
1.4.2,3,4辺りでpthread_yield()の実装が毎回変更されていて今はusleepになっていますが、これってやっぱりusleepのが速いからなんでしょうか。Solaris10の場合、sched_yieldの実装はシステムコールのthr_yieldになっていて、これがスレッドセーフだから遅いのと、yieldすると必要ない場合でもコンテキストスイッチが走るから・・・なのかにゃ(´・ω・)

 

svnkitを使ってSubversionを操作する

例えばツールで、何かをバージョン管理したい時はバックエンドにSVNを使っていたりするのですが、Davによる自動コミットがONになっていない環境だとファイルの更新が面倒です。ファイルの追加はSVNコマンドでIMPORTを使えば可能ですが、既に存在するファイルの更新は事前にローカルのワークファイルが存在しないとコミットできません。

というわけで、既に登録されているファイルを、チェックアウトしなくても更新できるプログラムを書いてみました。
本当はきちんとクラス化してあるのですが、長くなるので主要な部分を切り貼りしていきます。

// SVNKitの初期化
DAVRepositoryFactory.setup();
// 引数にはSVNのURLを指定します
// 認証とかの設定をしてレポジトリオブジェクトを返します
private SVNRepository getRepos(String baseUrl) throws SVNException {
	SVNURL url = SVNURL.parseURIDecoded(baseUrl);
	SVNRepository repos = SVNRepositoryFactory.create(url);
	if (userName != null) {
		ISVNAuthenticationManager authManager =
			SVNWCUtil.createDefaultAuthenticationManager(
					this.userName,
					this.password == null ? "" : this.password
			);
		repos.setAuthenticationManager(authManager);
	}
	return repos;
}

まずはファイルをSVNに追加するコードです。

/**
 * ファイルをSVNに追加する。
 * 実際に追加される場所は、baseUrl + filePath
 * @param baseUrl SVNのベースのURL
 * @param filePath ファイル名
 * @param fileData 追加するデータの内容
 * @param logMessage コミットログの内容
 * @return コミットログ
 */
public SVNCommitInfo addFile(String baseUrl, String filePath, byte[] fileData, String logMessage) {

	SVNCommitInfo cInfo = null;
	try {

		SVNRepository repos = getRepos(baseUrl);

		ISVNEditor editor = repos.getCommitEditor(logMessage, null, true, null);
		try {
			editor.openRoot(-1);
			editor.addFile(filePath, null, -1);
			editor.applyTextDelta(filePath, null);
			SVNDeltaGenerator deltaGenerator = new SVNDeltaGenerator();
			String checksum = deltaGenerator.sendDelta(filePath, new ByteArrayInputStream(fileData), editor, true);
			editor.closeFile(filePath, checksum);
			editor.closeDir();

			cInfo = editor.closeEdit();
		} catch (SVNException e) {
			editor.abortEdit();
			throw e;
		}

	} catch (SVNException e) {
		throw new RuntimeException(e);
	}

	return cInfo;
}

次はSVNのファイルの内容を更新するコードです。 上の登録のコードもそうですが、基本は差分(DELTA)を計算してそれを送信しているだけです。

/**
 * SVNのファイルの内容を更新する(変更する)。
 * 実際に変更されるファイルの場所は、baseUrl + filePath
 * @param baseUrl SVNのベースのURL
 * @param filePath ファイル名
 * @param fileData 変更後のデータの内容
 * @param logMessage コミットログの内容
 * @return コミットログ
 */
public SVNCommitInfo updateFile(String baseUrl, String filePath, byte[] fileData, String logMessage) {

	SVNCommitInfo cInfo = null;
	try {

		SVNRepository repos = getRepos(baseUrl);

		// 古いコンテンツを取得する
		ByteArrayOutputStream oldOut = new ByteArrayOutputStream();
		repos.getFile(filePath, -1, SVNProperties.wrap(Collections.EMPTY_MAP), oldOut);
		byte[] oldData = oldOut.toByteArray();

		ISVNEditor editor = repos.getCommitEditor(logMessage, null, true, null);
		try {
			editor.openRoot(-1);
			editor.openFile(filePath, -1);
			editor.applyTextDelta(filePath, null);

			SVNDeltaGenerator deltaGenerator = new SVNDeltaGenerator();
			String checksum = deltaGenerator.sendDelta(
					filePath,
					new ByteArrayInputStream(oldData),
					0,
					new ByteArrayInputStream(fileData),
					editor,
					true
					);
			editor.closeFile(filePath, checksum);
			editor.closeDir();
			cInfo = editor.closeEdit();
		} catch (SVNException e) {
			editor.abortEdit();
			throw e;
		}

	} catch (SVNException e) {
		throw new RuntimeException(e);
	}

	return cInfo;

}

複数ファイルを変更する場合、上記のメソッドを複数回呼び出すと別々のコミットになってしまいます。 まぁeditor.closeEdit()を複数回呼んでいるので、当然と言えば当然ですが。

というわけで、複数のファイルを1回で追加・更新するコードを書いてみます。
その前にファイルが存在するかを確認するメソッドを追加します。

public boolean exists(String baseUrl, String filePath) {
	try {

		SVNRepository repos = getRepos(baseUrl);
		SVNNodeKind nodeKind = repos.checkPath(filePath, -1);
		if (nodeKind == SVNNodeKind.NONE || nodeKind == SVNNodeKind.DIR) {
			return false;
		}

		return true;

	} catch (SVNException e) {
		throw new RuntimeException(e);
	}
}

複数のファイルを良い感じに更新するメソッドです。
BLOGにコードを書き写した時に手動で一部のメソッドを書き換えているのでコピペしても動かないかもしれません。

事前にexists等を求めているのはlockメソッドでエラーが出るからです。SVNKitのバグかどうかは知りません。

public static class SvnRequestEntry {
	public String filePath;
	public byte[] fileData;
	boolean exists;
	byte[] oldData;
}

/**
 * 複数のファイルをSVNに追加する。既にファイルが存在する場合は更新される。
 * また、コミットは1回で行われる。
 * @param baseUrl SVNのベースURL
 * @param entries 追加するデータのリスト
 * @param logMessage コミットログの内容
 * @return コミットログ
 */
public SVNCommitInfo putFiles(String baseUrl, List<SvnRequestEntry> entries, String logMessage) {

	SVNCommitInfo cInfo = null;
	try {

		SVNRepository repos = getRepos(baseUrl);

		// 事前にチェックを行う
		for (SvnRequestEntry entry: entries) {

			SVNNodeKind nodeKind = repos.checkPath(entry.filePath, -1);
			entry.exists = !(nodeKind == SVNNodeKind.NONE || nodeKind == SVNNodeKind.DIR);

			// 古いコンテンツを取得する
			if (entry.exists) {
				ByteArrayOutputStream oldOut = new ByteArrayOutputStream();
				repos.getFile(entry.filePath, -1, null, oldOut);
				entry.oldData = oldOut.toByteArray();
			}

		}

		ISVNEditor editor = repos.getCommitEditor(logMessage, null, true, null);
		try {

			editor.openRoot(-1);
			for (SvnRequestEntry entry: entries) {

				String filePath = entry.filePath;
				byte[] fileData = entry.fileData;
				boolean exists = entry.exists;
				byte[] oldData = entry.oldData;

				// 存在チェック
				if (!exists) {

					// 追加する
					editor.addFile(filePath, null, -1);
					editor.applyTextDelta(filePath, null);
					SVNDeltaGenerator deltaGenerator = new SVNDeltaGenerator();
					String checksum = deltaGenerator.sendDelta(filePath, new ByteArrayInputStream(fileData), editor, true);
					editor.textDeltaEnd(filePath);
					editor.closeFile(filePath, checksum);

				} else {

					// 更新する
					editor.openFile(filePath, -1);
					editor.applyTextDelta(filePath, null);

					SVNDeltaGenerator deltaGenerator = new SVNDeltaGenerator();
					String checksum = deltaGenerator.sendDelta(
							filePath,
							new ByteArrayInputStream(oldData),
							0,
							new ByteArrayInputStream(fileData),
							editor,
							true
							);
					editor.textDeltaEnd(filePath);
					editor.closeFile(filePath, checksum);

				}
			}
			editor.closeDir();
			cInfo = editor.closeEdit();

		} catch (SVNException e) {
			editor.abortEdit();
			throw e;
		}

	} catch (SVNException e) {
		throw new RuntimeException(e);
	}

	return cInfo;

}

使い方はこんな感じです。他のメソッドも同じ感じです。

SVNCommitInfo info1 = addFile(
	baseUrl,
	"test01.txt",
	"test01_init_data".getBytes(),
	"プログラムからからファイルを追加してみる\nテスト。"
	);

putFilesを呼び出すときは、SvnRequestEntryのfilePathとfileDataに値を入れて、 更新したいファイルの数だけList化すれば良いです。

Tokyo TyrantをSolaris10で使えるようにする 実装編その1

# TokyoとTyrant(Cabinet)の間はスペース入れるのが推奨みたいです(´・д・`)

それはさておき、

とりあえず動きましたヽ( `・ω・´)ノ

/dev/pollのサンプルが全然なかったので、Sunのページとlibeventを参考にしました。
(sun) http://docs.sun.com/app/docs/doc/816-5177/poll-7d?l=pt_br&a=view
(libevent) http://monkey.org/~provos/libevent/
(libev) http://software.schmorp.de/pkg/libev.html

今更だけどlibevも見れば良かった・・。
簡単なechoサーバのサンプルがあると嬉しかったのですが、見つけられなかった。゚(゚´Д`゚)゚。

とりあえず環境的なところで詰まったのは、/usr/ccs/bin/ldだと-soname指定が上手く動かない事。--with-ldとか指定してもダメだったので、とりあえず以下のページを参考にして/usr/local/bin/ldにシンボリックリンクを張りました。
http://www.rinneza.com/junya/tech/solaris10_x86/network/samba_3.2.4.html

あとはTokyo Cabinet同様にconfigure中のstd=c99をstd=gnu99に変更。

今後やることは、

今は最初に確保した配列を全部/dev/pollのハンドラに書き込んでいるので、これを必要な分だけ登録する。(配列[fd].xxxxって処理してるのを0,1,2,と使うように変更する。)
でもどっちが良いのかな・・。 ゥ─σ(・´ω・`*)─ン…

あとは↑の配列の確保をcallocで処理しているので、終了時にメモリ解放のコードを入れてあげる。
(終了時だから解放しなくても良いものなのかな・・? ってダメだよね・・ (´・д・`))

なんでcalloc使っているかと言うと、以下のようなコードで配列の添え字を求めているからです。

  int nfiles = 32000;
  struct rlimit rl;
  if (getrlimit(RLIMIT_NOFILE, &rl) == 0 &&
      rl.rlim_cur != RLIM_INFINITY)
    nfiles = rl.rlim_cur;

もうちょっと負荷かけたりスレッド周りの検証したらコードも公開します。
追加したコードはfprintfのデバッグコードも含めて100行くらいでした。

 

追記 2009/1/25 23:00

複数接続でIOがブロックされちゃうのと、切断時に何回もCLOSEを試みて↓のエラーが数秒出続けてしまう。その間は新規接続も受け付けてくれない・・。
2009-01-25T23:24:24+09:00       close failed
2009-01-25T23:24:24+09:00       connection finished
・・・

 

Tokyo TyrantをSolaris10で使えるようにする 方針編

Tokyo TyrantをSolaris10に対応させる方法として考えられる方針は以下になります。

  • epoll使っている部分の実装を書き換えちゃう
  • libeventを使う
  • epoll_create, epoll_ctl, epoll_waitをエミュレートする

ソースを見るとkqueueの場合はepollのエミュレートをしていてepollに寄せているみたいなので、とりあえず/dev/pollの実装も寄せる方向で実装してみようと思います。(libeventの使い方を覚えるのが面倒という理由もちょっと有り)

それにしてもSolarisユーザーほんと少ないですね(´・ω・`)
情報が少ない(´∩ω∩`)

Solaris10でTokyo Cabinetをビルドしてみた

Tokyo Cabinetってなんぞ?という人は、
http://tokyocabinet.sourceforge.net/
このページを。

Tokyo TyrantをSolaris10で動かすために、まずはTokyo Cabinetをビルドしようとしたらいろいろと怒られたので無理矢理ビルドを通してみました。正しい修正かどうかは知りません(*・ε・*)
なので指摘は大歓迎です。

環境は「SunOS tamsvr10 5.10 Generic_127128-11 i86pc i386 i86pc Solaris」です。
あとgccやccが混在していたり、いろいろ弄っていた環境なので実は素のSolarisだとすんなり通っちゃうのかもしれません。

アーカイブをげっちゅ。

wget http://tokyocabinet.sourceforge.net/tokyocabinet-1.4.1.tar.gz
gtar zxvf tokyocabinet-1.4.1.tar.gz
cd tokyocabinet-1.4.1

まずはconfigureを弄ります。これはrealpathが云々と怒られる対策です。std=c99をstd=gnu99にします。特に外から設定を与えない場合は1カ所の修正で大丈夫ですが、fastestの場合とか中で結構分岐していたので弄くり倒す予定の人は一括置換してください。

 MYCFLAGS="-std=gnu99 -Wall -fPIC -fsigned-char -O2"

Solaris10では「pthread_yield()なんて実装は排除しちゃったよ。あなたたちは違うんです。フフンっ。」という事らしいので、その代わりに「sched_yield」を使います。
本当は「_POSIX_PRIORITY_SCHEDULING」が定義されているか確認して、定義されていれば↓を、そうでなければ別の代替実装をするのが正しいのかもしれません。

// myconfig.hの413行目くらい
#if defined(_SYS_SUNOS_) && TCUSEPTHREAD
#include <sched.h>
#define pthread_yield() sched_yield()
#endif

警告が気持ち悪いのでtcutils.cを弄ります。

// tcutils.c
//static pthread_once_t tcglobalmutexonce = PTHREAD_ONCE_INIT;
static pthread_once_t tcglobalmutexonce = {PTHREAD_ONCE_INIT};

あとはconfigureしてgmakeします。
-D_REENTRANTはgmtime_r, localtime_rの実装を有効にするために、LIBS="-lrt"はsched_yield関連で必要です。

./configure CC=gcc CFLAGS="-D_REENTRANT" LIBS="-lrt"
gmake

まだ動くかどうか確認していません(; ・`д・´)
これで動いたら次はTokyoTyrantの/dev/poll対応してみます。

※追記:動きました。

Subversionの設定メモ

普通にSubversionを設定するだけなら簡単ですが、Rootを持っていないサーバに対して設置して貰う場合は依頼内容を明確にしないといけないのでそのメモ。

○前提条件

  • Apacheと連携してhttp://でアクセスできるようにする
  • 1個の設定で複数のレポジトリを管理できるように
  • Apacheのユーザーはnobodyで動いている
  • SVNのデータ置き場は/data/svn
  • BASIC認証をする
  • 特定のディレクトリproj1/document は自動管理をする。 

○Apacheの設定

<Location /svn>
    DAV svn
    SVNParentPath /data/svn
    SVNListParentPath on

    AuthType Basic
    AuthName "tamtam private repository"
    AuthUserFile /etc/svn-auth-file
    Require valid-user
</Location>
<Location /svn/proj1/document>
    SVNAutoversioning on
</Location>

HTTPSのみ受け付けるようにするには、以下の記述を追加する。

    Require SSL connection for password protection.
    SSLRequireSSL

○レポジトリを作成する

svnadmin create /data/svn/proj1
chmod -R nobody:nobody /data/svn/proj1

○コミットメールの設定

コミットメールはhookディレクトリにpost-commitというファイルを作成します。
検索するとRubyを使ってメールを送信しているものが多いですが、会社のサーバには標準インストールされていないのでPERLで書いてみます。

これは別の記事に書こう。

Subversionのワークディレクトリのリビジョンを取得する

以下のコマンドでリビジョンを取得できます。

svnversion -n ディレクトリ

指定するディレクトリは.svnディレクトリが存在しなければいけません。
-nオプションをつけると末尾の改行を出力しません。

この機能を使って今回はビルドバージョンを埋め込むようにしました。具体的なコードは面倒なので書きませんが、Antで以下の手順で処理します。

・SVNからソースを取得
・リビジョン番号を取得する
・コンパイルする→DESTディレクトリへ
・必要なファイルをDESTへコピーする
・DESTにあるXMLファイルへリビジョンを埋め込む
・WARファイルを作成する
・サーバへデプロイする

こんな感じかな。正確にはXMLの設定ファイルに「svn.revesion」みたいな定数を定義して、その値を置換しています。(このXMLファイルの設定は実行時にDIで設定保持クラスにInjectしています)

バイナリに埋め込む場合はコンパイルする前に埋め込まないといけないですね。

 

とりあえずは、これで共有環境のアプリがどのバージョンでコンパイルされたものなのか分かるようになりました。

RDBで文字列に対する効果的なインデックスの張り方

RDBで文字列にインデックスをそのまま張ってる事って結構あると思います。
(普通はないかな?)

例えばURLに対してINDEXを作った場合、1エントリに対してのインデックスの量が多くなってしまいます。
そこでカラムを一つ追加して、その文字列のハッシュ値を格納するようにします。
そして文字列のカラムにはインデックスを張りません。
WHERE句を書くときには、「ハッシュ値カラム AND 文字列カラム」とします。
これでインデックスツリーが小さくなってDBサーバのメモリ効率が上がります。
クライアントサイドでハッシュを求めることになりますが、クライアントサイドはスケール化が簡単な事と、ハッシュ値計算なんて微々たるものなので問題にはならないと思います。

Javaの場合であればhashCode()メソッドがあるので自前でハッシュ関数を作る必要はありません。

MySQLを例にしてみます。
(直接手打ちしたので構文エラーになるかもしれませんw)

-- これは悪い例
CREATE TABLE TBL_URL(
  IDX INTEGER NOT NULL,
  URL VARCHAR(4096) NOT NULL,
  STATUS INTEGER NOT NULL,
  TITLE VARCHAR(1024),
  BODY MEDIUMTEXT,
  PRCDATE TIMESTAMP NOT NULL,
  CONSTRAINT PK_TBL_URL PRIMARY KEY (IDX)
);
CREATE INDEX IDX_TBL_URL_01 ON TBL_URL(URL(255));
-- SELECT * FROM TBL_URL WHERE URL = ?
-- 改善例
CREATE TABLE TBL_URL(
  IDX INTEGER NOT NULL,
  URLCD INTEGR NOT NULL,
  URL VARCHAR(4096) NOT NULL,
  STATUS INTEGER NOT NULL,
  TITLE VARCHAR(1024),
  BODY MEDIUMTEXT,
  PRCDATE TIMESTAMP NOT NULL,
  CONSTRAINT PK_TBL_URL PRIMARY KEY (IDX)
);
CREATE INDEX IDX_TBL_URL_01 ON TBL_URL(URLCD);
-- SELECT * FROM TBL_URL WHERE URLCD = ? AND URL = ?

JavaでISO単位付きの文字列を整数に変換する

  • Posted by: タムタム
  • 2008年5月26日 00:26
  • Java開発

  • Tags: Java

すごく手抜き処理の動けばいいやー程度のやっつけコードです。
よい子は真似しちゃだめですよ(;´Д`)

public long toLong(String str) {

    long unit = 1L;
    long head;

    char tail = str.charAt(str.length()-1);
    switch (tail) {
    case 'G':
    case 'g':
        unit *= 1024;
    case 'M':
    case 'm':
        unit *= 1024;
    case 'K':
    case 'k':
        unit *= 1024;
        head = Long.parseLong(str.substring(0, str.length()-1));
        break;
    default:
        head = Long.parseLong(str);
    }

    return head * unit;

}

AmazonのBrowseNodeId一覧

  • Posted by: タムタム
  • 2008年4月14日 18:08
  • 開発

  • Tags: Amazon

こちらの公式リファレンスを見てBrowseNodeIdの一覧を取得していたのですが、どうにも結果がおかしいので調べてみると値がすっごい適当だという事が判明しました。 というわけで、アマゾンのTOPページからBrowseNodeIdを引っこ抜いてみました。

せっかくBrowseNodeIdの木構造一覧を作ったのに、作り直しです(´・ω・`)
まぁレスポンスは全てキャッシュしてあるので無駄にはならないですけど・・。
ためしにFreeMindの形式にしてみたら、項目が多すぎて(1万ノードくらい)ひどいことになった・・。

ジャンル BrowseNodeId
465392
洋書 52033011
エレクトロニクス 3210981
ホーム&キッチン 3828871
ミュージック 561956
DVD 561958
ソフトウェア 637392
ゲーム 637394
おもちゃ&ホビー 13299531
スポーツ&アウトドア 14304371
ヘルス&ビューティー 160384011
時計 324025011
ベビー&マタニティ 344845011
アパレル&シューズ 352484011


Amazonのサービス

  • Posted by: タムタム
  • 2008年4月 4日 11:09
  • 開発

  • Tags: Amazon

アマゾンでは検索や購入のAPIの他にもいろいろなサービスを提供しています。

今はアマゾンを使って本の管理をするアプリを作っていますが、その話は後日ということで今回はアマゾンの他のサービスを見てみたいと思います。とりあえずサービスの種類を表にしてみました。WEB+DBの無断転載とも言います。
WEB+DBの記事を書いた人のサイトはこちらです。 

ちなみにA2S(ECS)以外は有料です。

サービス名 説明
Amazon Associates Web Service(A2S) 書籍情報などを提供しているAPI郡。昔のAmazon E-Commerce Service(ECS)
Amazon Elastic Compute Cloud(Amazon EC2) 仮想マシンレンタルサービス
Amazon DevPay AmazonEC2上で動かすサービスに対して開発者が課金する手段を提供
Amazon Simple Storage Service(Amazon S3) 大容量ストレージサービス
Amazon SimpleDB 巨大で単純なデータベースサービス
Amazon Simple Queue Service(Amazon SQS) メッセージキューサービス
Amazon Mechanical Turk 人的タスクをAPIとして提供
Amazon Flexible Payments Service(Amazon FPS) 柔軟な決済サービス
Alexa Web Search Web検索エンジン
Alexa Web Information Service サイトに関するトラフィックなどの情報を提供
Alexa Top Sites トラフィックの多いサイトを列挙
Alexa Site Thumbnail サイトのスクリーンショットを提供

ちなみにTwitterの画像がAmazon S3を使っているみたいなので気になりました。

アップロード 0.1$/GB
ダウンロード
(1か月の転送量)
0~10TB 0.18$/GB
10~50TB 0.16$/GB
50TB~ 0.13$/GB
保存容量(1か月) 0.15$/GB
リクエスト 0.01$/1000回 アップロードリスト
0.01$/10000回 ダウンロードリスト

うーん・・。全然ドキュメント読んでないけど、このダウンロードリクエストというのはHTTPリクエストも含まれるのかな・・。EC2にマウントした場合もカウントされるのかな・・。たぶん使わないだろうから、調べるのはやめておこう。

NFSの設定とトラブル

  • Posted by: タムタム
  • 2008年3月29日 09:47
  • 開発

  • Tags: Linux NFS

Linux(UNIX)でネットワーク上にあるファイラーをマウントするときはNFSを使います。ちなみにWindowsから見たい場合はSambaを使います。NFSとSambaの違いは、、、NFSはメッセージ単位でやりとりするのに対して、Sambaはファイル単位でやりとりをする事ですかね。

というわけでファイルサーバにあるディレクトリを別のサーバにマウントしてみたいと思います。

○tamsvr01
 ファイラー(1.5TB)
 FedoraCore5

○tamsvr02
 FedoraCore8(64Bit)

まずはtamsvr01(ファイラー)の設定
nfsのサービスが入っているのでそれを使います。

/etc/exportsの編集
書式は以下のようになります。 rwで設定していますが、roにすれば読み込み専用になります。

共有するディレクトリ    公開先(書き込みの可否)
/share/share2/nfs 192.168.1.0/255.255.255.0(rw)

サービスを有効にします。 

chkconfig --level 345 nfs on
/etc/rc.d/init.d/nfs start

次はtamsvr02の設定です。

mount -t nfs tamsvr01:/share/share2/nfs /share/share2

自動的にマウントしたい場合は/etc/fstabに設定の追加をする

tamsvr01:/share/share2/nfs  /share/share2   nfs defaults

次はトラブル系です。
書き込みができなかったので、 umountしたらdevice is busy。ls /share なんてやっても帰ってこなくなりました。その解決策としては、tamsvr01(共有元)の/etc/exportsから対象の記述を消してreloadなりrestartなりします。そしてumountします。これで何とかなりました。
あと、UIDとGIDの同期が取れてないといけない? 最初から同期とっているので分かりません・・。

http://www.linux.or.jp/JF/JFdocs/NFS-HOWTO/server.html
http://www.linux.or.jp/JF/JFdocs/NFS-HOWTO/client.html

サーバの設定ファイルをWebDAVとSubversionで管理する

Linuxの設定ファイルをSubversionで管理したいと思います。
しかし、以下のようなケースで困った事が起こったのでその解決策としてのメモを残しておきます。

■ケース
 保存はファイル単位(Subversionはディレクトリ単位)
 普通のSubversionの使い方はimport → co → 変更 → commit
 でもファイル単位なので、例えばtomcatのserver.xmlだけを対象としたい場合に困る。
 サーバ側の設定ファイル置き場に.svnが出来てしまう。
 単純に保存するだけならimportで十分だが、上書きができない。

■理想
 importで上書きしてほしい
 →そんなん無理です。。

■解決策
 WebDAVと自動コミットを使って解決する

そうでした・・・。WebDAVを使えば自動コミットが出来るのでした。
つまりWebDAVクライアントからファイルをPUTすれば勝手にSVNにコピーされます。
というわけで、早速設定してみました。

まずwebdavのクライアントが必要です。Windowsでは標準でしょぼいクライアントがついてきますが、サーバはLinuxなのでLinuxのWebDAVクライアントを探しました。cadaverというのが有名みたいです。Fedora8ではyumからインストールできます。うむ。便利!
次にApache(WebDAV)の設定をします。

SVNAutoversioning on

を追加します。
設定全部をさらすと私の場合はこんな感じです。 (必要に応じて自分の設定に変更してください。)

<Location /svn>
    DAV svn
    SVNParentPath /data/svn
    SVNAutoversioning on

    Require SSL connection for password protection.
    SSLRequireSSL

    AuthType Basic
    AuthName "tamtam private repository"
    AuthUserFile /etc/svn-auth-file
    Require valid-user
</Location>

これで設定は完了です。Apacheの設定をreloadしてLinuxのcadaverからアクセスしてみたいと思います。

cadaver https://tamsvr01/svn/repos
WARNING: Untrusted server certificate presented for `tamsvr01':
Issued to: Admin, tamsvr01, Shinagawa, Tokyo, JP
Issued by: Admin, TripleKiss, Tokyo, JP
Certificate is valid from Sun, 15 Oct 2006 04:20:01 GMT to Mon, 15 Oct 2007 04:20:01 GMT
Do you wish to accept the certificate? (y/n) y
Authentication required for tamtam private repository on server `tamsvr01':
Username: tamtam
Password:
dav:/svn/repos/> help
Available commands:
 ls         cd         pwd        put        get        mget       mput
 edit       less       mkcol      cat        delete     rmcol      copy
 move       lock       unlock     discover   steal      showlocks  version
 checkin    checkout   uncheckout history    label      propnames  chexec
 propget    propdel    propset    search     set        open       close
 echo       quit       unset      lcd        lls        lpwd       logout
 help       describe   about
Aliases: rm=delete, mkdir=mkcol, mv=move, cp=copy, more=less, quit=exit=bye

SubversionのレポジトリがWebDAVとして公開してあるので、cadaverでURLを指定して実行します。
証明書がオレオレ証明書かつ有効期限が切れているので警告が出ています。
(会社からアクセスする時に中身を閲覧されたくないのでSSL暗号を使うためだけにHTTPSにしてます)
YESを選択して、ユーザー名とパスワードを入力します。認証が成功したらFTPのようにWebDAVプロンプトが出てきます。
とりあえず、どんなコマンドが使えるのか確認するためにhelpを実行してみました。
ふむふむ。コマンドがFTPに似ていますね。
ローカルファイルをリモートにアップロードするコマンドはputなので、putを実行してみます。

と、その前に、自動コミットで正しくリビジョンがあがるのか確認するために、一度ファイルをインポートしてみます。

svn import -m "tomcat default server.xml" server.xml https://tamsvr01/svn/repos/settings/tamsvr02/server.xml

次にWebDAVクライアントからputしてみます。

dav:/svn/repos/> cd settings
dav:/svn/repos/settings/> cd tamsvr02
dav:/svn/repos/settings/tamsvr02/> put server.xml
Uploading server.xml to `/svn/repos/settings/tamsvr02/server.xml':
Progress: [=============================>] 100.0% of 5623 bytes succeeded.
dav:/svn/repos/settings/tamsvr02/> ls
Listing collection `/svn/repos/settings/tamsvr02/': succeeded.
      > server.xml                          5623   2月 10 07:06
      > smb.conf                            7786   2月 10 05:57
      > smbusers                             114   2月 10 05:56

ふむ。成功したようです。ちなみにApacheの設定がきちんと出来ていないと、以下のようにエラーになります。

Progress: [=============================>] 100.0% of 1197 bytes failed:
409 Conflict

ログをWindowsのTortoiseSVNから見てみました。

問題なくコミット出来ていました。さて、ここのコメントはどうやって編集するのでしょう・・。
あと、やっぱりコメントをput時に指定できると嬉しかったりしますが、はてさて・・。

 

テストデータの生成

  • Posted by: タムタム
  • 2008年1月16日 23:26
  • 開発

テストデータの生成はだるいもんです・・。

昔は手作業で白い巨塔の登場人物のデータを生成したりもしてましたが、件数が増えてくるとそういうわけにもいきません。
適当なデータならExcelでランダム生成させたり、それ専用のツールがあったりもするのですが、(ちなみにSI Object Browserにはそれなりの適当なデータを生成する機能がついてます)このサイトで生成することもできます。

http://www.generatedata.com/#generator
日本人の名前などはもちろんありませんが、基本的なデータを作成するだけなら十分だと思います。
ただ、テストデータは「ああああ0001」とかのが見やすいし、実施しやすい件について。
スルー(´-ω-`)

CodeSugar

  • Posted by: タムタム
  • 2008年1月16日 23:26
  • 開発

  • Tags: Eclipse

JDK6u4がリリースされちょります。
主にBugFix関連だけど、Timezoneのパッチが既に含まれているような事がRELEASE NOTEに書いてあったような気がします。

さて、Javaでクラスを作成していると、決まったコードを記述するのが面倒になります。
hashCodeやEqualsが良い例なのですが、これはEclipse3.3には標準で生成機能が付いているので問題ないのですが、toString()を良い感じに生成してくれる機能がありません。そこで、探したらあっさりと見つかりました。

一つはCommonsLANGに依存するコードを生成するらしいので除外。
CodeSugarというやつがそうです。
http://sourceforge.net/projects/codesugar/

Eclipse3.3で動作確認しました。

ちょっと気になる点としては、
・PackageExplorerで対象クラスを選択しないとメニューが有効にならない事(エディタにフォーカスがあるとダメ)
・生成するコードでStringBufferを使用しているがStringBuilderを使って欲しい(´・ω・)

と、オープンソースだから、自分で修正しちゃえば良いのか(´・ω・)

 

そういえばEmEditorのバージョン7が正式リリースされてた(`・ω・)
さっそく更新。単語補完機能が便利そうだけど・・・ 大文字のみで補完するのは勘弁してください。。。
どっかに設定でもあるのかな・・・(;´Д`)

DivXProのシリアルナンバーが期間限定で無料配布中

GIGAZINE見てたらこんな記事をみつけました。

http://gigazine.net/index.php?/news/comments/20071214_divx_pro_free/

PCだけでなくPS3でサポートされるなど、今や有名な動画コーデックとなっているDivXですが、その有料版であるDivX Proのシリアルナンバーが期間限定で無料配布(通常2499円)されているそうです。

さっそく公式ページを見てみると・・・
あれっ? 半額キャンペーン中だ。
無料配布はどこにも書いてないよー・・。

GIGAZINEに書いてあったURLを踏んでみる。
おぉ・・あった!
いつまで配布中なのか分からないので、とりあえずインストールしてシリアルを頂戴しておきました。

 

話は変わってCommons-DBCP使ってたらなにやらエラーが発生・・。
IllegalStateException("Pool not open")が発生する模様。DBCPがバグってるのかなーと思いきや・・自作のThreadPoolがバグってました。
TaskListが空になったらMainを抜けるのですが、Mainを抜けたあとにDBCPの後処理をしており、Taskが終了していないのに後処理が走っているからでした・・。Mainを抜ける条件は「Taskが全て終了したら」じゃないとダメですね。

書き直すのも面倒なのでJava5以上からサポートされている標準のThreadPoolを使うように変更します。
使い方は後ほど記述します。

 

そろそろ・・・BLOGのデザインを変更したいなぁ。

PreparedStatementのキャッシュ

Wikiにも書きましたが、PreparedStatementをキャッシュするとスループットの向上が望めます。

parse, prepare処理が内部でどういった手順で行われているかは以下のサイトが参考になると思います。

実際にプログラムを書いて動作を確認したところ、確かにOracleでは30%ほど速くなりました。
MySQL(MyISAM)では200%ほど速くなりました。
INSERTしか確認していないので、実験の信頼性は低いと思いますが、気になる方は自分で試してみると良いかもしれません。

ちなみにWikiはこちらです。実験の結果も載せてます。

会員サイトで画像ファイルにもアクセス制限をかける方法

  • Posted by: タムタム
  • 2007年12月13日 13:59
  • 開発

タイトルどおり、会員サイトで画像ファイルにもアクセス制限をかける方法です。
ちなみにBlogWriteを購入したので、BlogWriteで書いてます。USBメモリでログを持ち歩いてます。
というわけで会社からカキコ。

本題にもどり、知人に聞きまわったのですが、いくつか方法があるようです。

  1. ApacheのBASIC認証を利用する
  2. 画像ファイルを非公開ディレクトリに配置し、プログラムで読み出してContent-type: image/****で返す
  3. SetEnvIfでリファラーを見る
  4. Javaの場合はTomcatで画像ファイルにServletFilterをかます
  5. リバースプロクシを使って4みたいなことをする

順番に説明していきます。

その前に、Mixiは制限かけてないのでログインしていなくても画像ファイル見れます。
プロフィール画像とかMixiのアカウントが無くても見れるはずです。

1のBASIC認証を利用する方法です。
ブラウザ依存のダイアログが出てしまうのと毎回HTTPヘッダに認証の情報が載ります。また、会員サイトはCGIやPHP、Servletで会員の登録をし、またそのアプリで認証を行うため、Apache側に認証をしたとしても二重認証になってしまいます。つまり、HTMLフォームにIDとパスワードを入力して、画像を見ようと思ったらBASIC認証のダイアログが出てしまう。この回避策としては、全てBASIC認証に統一するしかないと思います。
昔の会員系サイトではBASIC認証のサイトを結構見かけましたが、現状では現実的ではないと思います。

次は2の方法です。
URL指定ではアクセスできない場所に画像ファイルを配置し、CGIでバイナリデータを読み込みそのまま返す。というもの。
<img src="hogehoge.cgi?name=abc.jpg"/>みたいな感じ。CGIで認証チェックを行うため、利用者から見ると違和感はない。
でもサーバの負荷が尋常ではないような気がします。

次に3のリファラーではじく方法。
これはきるごあ先生に教えてもらったサイトで実装されてたようです。
例えばアプリサーバがapp.example.comというドメインで、画像サーバがimg.example.comというドメインだとします。
.htaccessファイルでは様々な方法でアクセス制限をかける事ができるのですが、その方法の一つにSetEnvIfがあります。これでリファラーをチェックしてapp.example.comではなければForbiddenを返すというもの。ブラウザのアドレス欄にURLを直接打ち込まれた場合はリファラーが空になるので、この場合もはじけます。また他サイトからリンクされた場合もはじけます。
ただし、リファラーは簡単に偽装が可能なため完全なる制限とは言えません。

次はServletFilterを使う方法です。
完全にアクセス制限をかけたい場合、かつServletで構築しているサイトであればこの方法がお勧めです。
ApacheとTomcatを連携させている場合、静的ファイルはApache側で処理させていると思いますが、画像ファイルをTomcat側に転送させます。するとTomcat側のHTTPエンジン(Coytoteでしたっけ?)で処理されます。このTomcatのweb.xmlにServletFilterをかまします。すると画像ファイルにアクセスする前にプログラムを実行することができるので、このプログラム内で認証チェックを行います。
ただし、TomcatはApacheと比較して画像ファイルを返すスループットがちょっとだけ低いです。

最後にReverseProxyを使う方法です。
これはServletFilterを使うのと同じ要領です。フロントのApacheで画像ファイルにアクセスしたらReverseProxyで特定のプログラムに飛ばします。そして飛ばしたプログラムで判定する。

実際は、そこまで制限したいか?ということになると思いますが・・・。
個人的にですが、基本的にサイトはServletで構築しているので4番目のServletFilterを使う方法が楽チンだと思ってます。

ところどころ脳内補完で説明がぶっとんでますが、行間読んでください・・。

jEdit

jEditを入れてみました。
このソフトはプログラマ向けテキストエディタらしいです。Javaで作られているのでマルチプラットフォームで動作するっぽい。日本語化もプロパティファイルを追加するだけで可能です。また、コード補完にも対応しており、プラグインでいろいろ機能を追加する事が可能です。クラス構造を表示したりとかコンソールを表示したりとか、エラーリストを表示したりとか、TODOリストを抽出したりなど。プラグインを追加していけばEclipseに匹敵する機能を備えることが可能です。
んで、Eclipseでもいいわけですが、Eclipseは起動が重いですからね。ちょっと編集するならjEditのが良いと思ったわけです。SVNプラグインなんかもあるし。

詳しくはこちらを。
http://jedit.sourceforge.net/

Java以外にも対応してます。C/C++, Perl, Rubyなど。

インストールはしてみたので、これから使ってみようと思います。

Solaris10壊れた(´・ェ・`)

Solaris10にOracle10gR2を入れるべく、イロイロ設定してたのですが、その前にMySQLやPHPのビルドのために色々なパッケージを入れてしまったせいでfont-cacheが壊れました・・・。
gdmと打ち込んでも、立ち上がりません・・・。
正確には、起動して・・・落ちて・・起動して・・落ちてを5,6回繰り返して終了します。

仕方がないので、ローカルマシンのCygwinにXサーバをたてて、SSHで転送しました。
いやー・・・便利ですね!!

 startx &
 xhost +
 ssh サーバ名
 export DISPLAY=ローカルマシン:0.0
 runInstaller -ignoreSysPreReqs

 

完美

土日は完美を廃人のごとくプレイしますた(`・ω・)
BLOGを更新する余裕もなくプレイしてました。

と、嘘をついてみる。

 

完美は1鯖(カペラ鯖)で「kirsche」という名前でやってます。人間の魔道士です。
レベルは16くらいだったはずです。
ひゆさんがギルドを設立したので、ちゃっかり入りました(`・ω・)
どれがお金になるアイテムかわからずに、結局アイテムを売れずにいる自分・・・。

倉庫拡張クエスト1個目はクリアしたけど、やっぱり倉庫キャラは必要ですね。
倉庫キャラというよりは、露店キャラが!!
まったりプレイ中ですので、まったりPTに誘ってくださいまし~。

 

話はちょっと代わってGoogleMapみたいなNPC検索のシステムを作ろうとしてます。
NPCの場所がわからなくて・・・。
完美の拡大したマップをつなぎ合わせてAJAXで処理。

NPCだけではつまらないので、草の座標も表示と・・・。
草や砂利の場所をメモっているので、それも表示できたら分布みたいなのができて面白いかなーと。
ボロノイ図か凸囲図形を作成すればデータが少なくてもそれっぽく見えるし(`・ω・)

というわけで、JavaScriptのライブラリをフルスクラッチ中でございます。 

メモリの無駄遣い

サーバ機の標準スペックをメモリ8GBにするって言ってたから、あまり気にせずにプログラミングしてみたんです。64BitLinux/メモリ2GBで動かしたらメモリを使い切りました・・・。

100万人 * 50属性
= (100万) * (50 * (30 + 30 + 40))
=1MB * 50 * 100
=1MB * 5000
=5GB

むお・・・。判定処理を書くのが面倒だったからダミーノードを入れたのが、こんなところでネックになるとは・・・。それと64BitOSで動作させるとポインタが8Byte取られるらしく、それも影響しているくさい。きちんとtypedefして使うか(´・ω・)
Cは面倒な言語ですね。
構造体(Struct)に関数ポインタを持たせてDeepCopyとDestructorがし易いようにしてるんですが、これは要らないのかな・・・。入れるデータが決まってるならわざわざ汎用データコンテナにしてメモリを使いすぎるのも問題と・・・。1属性あたり2個のポインタが増えちゃってるから16Byte(64Bit*2) * 50 * 100万人=100MB。うわぁ・・・・w
こんなんじゃコンシューマ機のプログラムやってる人に怒られそうだw

一応それなりには出来たけど、 メモリ使用量は全然減らせるですね(´・ω・)
処理性能は書き込みが毎秒20万件(1件辺り1ユーザー追加, 50属性の書き込み), 検索は毎秒100万件でスレッドセーフ。とりあえずここがネックになる事はなさそうですね。

複数の言語を同時に使うと、頭が混乱するですよ。
[現実]εεε゙(ノ´・ω・)ノ  [妄想]

 

今週はGoogle先生のセッションに参加してきます(`・ω・)

パソコンのスペック

  • Posted by: タムタム
  • 2006年11月12日 11:56
  • 開発

会社のパソコンのスペックってどんなもんなんでしょーか。
PCのスペックがどんどんあがっていってるけど、そんなに必要なの?と思うこともしばしば。
一般的なビジネス用途って、メールがかけてOfficeがそこそこ動けば問題ないのでは。。。と思ってしまう。
そもそも高機能なマルチメディア機能を利用すると、それなりにスペックを要求されるわけですが、逆を言えばマルチメディア機能を利用しないなら、スペックいらないんですよね。
なんでVistaとかいらない機能をつけるんでしょーか。
バージョンアップするに連れて重くなるソフトウェア。
本当にバージョンアップ?

ちなみに開発マシンはそこそこ良い物を使ってるんじゃないでしょうか・・。
メモリが無いと統合環境立ち上がらないし。(sshでviとgccでやってる人は関係ないけど)
(´ε`;)ウーン…

今使ってる開発機(会社の)
・Pen4 3G(HT)
・メモリ 2GB
・ビデオ:Quadroなんとか

[常時立ち上げソフト]
・Orchis
・Sleipnir
・FireFox
・フリーのスケジューラーソフト
・付箋ソフト
・Becky2
・LimeChat
・TeraTerm(2,3個)
・Eclipse+WTP
・EmEditor
[サービス関係]
・Oracle10G
・MySQL
・PostgreSQL
・Apache2個
・Tomcat2個
・TPTP
・SubVersion
[時々起動するもの]
・Cygwin
・電卓
・Excel
・その他

これでだいたいメモリ1GB~1.2GB使います。
Oracleはパラメータいじってメモリ使用量を100M以下にしてます。
(デフォルトだと500Mを確保しやがるので)
ここから開発作業をすると1.8GBとかに膨れあがります。
(プロファイラや負荷ツールを使うと特にね・・・)

結構メモリ使うもんなんですね・・・。
まぁ必要な物だけ起動しろと言われそうですが・・・。
サービス関係は開発サーバにも入ってるけど、ローカルにもあると便利なのでローカルにも入れてるだけ
(*・ε・*)

バギー

  • Posted by: タムタム
  • 2006年10月18日 23:59
  • 開発

JSONの公式ライブラリというか、公式ライブラリを拡張したライブラリJSON-LIBという物があるんです。
Javaによる実装です。

自前でBeanとマッピングするEncoderとDecoderを作っていたのですが、最新版が出ていたので試してみました。まぁタイトル通り、バギーなライブラリなんですがね。
JUnitのテストケースが1/3くらいしか通りません・・・。
しかも、自前ライブラリよりも遅いと来た(;´∀`)

んで、今日はちょこちょことライブラリを改修してたわけですが・・・
致命的な不具合を発見・・・。

Listの配列はJsonArrayのJsonArrayとみなしており、上手く処理できてません。
new ArrayList[]{new ArrayList(), new ArrayList()}
Encodeで[[],[]]となり、これをDecodeするとエラーが発生しました。

あとは、
class PojoA {
  private PojoB[] field = new PojoB[] {
    new PojoB(2001,2,3),
    new PojoB(2002,12,3),
    new PojoB(2003,12,23),
  }
  // getter. setter
}
class PojoB {
  private int year;
  private int month;
  private int date;
  // getter, setter, ctor
}
こんな感じでPojoAをEncode→Decodeできません。
PojoBの配列を処理するときに、
PojoBのフィールドを列挙して・・・ PojoAからそのフィールド名を使ってSetterを取得しようとしていたり・・。なんという不具合。
あとは、JSONではnullもデータ型として定義されているのに、nullの扱いが中途半端。
配列の要素や、BeanからDecodeするときにStringのnullは""にしたり、Integerなどは0にしたり・・・。
primitiveとラッパーを分離して考えて欲しい。むしろStringのnullを""に置き換えるのは如何な物かと・・。
Characterクラスの扱いも中途半端。charのnullを{}と変換したり・・・。

まぁ公式のJSONの実装も、Mapの要素をそのままtoStringしてたりと、再帰的に処理してないわけですが。いい加減すぎる・・・。明日もこの作業か・・・つらい・・。

疲れたよ(´・ω・`)

 

あと、ノートのMSNが接続不能になりました。
他のPCからは接続できる・・・。バージョンが問題なのかな。
2chを見たら同じ症状の人がいたので、レジストリを削除したら起動できるようになりました。
まったく。MSめ!!

ユーザーフレンドリーなインターフェースとは

  • Posted by: タムタム
  • 2006年10月10日 23:14
  • 開発

少し前の記事でPS3の公式サイトは見た目に拘って中身がないという事を書きました。
ではユーザーに優しいデザイン、インターフェースとは何でしょう。

私は、「考えさせないこと」だと思います。
どんなにすばらしいデザインでも、閲覧しづらかったらイライラしますよね。
行きたい情報に辿り着けなかったらイライラしますよね。
どこをクリックして良いか分からなかったり、ボタン名がアラビア語だったり、サイトの構成やアイコンが一般のメタファと異なっていたり・・・。
デザインも大切だけど、第一はユーザーに考えさせないことが尽きると思います。

ただし、現実世界ではそうでない方が良い場合があります。
働きマン3の中からの引用になりますが。。
わざと踏み石を不並びにし、利便性を下げることで人はその場所をゆっくり歩く。
その景色が素晴らしいから、たくさん見せたくてそのようなトリックを仕込む。
奥が深い(`・ω・´)

デザインのコツ

  • Posted by: タムタム
  • 2006年9月30日 10:41
  • 開発

ここでいうデザインとは、色遣い、サイトの構成、デッサンなどなど。
素材は自分で一から作る。

デザイナーさんはこんな事をやっているわけですが、コツを聞いてみました。

「経験値」

はい。まったくもってその通りでございます。
コツなんてありませんよね(´・ω・`)
ごめんなさい。

ちなみに、PSUのデザインに関して聞いてみました。
あのクオリティであの重さはあり得ない。との事でした。('・c_,・` )

ちなみに、プログラムのコツはあります。
他人のプログラムを改造するのが上達のコツです。

アーキテクトのコツもあります。
上辺だけでなく概念そのものを熟慮し理解すること。
プログラミングは手段であり目的ではありません。(趣味はのぞく)

ローテク仕様と矛盾点

  • Posted by: タムタム
  • 2006年9月30日 10:11
  • 開発

立派な理由があってそれを成し遂げたとしても、様々な箇所に矛盾が出て結局は振り出しに戻る。
結局の所、最初のコンセプトは無かったこと、もしくはやむなしとして、使いにくい物が出来上がる。
もしくは、一部にだけそのコンセプトを適用し、他の部分はどうでもいい理由によって仕様の差違が発生する。

たくさんの入力項目があり、エラーチェックが厳しい。
使いにくいので、入力項目を減らし、デフォルト値を設定することでエラー少なくする。
項目によってはプログラムで計算をして、エラーが発生する項目(組み合わせ)は表示されないようにする。
たとえば、こんな事をやったとします。
まぁ結論から言うとね、日付入力エラーを出させるなって事です。
「存在しない日付です」というエラーに何の意味があるのだろう。
カレンダーをポップアップさせるインターフェースにすれば解決するかもしれないけど、とりあえずその前に・・・。

存在しない日付とは何か(書式エラーとは違います)
(というか書式エラーなんてのもナンセンスなエラーですよね。明らかにローテクです。)
2月30日とかそーいう事らしいです。
1~12月のリスト 1~31日のリスト を選択させるらしいですが、そもそも2月30日を入力する時ってどんな時でしょうか。これを3月1日、もしくは3月2日の意味で入力しているユーザーは居ないと思います。それなら3月と1日、2日を選択しますからね。ほとんどのユーザーは月末を期待して入力してるわけで、なぜそれをプログラム側で補助してあげないのだろう。
エラーチェックをしてバシバシ弾いて入力エラーにするのではなく、有る程度はプログラム側で対応してあげるべきだと思うのです。
※2月15日から15日後っていう意味で2月30日という意味があるかもしれませんが。

親切な設計は度が過ぎるとおせっかいになりますが、少なくともカレンダーに予定を入れるのに、カレンダーの日付をクリックするような仕様なのに、次の画面に進むと日付入力はカレンダーではないという仕様には疑問を覚えますが。(;´∀`)

週末プレイヤー

  • Posted by: タムタム
  • 2006年9月 6日 13:33
  • 開発

PSUのキャラリセットが確定したようですね。
キャラリセットはこれで最後になって欲しいものです・・。

さて、タイトル通り・・・
ネトゲのプレイヤーには毎日プレイできない人もいます。週末だけプレイする人。
例えば土曜日にシステムの不具合が発生して丸一日サービスが提供できなかった場合、1日分課金を延長する措置がとられますが、土日のシステム不具合の場合は1週間伸ばして欲しい。
会社にとっては痛手かもしれないけど、土日しかプレイできない人も多いはず。
土日プレイヤーにとっては、1日延長されたところで関係ないはず。

難しい問題ですが、PSUは結局1ヶ月の試験期間となりましたが、最初のトラブルで1日延長ではなく、1週間延長と発表していれば、「あ、週末プレイヤーのことも考えていてくれているのかー」と思ったのに。

JamesとMySQLのメモφ(`д´)

忘れないように備忘録。

JamesのストレージにMySQLを利用すると、起動しません。
MySQLでは途中のバージョンからVARHCARのサイズ拡張が変更されており、VARCHAR(200)の場合は最大200バイトのはずですが(少なくともOracleではそうだったはず・・・)MySQLでは文字コード依存になります。200バイトではなく、200文字。なのでSJISの場合は1文字が2バイト扱いになり、UFT8では1文字が3バイト扱いになります。
そしてMySQLの制限の一つに、主キーのセットは1000バイト以下というものがあります。
JAMESのDDLによると、VARCHAR(200)とVARCHAR(255)を複合主キーとしているので、(200+255)*3=1365バイト。正確には1367バイトになるの?よくわかりません・・・。

こんな制限初めてしったよ(`□´)
もともとこの列の定義としては、200バイトという意味っぽいので列の定義に「CHARACTER SET latin1」を追加して回避しました。
VARCHAR(200) NOT NULL → VARCHAR(200) CHARACTER SET latin1 NOT NULL

備忘録φ(`д´)メモメモ...

WindowsXP HOMEをProに

新しいノートパソコンが届いてワクワクテカテカなタムタムです。
予想外に大きいノートでびっくりしてます。

OSがHomeエディションなので、これをProにしようと思います。
手元にあるWinXPProのCDはSPが当たっていない状態なので、HomeSP2のPCに入れても、警告が出て薦めることができません。そこでProにSP2をあてたCDを作成します。

使用するソフトは「SP+メーカー」
窓の杜でも取り上げられたソフトです。(Tone先生教えてくれて有り難う
これでHomeSP2→ProSP2のアップグレードができるようになります。

MSDNライセンスを取得していれば、SP2のCDも送ってくれるので、取得しておけば良かったなぁ(´・ω・`)
http://www.microsoft.com/japan/msdn/howtobuy/vs2005/subscriptions/

今のノートパソコンからOffice2003を移行することに。
片方のノートPCからOfficeを削除をして、新しいノートへOffice2003をインストール。

ここで力尽きました。
メールやEclipseの移行など、面倒でやってられません・・・。
一度構築した開発環境を移行するほど、面倒なものはない(´・ω・`)

それより、新しいパソコンが届いても今までのノートでBLOG書いてるっていったい・・・。

SQLチューニング

  • Posted by: タムタム
  • 2006年6月 2日 09:43
  • 開発

  • Tags: Database

この前書いたSQLチューニングの結果を。
DBはOracle9iR2。
こーいうのはWikiの方に書くべきかな。。

テーブルは以下のように定義。
NAME VARCHAR2(20)
POINT NUMBER(10,0)  ← こいつにINDEX張ってある

データ量は10万件
ObjectBrowserのデータ生成機能を使って生成。

比較したSQLは以下の通り。
[1]
SELECT NAME
FROM TBL_WORK1
WHERE POINT != 50;


[2]
SELECT NAME
FROM TBL_WORK1
WHERE POINT < 50 OR POINT > 50;

それぞれの実行計画と統計情報は以下の通り。
[1] 99807件選択されました(906 msec.)
------------------------ 実行計画 --------------------------
SELECT STATEMENT   Cost =
    TABLE ACCESS FULL TBL_WORK1

------------------------ 統計情報 --------------------------
recursive calls                                   0
db block gets                                     0
consistent gets                                 548
physical reads                                    0
redo size                                         0
bytes sent via SQL*Net to client            1517840
bytes received via SQL*Net from client         3106
SQL*Net roundtrips to/from client               252
sorts (memory)                                    0
sorts (disk)                                      0
rows processed                                99807

[2]99807件選択されました(672 msec.)
------------------------ 実行計画 --------------------------
SELECT STATEMENT   Cost =
    CONCATENATION  
        TABLE ACCESS BY INDEX ROWID TBL_WORK1
            INDEX RANGE SCAN IDX_TBL_WORK1_01
        TABLE ACCESS BY INDEX ROWID TBL_WORK1
            INDEX RANGE SCAN IDX_TBL_WORK1_01

------------------------ 統計情報 --------------------------
recursive calls                                   0
db block gets                                     0
consistent gets                               16712
physical reads                                    0
redo size                                         0
bytes sent via SQL*Net to client            1517840
bytes received via SQL*Net from client         3143
SQL*Net roundtrips to/from client               252
sorts (memory)                                    0
sorts (disk)                                      0
rows processed                                99807

まぁ統計情報使ってるので、実行時間は当てになりませんが・・・。
実行計画を見ると、1は全スキャンしてるのに対して、2は範囲スキャンをかけていますね。

きちんと動くバックアップスクリプト

  • Posted by: タムタム
  • 2006年5月 2日 18:32
  • 開発

シェルだとどうしても動かないので・・・。
stat使えないし、awkとか使うとエラー吐いてくるので、PHPで作りました(´・ω・`)
これはきちんと動作する事を確認済みです。

<?php
$targets = glob("/virtual/YourID/log/*.log");
$outputdir = "/virtual/YourID/logbk";
$workfile = "/virtual/YourID/logbk/lastdate.work";

// Check output directory exists
if (!file_exists($outputdir)) {
  mkdir($outputdir);
}

// Get last run time
$ldate = 0;
if (file_exists($workfile)) {
  $ldate = intval(file_get_contents($workfile));
}

// Write last run time
// file_put_contents($workfile, time());
// exec("echo " . time() . ">" . $workfile);
$fp = fopen($workfile, 'w');
fwrite($fp, time());
fclose($fp);

// Execute backup
foreach ($targets as $logfile) {
  $fdate = filemtime($logfile);
  if ($ldate <= $fdate) {
    $cpname = $outputdir . "/" . date("YmdHis", $fdate) . "-" . basename($logfile);
    copy($logfile, $cpname);
    exec("gzip -f " . $cpname);
  }
}

?>

ブログを書くならBlogWrite

株価解析ツール 続編

株価解析ツール。
HSQLDBを起動する。出ました。OutOfMemory。もうね・・・。やっぱり無理だよね・・・件数的に。組込用途のDBなのに、36万件も投入したら死にますよね(´・ω・`) てか、全部メモリに展開しなくてもいいがな・・(´・ω・`)

3884銘柄 * 360日 = 余裕で100万件超。絶対にHSQLDBでは無理です。
MySQLに変えました。MySQLのパフォーマンスも見れて一石二鳥だ( ・∀・)

ブログを書くならBlogWrite

株価解析ツールを作ってみました

株価解析ツールを作ってみました。というか、作ってる最中です。

解析するためには、過去のデータが無ければ話になりません。
そういうわけで、まずは、Livedoorファイナンスのランキングデータからコードを取得し、そのコードの過去1年分のデータを抜き出すツールを作りました。約1000銘柄 * 360日 = 36万件。

話はそれますが、ここまでをJavaで作りました。
HttpURLConnectionクラスの挙動がおかしかったです。disposeしても内部でCloseをしていないのか、同一サーバに対して連続通信する事ができませんでした。仕方ないので、Socketクラスを用いてHTTPプロトコルを直接やりとりすることに(´・ω・`)
もう一つ。ストレージにHSQLDBを使っているのですが、さすがにデータ量が多いのか起動に10秒以上かかります・・・・。やっぱりMySQLとかにしたほうがいいんですかね(;´∀`)

さて、解析と言ってもどうやって解析するべきか・・・。
データマイニングみたいなものだし。
とりあえず、単純にUPしたかDOWNしたかを木構造で統計して、確率を求めてみよう。

あとは、似たようなグラフを検索するのも面白そうですね。

ブログを書くならBlogWrite

データベーススペシャリスト

DBスペシャリストの試験を申し込んでいたのですが、寝坊しました。起きたら14時です。もうね・・・。

事の発端は、徹夜でGroovyの調査をしていて、気がついたら3時頃になっていたわけです。まぁ明日は何もないし徹夜しちゃうじぇー(・ε・)と、ふと気がつきました。明日(今日)試験じゃんっ! 今から寝ても遅刻するし、その時点で諦めましたよ・・・。お金もったいなかった・・・。
・゚・(つД`)・゚・ ウェ―ン

Groovyの開発MLにでも入ろうとしたけど、英語が無理なので諦めました。Groovyはしばらく放置して、Strutsを拡張する方向で行きます。StrutsにGroovyの便利なクラスを盛り込む事に。クロージャーそのものを組み込むことは無理でも、似たような事はできると思います。一部のクロージャーは、ある時点での処理を噛ますことができると考えられます。よって、メソッドの引数にインターフェースを渡してあげて、メソッド中から特定のタイミングでインターフェースに定義されたメソッドをコールしてあげれば良いだけ。呼び出すときは、匿名クラスで処理を書いて渡す。

こんな感じかな。。(直接書いてるので信憑性はゼロ)
<<呼び出し>>
ExFile.eachLine ( srcFile, new MethodCallback() {
    public void callback ( Object obj ) {
        System.out.println(obj);
    }
});

<<ExFile#eachLine(File srcFile, MethodCallback callback)>>
BufferedReader in = new BufferedReader(
        new InputStreamReader (new FileReader (srcFile), encoding)
    );
String line = null;
while ( ( line = in.readLine()) != null) {
    callback.callback(line);
}

ブログを書くならBlogWrite

Groovyのバグ

  • Posted by: タムタム
  • 2006年4月16日 00:35
  • 開発

GroovyRowResultクラスにて、値がNULLのプロパティを取得しようとすると、MissingPropertyExceptionが発生する。なんだこの仕様は・・・(;´∀`) バグなのかな(;´∀`)

ブログを書くならBlogWrite

Continue reading

Groovyのスクリプト分割とSQLのfirstRow

Groovyのスクリプト分割の方法について。
Groovyでスクリプトを分割するには、どーすればいいのか・・・。本家のMLに流れている情報によると、クラスパスを通していれば、きちんと使えるらしい。ただ、Groovletでスクリプト分割はできないっぽい・・・。importを書いても、クラスローダを使って読み込んでもだめっぽい・゚・(つД`)・゚・  だれかやり方教えてください(`・ω・´)

GroovyのSQLクラスについて。SQLクラスにfirstRowメソッドがありますが、結果が0件の場合にRangeExceptionが発生します。推測するに、内部的にsql.rows(SQL).get(0)と同じ事をしているのだと思います。get(0)を使用するから例外が発生するので、sql.rows(SQL)[0]で代用。これだと、戻り値が0件の場合はnullが返却されます。

ブログを書くならBlogWrite

ValueDomainのDDNS設定とメール設定

テスト用にドメインを取得しました。
今や1000円も出せば1年間ドメインが使えるので、良い時代になりましたね(ぇ。

今回設定したのは、ValueDomainのDDNS設定。WEBは自宅サーバへサクセスさせ、メールはXREAの有料サーバを利用するという設定です。最終的に設定した内容は以下のとおり。

a @ 自宅鯖IP
a www 自宅鯖IP
mx XREA鯖IP 10
a mail XREA鯖IP

ここで、自宅鯖IPはDDNSを使って更新するため、ValueDomainのネームサーバを使用するように設定します。そして、DICE等のツールを使って更新します。私はWGETを使って更新をかけています。公式ページにはh=*を指定するようになっていますが、この指定は当然ながらAレコードに*の設定がなければ動きません。つまり、「a * 自宅鯖IP」という設定がなければ更新されません。なので、h=@とh=wwwでそれぞれ更新をかける必要があります。てか、mailの設定て必要なのかな(;・∀・) これが無ければ、Aレコードを*で指定してしまっても良いような気が・・・。

ブログを書くならBlogWrite

Gigabitネットワーク

家庭のLANをGigabitネットワーク化しました。

ファイルサーバのNICが100Mでボトルネックになっているので、ファイルサーバのNICをGigabit対応のものに。PLANEXのGN-1200-TWという製品を使いました。パッケージにはLinux Readyとなっており、サポート対象外。CDROMについているソースはカーネル2.4までで2.6のソースは入っていませんでした・゚・(つД`)・゚・ でも、kudzuで一発認識するし、ethtoolで見ると1000Mでリンクしてたので、まぁ・・結果オーライということで。

Gigabit対応のHUBも購入し、P2P兼テレビパソコンとファイルサーバ間をGigabit化。そしてGigabitからTEPCO回線へ。ジャンボフレーム対応との事ですが、MTUをいじると、ファイル転送が遅くなるorz。まぁ暇な時に最適な値を求めるか(´・ω・`)

ブログを書くならBlogWrite

TemplateServletのエンコーディング

Windowsで開発したGroovyのモジュールを、そのままLinuxのサーバ環境にデプロイすると、エラーが発生します。調べてみると、GroovyServletもTemplateServletもエンコーディングを指定していないではありませんか(´・ω・`) マルチバイトの環境下にない人がメインだと、そーいう所はおざなりになっちゃうのね(´・ω・`)

TemplateServletの方はFileReader(file)、GroovyServletの方はGroovyScriptEngineクラスのInputStreamReader(System.in)としているので、それぞれにエンコーディングの指定をかけてあげないと、OSのデフォルトの文字コードで読み込まれるくさい。そんなわけでWindows(SJIS)からFedoraCore5(UTF-8)に持って行くと、文字化けどころか、コンパイルすらできないって事に・・・。オプションでencoding指定をつけてくれてもいいじゃないか!ヽ(`Д´)ノ

GSPの方はencoding指定ができるっぽいなぁ・・。
勝手にコミットしたら怒られるだろうし、ローカルでちまちま修正しよっと(´・ω・`)
開発者とコミュニケーション取りたくても英語わからないよヾ(。`Д´。)ノ彡

ブログを書くならBlogWrite

FedoraCore5環境構築

今日は新宿で適当にまったりして、買い物でもしてました。帰りに商店街にて、同じ袋を持った団体さんがぞろぞろと・・・。HOSHIという名前が書いてあったので、そんな店あったかなーと思いましたが、近くにそんな感じの薬科の大学があったような気がします。そーいう季節なんですねー。うちの会社にも新人さんが入ってきてると思うけど、有給消化中なんで、知りません。電話したときに出た人が新人だと思うけど、自社名を噛むのはどーかと(;´∀`)
ちなみに、私は適当に仕事してると思われがちですが、やることはやってますので(;・∀・)電話もそれなりに対応できますから(;・∀・) 仕事してないときは、やる気がない時か、余裕があるときです。

開発をWindowsで行っていると、Linuxで運用するときにちょっと戸惑う・・・。今日はTomcatとApacheを連携させるために、コネクタを入れようとしたら、apxsが入ってません。ヒドス。yumでインストールを試みるも、IOエラーが発生・・・。たぶんネットワークがおかしいのだろうと思い、しばらく放置した後に再度試みる。何回もやりなおして、やっとこインストールできましたよ。そういうわけで、こんな時間になっているわけですが。。TomcatやめてResinにしようと思ったけど、Resinのコネクタもapxsを要求してくるので、(´・ω・`)ションボリです。最近はJava製品を主に扱っていたので、ネイティブな環境についてはチンプンカンプンですよ(´ー`)┌

とりあえず、JDK5は前にインストールしてたみたいなので、Tomcatをインストール。Tomcatの不要なモジュールを削除。削除。削除。HSQLDBがなぜかサービスに登録されているのですが・・・。やった記憶はないし、標準なのかな・・。とりあえずサービスから削除して開発環境から必要なJarをTomcatのCommon/libに配置。GroovyのJarはアプリケーションに含めているのでWarにしてしまえば問題なし。(゚-゚;)ウーンめんどくさい。
ジェバンニに頼めばきっと一晩でやってくれるんでしょうね(゚ー゚*)

ブログを書くならBlogWrite

Continue reading

GroovyとHSQLDB

最近、Groovyが流行っている(?)ようなのでGroovlet、GSP、HSQLDBを使って簡単なプログラムを作ってみました。ただのアプロダですが( ´_ゝ`) 以前、小規模開発用に自作したフレームワークの移植ということで、コントローラをGroovlet、ビューをGSPという感じで。フレームワークと言っても、非永続化エリアのレイヤー化とディスパッチャーを作ったくらいですが・・・。

やっぱり流行っていないのか、情報が少ない・・・。
とりあえず、Tomcat上に環境を構築してみました。気になった点をいくつか・・・。

  • GSPでTaglibsは使えない?
  • GSPでJSPディレクティブが使えない?
  • Groovletでdefを使って宣言をするとローカル領域へ、def無しだとグローバル領域へ。これで合ってるのかな(;´∀`)
  • 引数の数が同じメソッドがオーバーロードされている場合の適用手順が不明。
    SimpleDateFormatのformatメソッドにはlongまたはdoubleを受け取るように定義されているが、明示的にlongの変数を渡しても、doubleの方がコールされる。
  • Switch構文のCase節でbreakを書かないと、コンパイルエラーが発生する。
    複数の値を同様に評価したい場合、わざとbreakを書かないという方法が使えません・・・。 case [A, B]とすることで同じ事ができるのでよしとする(;´∀`)
  • Groovletでout.printlnとすると、HTMLの方に出力されるが、クロージャー内でout.printlnをすると、コンソールに出力される。
    私のクロージャーの理解不足かな?Rubyとは違う動作なのでちょっと戸惑う。Groovletでout.printすることはないので、別に問題はないけど(;・∀・)

まだまだ情報が少なくて、エラーを多発させてますが、開発効率はなかなか( ・∀・)イイ!!です。特にDBの発行がかなり良い感じ。データソースさえ定義してしまえば、発行が超楽ちんです。ListenerにHSQLDBの起動、シャットダウンをするクラスを登録すれば、さらに楽ちん。

これがメジャー言語のオルタナティブになるとは思いませんが、Java開発者がちょっとしたツールを作るにはなかなかいい言語だと思います。まぁPerlとかRubyとかPHPとか使えばいいのかもしれませんが、最近の「Javaが出来ます!」と言って入ってくる技術者はJavaしか出来ない人が大多数を占めているので(・Θ・;) それならまだしも、Java技術者なのにJava出来ない人とか・・・(´・ω・`)

そうそう・・・傘をぱくられました。・゚・(つД`)・゚・ ウェ―ン
戸越銀座駅前のマツキヨで傘ぱくった人、死んでください!!となりに100円ショップあるんだから、ぱくらないで買え!ヽ(`Д´)ノプンプン
TSUBAKIとアジエンスのどっちにしようか迷っている間に・・。くそぅ。

ブログを書くならBlogWrite

フレームワーク

  • Posted by: タムタム
  • 2006年3月12日 23:18
  • 開発

こんにちは、アーキテクトを目指しているタムタムです。

最近のStrutsは機能が付きすぎて重量級フレームワークになってしまいました。ちょっとした小さいアプリケーションを作る場合、Strutsは確かに便利ですが設定やら環境面やらで、イニシャルコストがかかります。さらに、殆どの機能は眠ったままになります。(業務アプリを作る場合は別ですが。)

そこで、土日の休みを利用して軽量フレームワークを作ってみました。思想としては、コンパクトかつ高速でイニシャルコストがかからないこと。
※ここで言っているイニシャルコストは、「さぁ作ろうかな!」から「実際にビジネスロジックを組む」までの時間です。前に仕事で作った.NETのフレームワークの思想を元に、IBMが作った非公開のフレームワークの思想をぱくりました。

ときに、SimpleDateFormatのスレッドセーフ化をしていたら、Commonsに既にあるではないか(;・∀・)Log4jやらTomcatのDateFormat系のクラスを眺めていたのがバカバカしい(´・ω・`)久しぶりにソースを読んで知恵熱が出そうでした。(Log4jのソースって綺麗じゃないよね・・・(;゚Д゚))

Continue reading

Eclipse

  • Posted by: タムタム
  • 2005年5月 5日 23:59
  • 開発

職場ではWSADを使用しているのですが、Eclipseで同じ事を実現してみようと思いました。って前にもこれやってるような・・・。Eclipse3.02だと何回やっても上手くいかない箇所が出てくるので、諦めてEclipse3.1系をベースにやりました。これだと日本語化できないんだよね(・・;)

EclipseのWebToolsを入れました。そんだけです・・・。
一部ejbdocletの設定ページが開けません。原因はわかりません∑( ̄□ ̄;)
もう、ここに書いてある通りに設定しました。
あとは、ここの設定も。

IEのバグ

  • Posted by: タムタム
  • 2005年4月30日 10:15
  • 開発

開発チームの人が発見したIEのバグです。チェックボックスにフォーカスを当ててスペースを押しながらTABキーを押します。すると、チェックボックスの背景の色が白からグレーっぽく変わります。その状態でいろいろな場所をクリックしてみましょう。関係ない場所をクリックしても、チェックがON/OFFされます。
ま、致命的じゃないからいいんだけどさ・・・。

SUSE Linux

  • Posted by: タムタム
  • 2005年4月24日 18:15
  • 開発

WindowsXPが入っていたパソコンを真っ新にして、こいつを入れてみました。まぁこのパソコンに入った居たのは、WebsphereとWebLOGICとOracle9iしか入ってなかったし。会社の名前使って勝手に体験版を貰いました(〃⌒∇⌒)ゞ
今のLinuxのインストールってすごい簡単ですね。それにデスクトップ環境もかなり綺麗になってる。といっても、サーバ用途のためのインストールなのでGUIはいらにゃい。それにSSH使ってメインのパソコンから操作するし。
・・・シェルコマンド覚えてないΣ(゚д゚lll)
勉強し直そう(´・ω・`)

BLOG on Servlet

  • Posted by: タムタム
  • 2005年4月17日 22:14
  • 開発

Blojsom, Roller というJavaで実装されたBLOGシステムがあるようです。
MTをJavaに移植する必要はなさそうですね(*゚▽゚)
ソースコードはまだ見てないのですが、綺麗なソースだったら、仕事中にちょっといじくって遊んでみよう。(だって、手持ちの作業が完了しちゃっててヒマなんだもん(^▽^;))
さらっと見た感じ、Blojsomがよさげなんですが、どういうライセンス形態なんだろう。ファイル見るとApacheライセンスVer1.1っぽいけど、Apacheライセンス自体どういうものか知らないし・・・。著作権の表示を消さなければイイヨって事なのかな?英語が読めない(´▽`*)

プレゼン層はJSPとVelocityっぽいですね。
MTみたく、オンラインでテンプレートを編集できたりするのかな・・・
ストレージはDBじゃないっぽいんですが・・・。ストレージの部分のインターフェース作ってファイルかDBか切り替えられるようにすればいいのに・・。詳しく見てないから、そう言う作りになっていたらごめんなさい。
ちょっと解析してみよう。

これを改造して公開しても良いのかな(;・∀・)

Continue reading

コンプライアンス

  • Posted by: タムタム
  • 2005年3月10日 22:54
  • 開発

今の職場、かなりコンプライアンスが厳しいのです。
ネットに繋げないのは別に構いません。それが普通だと思いますし。(某森ビルの中にある某企業みたくギガビットの回線を使い放題という職場もありましたが・・・)
そんなのは些細な事です。

ただ、工数管理とかがめちゃくちゃすぎます。作業時間に関しては4重管理になってます。多重管理もたいがいにして欲しいです。時間の無駄です。出退勤の時間をパソコンに打ち込んで、納品書に作業内容と作業時間を書いて、工数表に作業時間を書いて、週報に作業時間と作業内容を書く。アホらしくてやってられません。しかも、この週報ってやつがくせ者で、管理する人の立場で作ってあるから書きづらい。
作業の内容が50種類以上あって、この作業をしたときはXXXを書いてください。みたいな説明がずらずらと・・・。今は納品作業をしているのですが、この納品作業ってやつが作業リストにないんですよ。話になりませんな。
これ書くために、何十分かかると思ってるんだ・・・。時間の無駄だ。
しかも毎日、定例MTして作業の内容等を報告しているんだから、その報告からマネージャやPLが作れば良いじゃないか・・。現場からは不平不満の声が。作業リストと実際の作業の内容が一致してないし。
管理マニアも困ったもんですなヾ(´ー`)ノ

打ち合わせ

  • Posted by: タムタム
  • 2005年2月 8日 23:59
  • 開発

仕事が一番忙しい時期に、定時で出向先を出て本社へ向かう。
講習会の講師なんて面倒でやってられませんよヾ(´ー`)ノ
もっと割にあったお金が欲しいですね。
時間外の作業が15,6時間取られて、おまけに移動時間も含めるともっと超える。さらに当日8時間として24時間。これを1万円でってのがおかしい。時給に換算すると、最低賃金を下回って労働基準法違反してるじゃん。
しかも、いつも忙しい時期とぶつかるから私生活を圧迫されるんですよね。もう少し考えて欲しいです。

次の講習会で3回以上講師が割り当てられていたら会社ヤメヨウっと・・・。

トリガー

  • Posted by: タムタム
  • 2004年12月 8日 23:40
  • 開発

作成する画面の数と工数を減らすために、DBトリガーを使用することになったのですが、PLSQLに慣れてないから今日一日くらいかかっちゃったよ・・・。70行くらいの関数だったんですが、トリガーのデバッグって難しいですね。何か良い方法あったら教えて欲しいデス。
数値上は進捗前倒しになってるけど、実は結構間に合わなそう・・・。休日出勤の予感(゚◇゚)

ソースコード

  • Posted by: タムタム
  • 2004年12月 2日 23:59
  • 開発

あのですね・・・。なんていうか・・・。
ソースコード管理にはCVSとかSubVersionとかVSSとかが有名どころだと思いますが、今の開発ではPVCSというものを使ってます。PVCSには最低限動くモノをUPしてくださいよ・・・。正常ケースすら通らないソースコードUPすんな!!自分一人で担当している新機能ならともかく、既存の主要な機能のところをいじられると、作業が止まるんです!

それと、入力チェックもかけなさい!不正な入力したら落ちるとかまぢであり得ないんですけど。これパッケージソフトでしょ。あと、ライブラリ使わないで独自の処理を実装するのもやめてください。特にライブラリのソースコードをそのままコピペして自分のソースに貼り付けるとかまぢアリエナイ。ライブラリの意味ねーじゃん。そのライブラリにバグがあった場合、修正箇所増えるんだよ。まぁ私が修正するわけじゃないから別にいいけど(-ω-#)y-~~~~

あと、お前が書いたコメントだけ綺麗に文字化けしてるんだよ。なんのエディタ使ってるんだ。そのままUPすんな。2chとかでアリエナイ話を良く聞くけど、実際に目の前で起こるとぐったりします。脱力です。まぢで開発に遅延が生じる原因です。


普通、WEBサーバとAPサーバをわける場合、HTMLとかIMAGEとかスタイルシートはWEBサーバの方へ、プログラムはAPサーバの方へとわけるわけですが、スタイルシートをAPサーバの方に置くな!しかも href=/exec/xxx?file=xxxx/xxxx/xxx.cssとかAPサーバにスタイルシート処理させるなよ。それやると毎回DBにアクセスして役割によるアクセス権調べる仕組みになってるんだから・・・。どういう思考してるんだ・・・。
さらに、システムで用意してるスタイルシート使え・・・。独自のセンスなきデザインなんかするからレビューでボロボロに言われるんだ。しかも誰もフォローしてないしヾ(´ー`)ノ

本当にパッケージソフトの開発なのかな・・・これは。

バグ

  • Posted by: タムタム
  • 2004年11月22日 23:59
  • 開発

<(゚ロ゚;)>ノォオオオオオ!!
お客さんからバグっぽいものが報告されました(ノ_・、)シクシク
計算結果の小数点以下を切り捨てたつもりが、おもいっきり四捨五入してました・・・。
そりゃ計算合わないわ(´▽`*)アハハ

・・・・ごめんなさい。PTFで直します。


それよりも、新しい案件が2人月でスケジューリングされてるけど、正月挟むから正直2人月もないじゃん・・・しかも画面数多すぎて普通に終わらないから。難易度で見積もるんじゃなくて、作業量も考慮して人月計算を!
ヾ(。`Д´。)ノ彡☆ブーブーッ!!

テスト

  • Posted by: タムタム
  • 2004年11月20日 16:22
  • 開発

自分が作成した機能はある程度デバッグをしなければいけませんが、私たちのチームでは開発者が製品評価に渡す前に単体テストしてます。
つまり、テストデータを作ったりテストケースを書いたりとかなり面倒なわけなんですが、半日かけて作ったテストデータを勝手にいじられると腹が立ちますヽ(`Д´)ノプンプン

テスト中にデータが変わったりして、あれ・・・って思うと勝手にデータをいじくられてたりして・・・他人のテストデータを使うなYO!!

バージョン管理

  • Posted by: タムタム
  • 2004年11月20日 16:14
  • 開発

いま、お仕事で使用しているバージョン管理ソフトがPVCSっていうやつなんです。
PVCS自体の機能としては結構良いと思うんだけど、クライアントがタコすぎて使いものになりません。
1Mのファイルのリビジョンが100超えたあたりから、チェックイン/チェックアウトに5分くらいかかるように・・・。
ちょこちょこ更新して履歴を取っておきたい場合、プロジェクト全体で使用しているPVCSにUPするのはとても危険なので、自分のPCにバージョン管理ソフトを入れてることにしました。

そういうわけで、開発用のPCにCVSとSubVersionを入れてみました。
SubVersionはApache2と連携できなかったので終了・・・。なんでだろう。参考サイトの通りにやったのに(ノ_・。)
そういうわけでCVSをインストール。ついでにエクスプローラ(シェル)と連携するCVSのツールを入れてみる。なかなか使い勝手がよかったけど、ファイルを登録したら、APサーバがそのファイルを読み込まないようになってしまった・・・。

もうね・・・ もうね・・・。゚゚(´□`。)°゚。ワーン!!

リリース直前のバグ

  • Posted by: タムタム
  • 2004年11月20日 16:10
  • 開発

リリース2時間前にバグ出ちゃいました(゚◇゚)~ガーン
PTFでいいっていってたけど、PTFだとドキュメント書かないといけないからなんとかリリース版に載せてもらうように、急いで修正・・・(〒_〒)
普段ならこんな修正1時間もかからないのに、てんぱってて自分で何やってるのかわからなくなりましたヽ(`Д´)ノ

しかし・・・なんであんなアフォなSQL文書いたんだろう・・。謎。

Groovy 再び

  • Posted by: タムタム
  • 2004年9月25日 23:26
  • 開発

Tomcat上でGroovyを動かしてみました。
requestとresponseとout等がグローバルなだけで、後はJSPと使い勝手は同じですかね。ただ${}が使えたり、println << ただ、Javaの膨大なクラスにフルアクセスできるのは良いですね。速度も全然気になりません。

それで、
ちょっと掲示板っぽいものを作ってみたんですが、なんか日本語が出力できないので挫折しました。Google先生に聞いてみるとどうやらgroovy.servlet.GroovyServletを継承して作り直さないといけないようで・・・わー面倒だ。ソース見る時間もないので( 。・・)/⌒□ポイするはずだったのですが、気が変わってソースを見てました。
まぁなんて単純なソースコードなんでしょう。
setContentTypeでcharsetをきちんと指定してあげると日本語が出力されました。グローバルオブジェクトも簡単に追加できます。ちょこちょこと改造してみようと思います(-ω-)

Groovy

  • Posted by: タムタム
  • 2004年9月23日 15:37
  • 開発

巷で流行っているJavaVMで動作するスクリプト言語Groovyを少し触ってみました。
しばらくはコレで遊んでいこうと思います。後でGroovyのページを作ろうっと・・。
マニュアルをちょろっと見た感じだと、JavaScriptっぽいんだけど、J2SEのAPIにアクセスできたりするようです。ただ文法的にはRubyに近いような気がします。

サンプルコードを少し動かしてみましたが、
JVM上で動くだけあって起動が遅いです。これはJVMのせいだと思いますが、スクリプト言語で実行までの時間が長いってかなり致命的だと思うのですが・・・。JUNITが標準で組み込まれているのでテストケースをさらっと書き上げるくらいしか用途がないような。

Groovletというのもあるそうですが、これでJSP、Servletの変わりになるそうです。
こちらも試してみようかな。

EmEditorの色分けファイルを誰か作ってください(-ω-)
Groovyプラグインが公式サイトでイロイロ紹介されてますが、殆どがマックのエディタのやつなのが悲しい。

PEレビュー

  • Posted by: タムタム
  • 2004年9月11日 08:05
  • 開発

PEの人たちが開発中のシステムを見るという事を聞かされて、勝手にカスタマイズしたスタイルシートを慌てて元に戻しました。まぁ私の開発マシンから見ない限りは問題ないんだけど。
PEの人たちとプロマネの会話に聞き耳を立ててたら、「ずれる」とか「バグ?」とか「CSV」とかいう単語が聞き取れたので、慌てて自分が担当した所を見るとバグが発覚。
後で聞いてみると、全く関係ない事だったらしい。まぁバグが見つかって良かった(*゚▽゚*)

そのシステムのTOPページを見ると「代表の家」というリンクがあってクリックしてみると、元麻布ヒルズが・・・。ふむふむ家賃が240万円ですか・・・。ジョークか何かだろうと思ってプロマネに聞いてみると、どうやら本当に200万円/月 くらいのところに住んでいるらしい。間取りを見ると、一部屋の面積が、私が今住んでいる物件(2Kの33㎡)よりも広いんですが・・・。

レビュー

  • Posted by: タムタム
  • 2004年9月 8日 00:02
  • 開発

レビューって大切ですね。
仕様を作ったら、一回レビューをするべきだと思います。
実装までしてから、初めて仕様を見せて貰って愕然とするわけです。
進捗会議で仕様のレビューをするとか意味不明なんですけど(人が作った仕様を叩くのは好きなので別にいいんですが・・・。)

先輩とか上司に相談してDB設計をしましょう。と心から願うばかりです。
(私も人の事言えないけどね。)

Home > 開発 Archive

Search
Feeds

Return to page top