Home > 開発 Archive

開発 Archive

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