くりーむわーかー

プログラムとか。作ってて ・試しててハマった事など。誰かのお役に立てば幸いかと。 その他、いろいろエトセトラ。。。

2016年02月

GitbucketをWindowsサービスにする

Gitbucketを自前サーバで使ってるけども、、 コマンドプロンプトで「java -jar ~~」って実行するのでプロンプトが出るのがかっこ悪い。 あと、再起動とかした時に勝手にやってほしい。

スタートアップとかタスクでどうこうできそうだけども、せっかくなのでWindowsサービスを作る。

Windowsサービスを作る手順はこのサイトで。

作ったら、ソースを下の感じにする。

System.Diagnostics.Process p=null;
public void CallGitbucket()
{
    p = new System.Diagnostics.Process();
    //javaの実行ファイル
    string javaexe = System.IO.Path.Combine(@"C:\ProgramData\Oracle\Java\javapath","java.exe");
    p.StartInfo.FileName = javaexe;
    //実行引数
    p.StartInfo.Arguments = @" -jar gitbucket.war --port=8890 ";
    //もろもろフラグ
    p.StartInfo.UseShellExecute = false;
    p.StartInfo.RedirectStandardOutput = false;
    p.StartInfo.RedirectStandardInput = false;
    //ウィンドウを表示しないようにする
    p.StartInfo.CreateNoWindow = true;
    //起動
    p.Start();
}
//サービス開始時
protected override void OnStart(string[] args)
{
    CallGitbucket();
}
//サービス停止時
protected override void OnStop()
{
    if(p != null)
    {
        p.Kill();
        p.Close();
        p.Dispose();
        p = null;
    }
}

無理やり。止めるときの書き方を調べずにとりあえず書いたので 合ってるかは不明。絶対違うと思う。おいおい調べる。

まーでもとりあえず動いてるみたいよ。

ついでに、作ったサービスのExeの登録に参考サイトは「installutil」使ってるけど、VisualStudioのコンソール上でやるかSDKのフォルダにパス通しておかないと普通には使えない。登録するだけなら、↓のコマンドで行ける。

sc create service_name binPath= "*********\WindowsService1.exe"

--2016/4/6追記
start 使えばコマンドだけでもバックグラウンドに回せるかと思ってたけど、 試してみたら、Javaのコンソールが上がってしまった。ダメじゃん。 んーやっぱサービスにするプログラム作らないといい感じにはならないかな。 試したのは↓のコマンド。

start java -jar gitbucket.war --port=8008 --gitbucket.home=*********

C#でバルクインサート

大量のデータを出来るだけ高速にテーブルに入れたい時の性能的な比較メモ。

試しの環境

DBMS:SQLServer2012
DB機: CPU:2.6G(1コア) RAM:8GB
PG動かした端末 : CPU:2.6G(4コア) RAM:8GB

試しに使ったテーブルは下の定義

create table TEST01(
test01 int,
test02 int,
test03 varchar(100),
test04 varchar(100)
)

最初のためしは1万件のデータをインサート。①1件毎にインサート文を発行、②StringBuilderにインサート文をためて1000件毎に発行。③BulkCopyを使って1000件毎に実行。で下が結果。

①Insertを1件毎
98.32秒
②StringBuilderにInsertをためて、1000件たまったら実行
15.34秒
③Bulkを使って1000件たまったら実行
0.57秒

Bulkが圧倒的でした。この辺りは割とネットでもよく見かける。それじゃつまんないので、前々から非同期で動かしてみたかったので、次はBulkをTaskを使って非同期実行した場合の結果。100万件のデータを10万件毎にBulkCopy。

①Bulkを使って10万件たまったら実行
15.36秒
②Bulkを使って10万件たまったら非同期実行
PG上の戻りは1.8秒
DB上で全件登録されるのが7秒くらい

非同期実行かなり早い。やっぱ制御をさっさと戻したい場合は非同期がいいよね。ただ、やっぱ非同期なので、制御が戻ってきても、DB上の登録が終わってない。PG側が終わるのは2秒くらいだけど、DB側で全件登録終わるのは7秒くらいかかってた。

ついでに、やっぱ非同期なので、データが登録される順番はランダムになるっぽい。順番にインサートする必要があるなら使っちゃダメね。あと、今回はDataTableにデータを入れてBulkCopy実行したけど、実行中はその分のメモリがPGに必要になるので大規模すぎるデータでやる場合は同時に実行するTaskの数に制限をかけたりといろいろ考えないとだめかな。まぁ、当然の話です。

でも、BulkCopyを実行している間に次のデータの読み込みが出来るのはいい感じ。

非同期実行でBulkCopyするサンプルは↓

//バルクコピー本体
public void BulkInsertProc(string targetTable, DataTable wkDt)
{
    string ConnectionString = GetConnectionString();//接続文字列
    using (SqlConnection cn_ = new SqlConnection(ConnectionString))
    {
        try
        {
            SqlBulkCopy bc = new SqlBulkCopy(cn_);
            bc.BulkCopyTimeout = 3600;//適当に1時間
            bc.DestinationTableName = "[" + targetTable + "]";
            cn_.Open();
            bc.WriteToServer(wkDt);
        }
        catch (Exception e)
        {
            throw e;
        }
    }
}
//↑をTaskで呼び出す。
public async void DoBulkAsync(string wkTableName, DataTable wkBulkDt)
{
    await Task.Run(() =>
    {
        BulkInsertProc(wkTableName, wkBulkDt);
    });
}

ちなみにDataTableは定義を作るのが面倒なのでSQLで適当にSELECTしてその結果を使う。↓の感じ。

//SQL発行して結果をDataTableで戻す関数
public DataTable ExecSql(string p_query)
{
    string ConnectionString = GetConnectionString();//接続文字列
    DataTable wkDt = new DataTable();
    try
    {
        using (SqlConnection cn_ = new SqlConnection(ConnectionString))
        {
            cn_.Open();
            SqlCommand command = new SqlCommand();
            command.CommandTimeout = 3600;//タイムアウトの設定
            command.CommandText = p_query.ToString();
            command.Connection = cn_;
            using (SqlDataReader sqlDr = command.ExecuteReader())
            {
                //SQL発行結果の編集
                GetSchemaDataTable(sqlDr, wkDt);//DataTableの定義をSqlDataReaderから作る
                while (sqlDr.Read())
                {
                    DataRow nr = wkDt.NewRow();
                    object[] dtArray = new object[wkDt.Columns.Count];
                    sqlDr.GetValues(dtArray);
                    nr.ItemArray = dtArray;
                    wkDt.Rows.Add(nr);
                }
                wkDt.AcceptChanges();
            }
        }
    }
    catch
    { }
    finally
    { }
    return wkDt;
}
//DataTableの定義をSqlDataReaderから作る関数
private void GetSchemaDataTable(SqlDataReader sqlDr, DataTable wkDt)
{
    DataTable schemaDt = sqlDr.GetSchemaTable();
    foreach (DataRow schemaDr in schemaDt.Rows)
    {
        string columnName = schemaDr["ColumnName"].ToString().Trim();
        string dataType = schemaDr["DataType"].ToString().Trim();
        DataColumn dc = new DataColumn();
        dc.ColumnName = columnName;
        dc.DataType = System.Type.GetType(dataType);
        wkDt.Columns.Add(dc);
    }
}

もっと美しく書けると思うけれども、、、やっつけ。

後は、↑で取ってきた定義のDataTableをテンプレとして持っておいて、 必要な時にCloneでオブジェクト作る。ただの代入だと、参照になって、元も変更されてえらいこっちゃになる。 下の感じ。

DataTable bulkDt = ExecSql("SELECT * FROM TEST01");
DataTable bulkTmpDt = bulkDt.Clone();
//一行追加
var addRow = bulkTmpDt.NewRow();
addRow.BeginEdit();
addRow["test01"] = 1;
addRow["test02"] = 2;
addRow["test03"] = "TEST1";
addRow["test04"] = "TEST2";
addRow.EndEdit();
bulkTmpDt.Rows.Add(addRow);
//BulkCopyの実行
DoBulkAsync("TEST01", bulkTmpDt);

そーいえば、BulkCopyって.Net2.0から使えるんですね・・・。もっと最近のものだとばかり思っていた。なんか損した気分。。。

続きを読む
問合せ