| 2003年11月27日
『どっとねっとと雑多な日々 16』
皆さん、こんにちは。
前回修羅場の模様を生々しく書きました結果を報告したいと思います。何とか玉砕せずに、暫定的な対応も含め、無事にカットオーバーできました。
今回は仕様的な問題以外にもソフトウェア(パッケージなどのミドルウェア)のバグにずいぶん泣かされました。
プロダクト名を出すと問題があるので、それ以外のところで簡単に説明すると、エラーログを出力するエラーロガーが Thread
Safe ではないために、エラーが立て続けに起こるとミドルウェアがハングアップするという問題とミドルウェアの ConnectionPooling
を使うとアプリケーションデッドロックが発生し、アプリケーションがだんまりになってしまうという問題でした。
結局、前者の方はパッチを当てたり設定を変えることで回避できましたが、後者のほうは最悪で、自前で ConnectionPooling
を実装して回避しました。
#しかも 1 日で。
開発環境が .NET ではないので、ざっと変換したロジックはこのような形です。
using System;
using System.Collections;
using System.Data;
using System.Data.SqlClient;
namespace Connections
{
/// <summary>
/// ConnectionObject の概要の説明です。
/// </summary>
public class ConnectionObject
{
public SqlConnection connection = null;
public bool usedFlag = false;
public ConnectionObject(SqlConnection _connection)
{
this.connection = _connection;
this.usedFlag = true;
}
}
public class Databases
{
private const int CONNECTION_INIT = 0;
private const int CONNECTION_CLOSE = 1;
private const int CONNECTION_OPEN = 2;
private static ArrayList alConnection = new ArrayList();
private static bool initFlag = false;
private static int threadCount = 0;
private ConnectionObject connectionObject = null;
private string connectionString = "";
public String ConnectionString
{
get
{
return this.connectionString;
}
set
{
this.connectionString = value;
}
}
public Databases()
{
initFlag = true;
}
public Databases(string _connectionString)
{
initFlag = true;
this.connectionString = _connectionString;
}
public void Execute()
{
this.ConnectionControl(CONNECTION_INIT);
//int coFind = 0;
SqlConnection sc = this.ConnectionControl(CONNECTION_OPEN);
// SQL 処理
this.ConnectionControl(CONNECTION_CLOSE);
}
private SqlConnection ConnectionControl(int mode)
{
int i = 0;
lock(alConnection)
{
SqlConnection sc = null;
if(mode == CONNECTION_INIT)
{
threadCount++;
return null;
}
else if(mode == CONNECTION_CLOSE)
{
if(threadCount == 1)
{
while(alConnection.Count > 0)
{
ConnectionObject co = (ConnectionObject)alConnection[0];
co.connection.Close();
alConnection.RemoveAt(0);
}
threadCount = 0;
}
else
{
threadCount--;
connectionObject.usedFlag = false;
}
return null;
}
else
{
for(i = 0; i < alConnection.Count; i++)
{
ConnectionObject co = (ConnectionObject)alConnection[i];
if(!co.usedFlag)
{
co.usedFlag = true;
connectionObject = co;
break;
}
}
if(connectionObject == null)
{
sc = new SqlConnection(this.connectionString);
sc.Open();
ConnectionObject co = new ConnectionObject(sc);
alConnection.Add(co);
connectionObject = co;
}
sc = connectionObject.connection;
return sc;
}
}
}
}
}
本当にざっくり書いているので、これだと動かない場合もあると思いますが、雰囲気は伝わると思います。
また、この ConnectionPooling 機構はマルチスレッドでないと威力を発揮しません。
私が携わった環境ではマルチスレッドで検索更新削除が同時に行われるためにConnection を使いまわすことはしていません。
#Connection を使いまわすとアプリケーションでデッドロックが発生してし まうために Connection をスレッドごとに作成しました。
この仕様は、Windows では必要ないと思いますが、もし自前で ConnectionPooling を実装する場合は参考にしてみてください。
●最後に
データベースでやろうと思えば何でもできますが、パフォーマンスの観点で捉えると、何でもデータベースでやろうと思ってはいけません。たとえば、Web
ページのページングを考えた場合、毎回データベースに問い合わせて、x 〜 y 件のデータを取るというような形で実装するのではなく、可能であれば、DataCache
を使うを検討してください。また、SQL Server には一時テーブルという強力かつ高速な機能があるので、処理が遅いと感じているのでしたら、一時テーブルを使うことを検討してください。
お客に、できること、できないこと、パフォーマンスのことをはっきり言う勇気を持ってください。
コンピュータは命令どおりのことしかできませんので。。。
|