2010年1月16日土曜日

何のものさしかと思えば

http://careerzine.jp/monosashi/search
上場企業の年収って、情報ソースはどこだろう?

2010年1月10日日曜日

ASP.NETでのstatic変数の初期化と利用

先週、職場でASP.NETについての質問を受けましたが、よく分らなかったので週末調べてみました。
質問内容は、(Webサーバは1台でスケールアウトしないものとして)ASP.NETアプリ全体で利用する変数を使う場合に、static変数を使って、Application_Startで初期化する使い方で問題ないか?というもの。

これを調べるに当たって、「.NET エンタープライズWebアプリケーション開発大全Vol.3」とMSDNを利用しました。

まず、Vol.3のP.131
>プロセスリサイクリングやフェイルオーバ時、システム再起動時、アプリケーションリスタート時などにはデータが消失する。
とあります。「など」という言葉が非常に気持ち悪いので、データが消失するのは上記以外に無いものと仮定します。


で、フェイルオーバ時、システム再起動時は判るとして、プロセスリサイクリングとアプリケーションリスタートっていうのが
それぞれ、P.188と、201に書いてある。
プロセスリサイクルとは、プロセス単位で、新プロセスを立ち上げて次回以降の要求を新プロセス側で処理するもので、旧プロセスは(受付処理が終わるか、直ちに)落とされるというもの。
それに対して、アプリケーションリスタートでは、同じプロセスの中に新たなアプリケーションドメインを立てて旧アプリケーションドメインでは受付しなくなるというもの。

なので、プロセスリサイクルとアプリケーションリスタートの違いは、新プロセスを起すか起こさないかの違いで、新アプリケーションドメインを立てるのは違いが無いと読み取れる。

で、Application_Startはいつ呼ばれるかというと、
http://msdn.microsoft.com/ja-jp/library/ms178473(VS.80).aspx
で、

ASP.NET アプリケーションの最初のリソース (ページなど) が要求されたときに呼び出されます。Application_Start メソッドは、アプリケーションのライフ サイクル中に一度だけ呼び出されます。このメソッドを使用してデータのキャッシュへの読み込み、静的な値の初期化などのスタートアップ タスクを実行できます。

とあります。

で、結論(アプリケーションドメインでアセンブリをロードした段階ではリソースがまだ要求されていないので、)アプリケーションリスタートでもワーカープロセスリサイクリング時でもどちらでもその後のリクエスト1個目のときにApplication_Startは呼ばれていそうです。

2次元座標の距離のSQL

遅いっていって見せてもらったSQLがSQL1のもの、
(px,py)からの距離がdistanceの間にあるものを近場から最大100件表示するって言うもの。
Where区が列の計算式となっているためインデックスが聞かない。
距離を測るには、いきなり範囲円内で比較するのではなく、対称点を中心とした正方形の範囲内でまず絞ってから、次に範囲円内にあるかを調べたほうがいいんじゃないかなという記事です。
(ちなみに、3次元なら立方体→球ってな感じで。)


まずは、テーブル準備。こんな感じなテーブルにして見ます。

CREATE TABLE GeoData(
Id int IDENTITY(1,1) NOT NULL,
Name nchar(10) NULL,
X real NOT NULL,
Y real NOT NULL,
CONSTRAINT PK_GeoData
PRIMARY KEY CLUSTERED ( Id ASC) ON PRIMARY)
ON PRIMARY

で、テーブルデータ投入。満遍なく10万の点を投入したあと、x座標が300~310の範囲に満遍なく10万の点を投入し、最後にy座標が300~310の範囲に満遍なく10万の点を投入してみる。

declare @i as int
set @i=0
while @i < 100000
begin
insert GeoData(Name,X,Y)values(@i,rand()*10000,rand()*10000)
set @i=@i+1
end

set @i=0
while @i < 100000
begin
insert GeoData(Name,X,Y)values(@i,300+rand()*10,rand()*10000)
set @i=@i+1
end
while @i < 100000
begin
insert GeoData(Name,X,Y)values(@i,rand()*10000,300+rand()*10)
set @i=@i+1
end

このテーブルにインデックスをはります。

create index GeoData_X on GeoData(X)
create index GeoData_Y on GeoData(Y)


このテーブルについて、結果が同じSQL3つ(SQL1:元のSQL,SQL2:高速化を期待して作ったSQL文,SQL3:SQL2について最適なインデックスを使うように、毎回実行プランを再作成することを指定したSQL文。

流すSQLはこんな感じ。
DECLARE @SQL1 nvarchar(MAX),@SQL2 nvarchar(MAX),@SQL3 nvarchar(MAX),@ParmDefinition nvarchar(MAX)
SET @ParmDefinition = N'@px real,@py real,@distance real'

SET @SQL1 =N'select top(100) Id,Name,X,Y,distance from
(select Id,Name,X,Y,SQRT(SQUARE(X-@px)+SQUARE(Y-@py)) as distance
from GeoData) as Tab where distance< @distance
order by distance'

SET @SQL2 =N'select top(100) Id,Name,X,Y,SQRT(distance2) as distance from
(select Id,Name,X,Y,(X-@px)*(X-@px)+(Y-@py)*(Y-@py) as distance2
from GeoData where X > @px-@distance and X < @px + @distance
and Y > @py-@distance and Y < @py + @distance ) as Tab where distance2< @distance*@distance
order by distance'

SET @SQL3 = @SQL2 + N' option(recompile)'


exec sp_executesql @SQL1,@ParmDefinition,1000,1000,150
exec sp_executesql @SQL2,@ParmDefinition,1000,1000,150
exec sp_executesql @SQL3,@ParmDefinition,1000,1000,150

exec sp_executesql @SQL1,@ParmDefinition,305,1000,10
exec sp_executesql @SQL2,@ParmDefinition,305,1000,10
exec sp_executesql @SQL3,@ParmDefinition,305,1000,10

exec sp_executesql @SQL1,@ParmDefinition,1000,305,10
exec sp_executesql @SQL2,@ParmDefinition,1000,305,10
exec sp_executesql @SQL3,@ParmDefinition,1000,305,10


この結果が下の画像。(今回は厳密さよりも見易さのため、XMLではなく、グラフィカルな実行プランを貼り付けます。)




この結果からだと、SQL1よりもSQL2のほうが20倍ぐらい早くて、SQL2とSQL3の違いはほとんどない。
SQL2,3が同じ実行プランを返すみたいなので、結局はSQL2がいいみたいです。
それでは。