秋月 巌 AKIZUKI,Iwao
| PCDNカンファレンスと締め切り |
|---|
今日,PCDNのカンファレンスから帰ってきてこの原稿を書いている.PCDNのカンファレンスには本誌の執筆者の何人かが参加していたが,私以外にも原稿を提出していない不届き者がいた.類は友を呼ぶのだろうか.しかしこれで安心である.少なくとも私の原稿が最後になるという事態だけは回避できる.しかし編集部にとって,この時点で原稿が揃っていないのは大変なことなのである.私にとっても「早く書きあげると他の人の原稿と時期が重なって整理が大変だろうから気を遣って遅めにしておこう」という言い訳が通用しなくなる(お気遣いお気持ちだけ頂戴します:編集部).
ちなみにこの連載の担当者である,フミッチーこと美人編集女史の原稿催促は過酷である.ゆえに私はその期間メールを読むことができない.現在,特定の相手からのメールはダウンロードしないメールクライアントアプリケーションを開発中である.
| 先月はActive Server Pages + ADOを利用 |
|---|
先月のこの連載では,Active Server Pagesのデータベースアクセス機能をデータベースサーバーとして流用し,利用する方法を解説した.この方法はいわば“寄り道”として解説したのだが,Windows NT 4.0 Option Packに付属するAccessのODBCドライバの完成度の高さを考えると,案外,最善の方法になる可能性もある.
現時点で完成しているデータベースサーバーType1〜4までの特徴を表1に再掲載する.これらのサンプルは本誌付録のCD-ROMに収録されているほか,最新のモジュールが秋月巌ソリューション事務所のホームページ(http://akizuki.adsp.or.jp/sample/)よりダウンロード可能である.サンプルを実行する際には,Visual Basicに付属のBIBLIO.mdbファイルをBIBLIOというデータソース名でODBCに登録する必要がある.もちろん,サンプルアプリケーションのODBCデータベース名を変更することで,他のデータベースにアクセスすることも可能である.
| 特徴 | 短所 | 長所 | |
|---|---|---|---|
| HTTPクライアント with ASP(7月号) |
Visual Basicアプリケーションから,Active Server Pagesの データベースアクセス機能を利用する |
仮想的なセッションしか継続できない | マルチユーザーがシングルプロセスで非同期で利用可能 |
| Type1(5,6月号) | データ転送にバリアント配列を使用 | 転送データが増えたときに極端に遅くなる | クライアントのプログラミングが容易 |
| Type2 | データ転送に文字列を使用 | 転送データが増えたときに遅くなる | サーバーのプログラミングが容易 |
| Type3 | データ転送に文字列を使用し,データを分割して転送 | ||
| Type4 | ActiveX.exeを利用し,マルチプロセス,マルチスレッドで動作 | マルチユーザー時にメモリの消費が激しい | マルチユーザーが非同期で利用可能 |
| パフォーマンスの向上を目指したType2,Type3 |
|---|
図1:最初にサーバーを起動し,データベースに接続
|
図2:クライアントを起動し,SQL文を実行
|
さて今月は,6月号で発表したType1 DBサーバーの発展形であるType2,Type3について,そしてType4についても軽く解説する.発展形とはいっても,Type2はType1のバリアント配列サポートをドロップしたものである.というのは,Type1で大量のデータを取得した場合生じる極端な性能劣化が,実用の範囲を超えていたからである.
サンプルを実行するには,Easy Socketコントロールがクライアントマシンにインストールされている必要がある.学習用途としては十分な機能を持つランタイム版を付録CD-ROMからインストールできる.
Type2を起動するには,最初にDBサーバーを起動し,ODBCのデータソース名(デフォルトはBIBLIO)を指定し,[データベース接続]ボタンをクリックする(図1).これでサーバーは,データベースに接続した状態でクライアントからの接続を待機する.
次にクライアントを起動する(図2).デフォルトではクライアントと同じマシンで起動しているデータベースサーバーに対して,起動時に接続する.サーバーがリモートにある場合,フォームのLoadイベントプロシージャに記述されている次のコードの「LocalHostName」にそのサーバーのマシン名を記述する.
KnSocket1.RemoteHostName = KnSocket1.LocalHostName
これで,SQL文を実行することができる.この実行のシーケンスを間違えると最初からやり直す必要がある.ポイントは次の2点である.
現時点では,まだSELECT文を使うことができるに過ぎない.まずはサンプルプログラムのコードの可読性を考えて,できる限り短く記述するためにそうしている.
Option Explicit
Private Declare Function timeGetTime Lib "WINMM" () As Long
Private mlngStartTime As Long
Private mlngEndTime As Long
Private en As rdoEnvironment
Private cn As rdoConnection
Private rs As rdoResultset
Private daodb As Database
Private Sub s_search(pszText)
mlngStartTime = timeGetTime() ' 計測を開始
' 検索を実行
Set rs = cn.OpenResultset(pszText, rdOpenKeyset, rdConcurReadOnly, _
rdExecDirect)
mlngEndTime = timeGetTime() ' タイマーを終了
lbl3 = (mlngEndTime - mlngStartTime) / 1000
End Sub
Private Sub cb_connect_Click()
Set en = rdoEnvironments(0)
Set cn = en.OpenConnection(dsName:=Text1, _
Prompt:=rdDriverCompleteRequired)
End Sub
Private Sub Form_Load()
KnSocket1.Open
cb_connect_Click
End Sub
Private Sub Form_Unload(Cancel As Integer)
On Error GoTo errLabel
cn.Close
Set cn = Nothing
On Error GoTo 0
Exit Sub
errLabel:
Debug.Print "Not Connection"
End Sub
Private Sub KnSocket1_ReceiveText(ByVal nIndex As Integer, _
ByVal pszText As String)
Dim RetResult As String
Dim i As Integer
Dim MaxfieldCount As Integer
Text2.Text = pszText
s_search pszText ' 検索を実行
MaxfieldCount = rs.rdoColumns.Count ' 項目数を取得
RetResult = ""
Do While Not rs.EOF
For i = 0 To MaxfieldCount - 1
RetResult = RetResult & rs(i) & ";"
Next
RetResult = RetResult & vbCrLf
rs.MoveNext
Loop
KnSocket1.Sockets(nIndex).SendText RetResult
rs.Close
Set rs = Nothing
End Sub
|
| 検索とデータ通信 |
|---|
動作のメカニズムは,6月号で解説したType1 DBサーバーと変わらない.クライアントが送信したSQL文を受信し,それをrdoConnectionオブジェクトのOpenResultsetメソッドの引数に指定して実行している.
ReceiveTextイベントプロシージャから呼び出されるs_searchサブプロシージャ内の以下のコードで実際の検索処理を行なっている.
Set rs = cn.OpenResultset (pszText, rdOpenKeyset, rdConcurReadOnly, rdExecDirect)
検索の結果は,rdoResultsetオブジェクトに格納される.その内容を取り出してクライアントに送り返しているのが,knSocket1のReceiveTextイベントプロシージャに記述された次のコードである.
MaxfieldCount = rs.rdoColumns.Count
RetResult = ""
Do While Not rs.EOF
For i = 0 To MaxfieldCount - 1
RetResult = RetResult & rs(i) & ";"
Next
RetResult = RetResult & vbCrLf
rs.MoveNext
Loop
KnSocket1.Sockets(nIndex).SendText RetResult
rdoResultsetオブジェクトに格納された内容を,文字列型変数であるRetResultに格納し,Easy SocketコントロールのSendTextメソッドでクライアントに送信している.
このようにデータベースに接続し,クライアントからの接続要求を受け入れたのち,ほとんどの処理はReceiveTextイベントをトリガにして行なわれる.ユーザーの要求に対してデータベースへのアクセスが行なうのがデータベースサーバーの仕事なのだから当然である.実際に動作させてみると,バリアント配列を使ったType1よりも,取得データが多い場合,はるかに高速に動作する.取得データ行数と処理時間を表にしたのが表2である.これなら実用の範囲だということができるだろう.
| 文字列操作がボトルネック |
|---|
表2:取得データ行数と処理時間の関係(Type2)
|
しかし,100行取得時の0.05秒という数値は悪くはないが,それ以上の値になると件数の比例以上に処理時間が必要となっている.調べてみると,これはサーバーがクライアントに送り返す文字列変数であるRetResultの操作に時間を要していることがわかった.
つまり,次のようなコードはループの回数が増加し,文字列が長くなるに従って1ループあたりの処理時間が長くなるのである.考えてみれば当たり前のことだが,これだけ露骨に遅くなるという認識はなかった.
For i = 0 To 1000
RetResult = RetResult & "文字列の連続"
Next
| 結果データを分割して送信するType3 |
|---|
取得するデータが1000件を超えるSELECT文を,クライアント側で実行するなどというのは,そもそも間違っていると思うが,検索結果のサイズ以上に時間がかかるというのは避けたい.そこで改良したのがType3 DBサーバーである.
Type3では結果行数が多い場合,200行ごとに分割してクライアントに返信するように改良してある.Type2とType3の処理時間を比較したのが表3である.結果データが多いときに性能が改善されていることがわかる.
| 取得したデータの行数 | 処理時間/Type2(秒) | 処理時間/Type3(秒) |
|---|---|---|
| 1 | 0.02 | 0.02 |
| 100 | 0.05 | 0.05 |
| 500 | 0.36 | 0.29 |
| 1000 | 1.52 | 0.60 |
| 2000 | 5.79 | 1.42 |
| 200行ごとにデータを送信 |
|---|
変更しているのは,Easy SocketコントロールのReceiveTextイベントプロシージャに記述された次の部分である.
Do While Not rs.EOF
RecCount = RecCount + 1
For i = 0 To MaxfieldCount - 1
RetResult = RetResult & rs(i) & ";"
Next
RetResult = RetResult & vbCrLf
rs.MoveNext
If RecCount = 200 Or rs.EOF Then
RecCount = 0
KnSocket1.Sockets(nIndex). SendText RetResult
RetResult = ""
If rs.EOF Then
Exit Do
End If
End If
Loop
Type2では,変数RetResultの送信をループの外で行なっていたが,このType3では200ループごとに行なっている.送信した後,変数の内容は再初期化されるため文字列はある程度上長くなることはない.これで,結果セットのサイズが大きくなっても極端にレスポンスが劣化することはなくなる.
| クエリーの並行処理への対応 |
|---|
これでパフォーマンスのめどは立ったわけだが,Type1,Type2,Type3のDBサーバーは,マルチユーザーの要求に対して並行して処理を実行できないという問題点がある.サーバー側に設定されているEasy Socketコントロールは,マルチセッションに対応しているため,複数の要求に対応することができる.しかし,マルチスレッドアプリケーションを作成できないVisual Basicでは,ひとつのプロセスで複数の処理が実行できないため,データベースへのアクセスはひとつのスレッドで行なわれる.これは,あるユーザーが時間のかかるクエリーを実行している間,別のユーザーの要求が待たされることを意味する.
先月号で紹介したActive Server Pagesをリクエストブローカーとして利用する方法は,Webサーバーがマルチスレッドアプリケーションとして動作することで,並行して処理を行なうことが可能であった.
では,Visual Basicで並行処理を可能にするには,どのようにすればいいのだろうか? そのひとつの方法がマルチスレッドに対応したActiveXサーバーを作成することである.Visual Basicはマルチスレッドアプリケーションやマルチスレッドコンテナを作成することはできないが,マルチスレッドに対応したActiveXサーバーを作成することができる.
また,もうひとつの方法はマルチプロセスとして実装する方法である.1台のマシン内で起動したプロセスは,各々別のメモリ空間を所有し,非同期で動作する.したがって接続ごとに別のプロセスを利用することで,非同期でデータベースにアクセスすることができる.
| マルチプロセスにより並行処理を実現するType4 |
|---|
Type4はこの方法を利用して,並行処理を実現している.また,ActiveXサーバーとして設計されているので,Instancingプロパティを変更することでマルチスレッド仕様への変更も可能である.
私がマルチプロセスにこだわるのは,Jetデータベースエンジンの元々の設計が,ひとつのプロセスを占有するように作られているからである.以前にも説明したようにAccessのODBCドライバとはJetデータベースエンジンそのものである.つまり,シングルプロセスのマルチユーザーでODBCを使用した場合,各接続に割り当てられたスレッドに対してひとつのJetデータベースがロードされるということになる.これは,たとえば10の接続がある場合,ひとつのプロセスに10のJetデータベースエンジンが読み込まれることを意味する.
直感的に,これがあまり安全な方法でないことは理解できるだろう.実際かなりの数のAccessのドライバをひとつのプロセスにロードすると,レスポンスが急激に劣化する現象が発生している.また,Internet Information Server 3.0のドキュメントには,Jetデータベースエンジンはシングルユーザー用に作られたデータベースエンジンであり,マルチユーザーでの使用時の動作は保証しないと記述されている.
しかし,これは間違いである.Jetデータベースエンジンはマルチユーザー用に設計されたデータベースエンジンであり,マルチユーザーでの使用に対応している.しかしそれは,ファイル共有型のデータベースエンジンとしてマルチユーザーに対応しているのであって,Active Server Pagesで使用する場合のようにスレッドごとにデータベースエンジンがロードされることを想定されているわけではない.
| 最新のODBCドライバでは障害が改善 |
|---|
ただ,多数のJetデータベースエンジンを1プロセスにロードするとパフォーマンスが劣化する現象だが,これはWindows NT 4.0 Option Packに付属する最新のODBCドライバ(Ver 3.51)を使用すると再現しない.つまり,この最新のドライバによって1プロセス,マルチスレッドに対応した可能性もあるのだ.時代の要求を考えれば当然の対応だということができるだろう.ゆえにどこまでマルチプロセスにこだわるかは,今後の調査次第だということになる.とはいえ,それでもマルチプロセスで作成しておいた方が安全であることには間違いがない.しかしマルチプロセスはメモリの消費が激しい.プロセスの起動時間は接続時に発生するだけなので支障はないにしても,接続中,プロセス空間を確保するコストをどう判断するかは,サーバーマシンの環境によるだろう.
| Type4のセットアップ |
|---|
図3:参照設定で[AKIZUKI DBsvr without Collection]をチェック
|
Type4についての説明は今月はこれくらいにしておこう.これ以上原稿が遅れると,フミッチーが怒り出す可能性がある.Type4のDBサーバーもCD-ROMに収録しているので,読者は動作を確認することができる.最後にサンプルの使用方法を解説しておこう.
Type4はサーバー側で2つのプロジェクトファイルを使用する.ひとつはActiveXサーバー(DBsvr.vbp)であり,もうひとつはActiveXコンテナ(DBsvrRB.vbp)である.
このサーバーを使用するには,まず,ActiveXサーバーをレジストリに登録する必要がある.そのためには,DBsvr.vbpをダブルクリックしてプロジェクトをオープンする.メニュー[ファイル]-[DBsvr.exeの作成]を選択し,実行モジュールを作成する.これで登録は完了である.
次にDBsvrRB.vbpをオープンする.メニュー[プロジェクト]-[参照設定]を選択し,図3のように[AKIZUKI DBsvr without Collection]をチェックする.これでアプリケーションを実行すると,サーバーはクライアントからの待機モードに入る(図4).
図4:Type4 DBサーバーを起動
|
次にType4のDBクライアントを起動する(図5).サーバーの名前を指定して[接続]ボタンをクリックすると,データベースにアクセスするたるのActiveXサーバー(図6)が自動的に起動する.これで接続が完了である.クライアントからSQL文を実行すると結果が取得できる.このデータベースサーバーとクライアントは並行処理をサポートするが,1台のマシンで複数のクライアントを起動することはできない.マルチユーザーで実験するときは,2台以上のマシンが必要になる.また,接続を行なう部分は並行処理に対応していないので接続は1人ずつする必要がある.
動作も速いし,クエリーの並行処理もできる,まあまあ実用的なデータベースサーバーである.今後は,これをシェイプアップする形で機能を追加していく.
図5:Type4 DBクライアントを起動![]() |
図6:自動的に起動されるDBアクセスサーバー![]() |