SQL Azure Team Blog

I Miss You SQL Server Agent: Part 2 – SQL Azure Team Blog – Site Home – MSDN Blogsを簡単に翻訳したエントリーです。

今のところ、SQL AzureはSQL Server Agentの概念を持っていません。このシリーズでは、Windows Azureワーカーロールを使用して、簡単に代用できるものを作成したいと思います。このシリーズの最初の投稿で、Visual Studioとコードで、Windows AzureワーカーロールでSQL Server Agentと同等のものを作成する方法を紹介しました。
このエントリーでは、一日に一回ジョブを実行するメカニズムを作成します。

 

データベースの作成

Windows Azureは、ワーカーロールは、そのうちデータセンターで異なるサーバに移動する可能性のあるステートレスなプラットフォームです。このため、ジョブが完了するまでジョブの状態を記録し続ける必要があります。その為に迷うことなくSQL Azureを選択します。SQL Azureサーバ下に、SQLServerAgentと呼ばれるデータベースを作成します(データベース名msdbは予め予約されています)。このデータベースに、オンプレミスのSQL Server Agentテーブルsysjobactivityの簡単バージョンであるjobactivityと呼ばれるテーブルを作成します。
私が使用したスクリプトは以下のものです。

CREATE TABLE [dbo].[jobactivity](
    [job_id] uniqueidentifier NOT NULL PRIMARY KEY,
    [job_name] nvarchar(100) NOT NULL,
    [start_execution_date] datetime NOT NULL,
    [stop_execution_date] datetime NULL,
) 

job_idはオブジェクトの日次インスタンスを表し、job_nameはジョブ実行の為の任意のキーです。異なる名前を使用することで多くのジョブを走らせる為に、このテーブルを使用します。

 

ジョブの開始と停止の追跡

 

ジョブ開始時にテーブルにデータを追加する為のストアドプロシージャと、ジョブ停止時に実行ストップを更新する為のストアドプロシージャの2つが必要です。Startjobストアドプロシージャは、ワーカーロールがジョブを開始する為のシグナルを行に登録する前に、ジョブが始まらないことを保証します。

CREATE PROCEDURE StartJob (
    @job_name varchar(100),
    @job_id uniqueidentifier OUTPUT)
AS

BEGIN TRANSACTION

SELECT    @job_id
FROM    [jobactivity]
WHERE    DATEDIFF(d, [start_execution_date], GetDate()) = 0 
    AND [job_name] = @job_name

IF (@@ROWCOUNT=0)
BEGIN
    -- Has Not Been Started
    SET @job_id = NewId()
    INSERT INTO [jobactivity] 
        ([job_id],[job_name],[start_execution_date])
        VALUES (@job_id, @job_name, GetDate())
END
ELSE
BEGIN 
    SET @job_id = NULL
END

COMMIT TRAN

もう一つのストアドプロシージャStopJobは、次のようになります。

CREATE PROCEDURE [dbo].[StopJob](
    @job_id uniqueidentifier)
    
AS

UPDATE [jobactivity]
SET [stop_execution_date] = GetDate()
WHERE job_id = @job_id

ストアドプロシージャを呼び出すワーカーロールを書きます。

protected Guid? StartJob(String jobName)
{
    using (SqlConnection sqlConnection = new SqlConnection(
        ConfigurationManager.ConnectionStrings["SQLServerAgent"].
            ConnectionString))
    {
        try
        {
            // Open the connection
            sqlConnection.Open();

            SqlCommand sqlCommand = new SqlCommand(
                "StartJob", sqlConnection);

            sqlCommand.CommandType =
                System.Data.CommandType.StoredProcedure;

            sqlCommand.Parameters.AddWithValue("@job_name", jobName);

            // WWB: Sql Job Id Output Parameter
            SqlParameter jobIdSqlParameter = new 
                SqlParameter("@job_id", SqlDbType.UniqueIdentifier);
            jobIdSqlParameter.Direction = ParameterDirection.Output;
            sqlCommand.Parameters.Add(jobIdSqlParameter);

            sqlCommand.ExecuteNonQuery();

            if (jobIdSqlParameter.Value == DBNull.Value)
                return (null);
            else
                return ((Guid)jobIdSqlParameter.Value);
        }
        catch (SqlException)
        {
            // WWB: SQL Exceptions Means It Is Not Started
            return (null);
        }
    }
}

protected void StopJob(Guid jobId)
{
    using (SqlConnection sqlConnection = new SqlConnection(
        ConfigurationManager.ConnectionStrings["SQLServerAgent"].
            ConnectionString))
    {
        // Open the connection
        sqlConnection.Open();

        SqlCommand sqlCommand = new SqlCommand(
            "StopJob", sqlConnection);

        sqlCommand.CommandType =
            System.Data.CommandType.StoredProcedure;

        sqlCommand.Parameters.AddWithValue("@job_id", jobId);

        sqlCommand.ExecuteNonQuery();
    }
}

1日1回、午後1時にspTestJobストアドプロシージャを実行するために、ワーカーロールのRunメソッドを実行します。

public override void Run()
{
    Trace.WriteLine("WorkerRole1 entry point called", "Information");

    while (true)
    {
        DateTime nextExecutionTime = new DateTime(
            DateTime. UtcNow.Year, 
            DateTime. UtcNow.Month, DateTime. UtcNow.Day,
            13, 0, 0);
        if (DateTime. UtcNow > nextExecutionTime)
        {
            // WWB: After 1:00 pm, Try to Get a Job Id.
            Guid? jobId = StartJob("TestJob");
            if (jobId.HasValue)
            {
                Trace.WriteLine("Working", "Information");

                // WWB: This Method Has the Code That Execute
                // A Stored Procedure, The Actual Job
                ExecuteTestJob();

                StopJob(jobId.Value);
            }

            // WWB: Sleep For An Hour
            // This Reduces The Calls To StartJob
            Thread.Sleep(3600000);
        }
        else
        {
            // WWB: Check Every Minute
            Thread.Sleep(60000);
        }
    }
}

上のコードにエラーハンドリングのコードがないことに気づいたでしょうか。エラーが発生したらどうなるでしょうか。SQL Azureがエラーを返したらどうなるでしょうか。データセンターで異なるサーバでワーカーロールがリサイクルしたらどうなるでしょうか。それらの問題への対処については、このシリーズの3回目でコードを追加して対処したいと思います。

SQL Azure Team Blog

I Miss You SQL Server Agent: Part 1 – SQL Azure Team Blog – Site Home – MSDN Blogsを簡単に翻訳したエントリーです。

今のところ、SQL Azureはクラウド上でのSQL Server Agentの動作をサポートしていません。SQL Azureで、SQL Server Agentの機能が必要な場合は、Windows Azureワーカーロールとカスタムコードで代用できます。このシリーズで、代用する方法を紹介します。

 

SQL Server Agent

SQL Server Agentは、ジョブと呼ばれる管理タスクをスケジュール実行するためのMicrosoft Windows サービスです。SQL Server Agentは、SQL Serverに格納されたジョブ情報を使用します。ジョブは、一つ以上のジョブステップを含んでいます。各ステップは、データベースバックアップのようにSQL Server自信のタスクも含んでいます。SQL Server Agentは、ジョブをスケジュール実行、特定のイベント結果を受けての実行、呼び出されての実行に対応しています。例えば、平日業務後、全ての会社のサーバをバックアップしたい場合、SQL Server Agentを使用すれば自動実行させることができます。月曜から金曜までの22時以降起動するバックアップをスケジュールします。もしバックアップ実行に問題が発生した場合、SQL Server Agentはイベントを記録し、あなたに通知します。SQL Server AgentはオンプレミスのEnterprise、Datacenter、StandardエディションのSQL Serverにインストールされますが、ExpressとCompactエディションには提供されていません。

 

ワーカーロール

ワーカーロールはクラウド上の基本的なWindowsサービスです。ワーカーロールを開始させたとき、エンドレスループをするようにデザインされています。

  • シングルプロセスイベントを確認する
  • 動作を実行する
  • 次に確認する必要ができるまでスリープする様々な目的で使用できるように、ワーカーロールは意図的に抽象的になっています。

注意事項:ワーカーロールは意図的に抽象化されており、SQL Server Agentは開発されており、一年程度で製品提供されると思うので、ワーカーロールで完璧にSQL Server Agentの機能を複製するつもりはありません。代わりに、Windows AzureワーカーロールからSQL Server Agentが行うような多くのタスクを実現できるように、いくつかのアイディアとシンプルなコードを提供しようと思っています。

 

初めに

初めに、Visual Studioでワーカーロールを含むクラウドプロジェクトを作成します。これに関して、基本的な事を知りたい場合は、ここを参照してください。

vs01

WorkerRole.vbと呼ばれるファイルにいくつかのサンプルコードが生成されます。

public override Sub Run()
    ' これは WorkerRole1 の実装例です。実際のロジックに置き換えてください。
    Trace.WriteLine("WorkerRole1 entry point called", "Information");

    While (True)
        Thread.Sleep(10000);
        Trace.WriteLine("Working", "Information");
    End While
End Sub

最初にすることは、SQL Azureデータベース上でストアドプロシージャを実行させるコードを書くことです。これは、SQL Server Agentジョブ内のステップでTransact-SQLを実行させるのと同等です。

vs02

次のようなコードになります。

    Protected Sub ExecuteTestJob()

        Using sqlConnection As SqlConnection = New SqlConnection(ConfigurationManager.ConnectionStrings("AdventureWorksLTAZ2008R2").ConnectionString)

            Try

                '接続を開く

                sqlConnection.Open()



                Dim sqlCommand As SqlCommand = New SqlCommand("spTest", sqlConnection)

                sqlCommand.CommandType = System.Data.CommandType.StoredProcedure



                sqlCommand.ExecuteNonQuery()



            Catch ex As SqlException

                Trace.WriteLine("SqlException", "Error")

                Throw

            End Try

        End Using

    End Sub

基本的にはSQL Azure上で、SqlCommand.ExecuteNonQuery()メソッドを使用して、データを返さないストアドプロシージャを実行します。SQL Server Agentのように何も結果を返しません。

 

例外のハンドリング

 

上の例では、Windows AzureワーカーロールでSQL Server Agent版を作成するための最初のステップなので、例外のハンドリングがとても貧相になっています。

SQL Server Agentのロジックから、成功したら次のステップへ進み、失敗したらジョブを終了し、失敗を報告するものを使用する場合、ワーカーロールでtry/catchを使用してSQL Server Agentの動作をシミュレーションします。このシリーズの3回目でエラーハンドリングの問題について取り組みます。

 

まとめ


このシリーズの2回目では、一日の間で、特定の時間にストアドプロシージャを実行させる方法について紹介します。

SQL Azure Team Blog

Finding Your Clustered Indexes – SQL Azure Team Blog – Site Home – MSDN Blogsを簡単に翻訳したエントリーです。

SQL Server から SQL Azureにマイグレーションする前に全てのテーブルにクラスターインデックスを作成する必要があります。その理由については、このエントリーを参照してください。このエントリーでは、クラスターインデックスが無いテーブルを見つける方法を紹介します。

SQL Serverデータベースから更新しマイグレーションするテクニックの一つは、スクリプ生成ウィザードを使用して、SQL Azureに同じスキーマを転送することです。それに関しては、このエントリーで書きました。このテクニックを使用する場合は、SQL Serverで次のスクリプトを実行して、クラスターインデックスのないテーブルを探します。

SELECT DISTINCT [TABLE] = OBJECT_NAME(OBJECT_ID)
FROM SYS.INDEXES
WHERE INDEX_ID = 0 AND OBJECTPROPERTY(OBJECT_ID,'IsUserTable') = 1
ORDER BY [TABLE]

クラスターインデックスの無いテーブルリストを取得できます。クラスターインデックスを簡単に作成する方法は、非クラスタインデックスをクラスタインデックスに変更することです。SQL Server Management Studioで実行することができます。

1.オブジェクトエクスプローラーで、テーブルのツリーを開き、テーブルのインデックスを拡げます。

2. クラスターインデックスに変更したい、非クラスタインデックスを選択します。

3. 右クリックをし、「インデックスをスクリプト化」の「Drop toおよびCreate To」を選択します。

vs07

4. SQL Serverはインデックスを削除し、再作成するTransact-SQLを生成します。

5. CREATE INDEX文からNONCLUSTEREDを探し、CLUSTERDに変更します。

6. スクリプトを実行します。

 

次のクエリは、SQL Server Management Studioによって生成されたスクリプト例です。

/****** Object:  Index [PK_GroupFeature]  
  Script Date: 06/01/2010 17:00:47 ******/
IF  EXISTS (SELECT * FROM sys.indexes 
    WHERE object_id = OBJECT_ID(N'[dbo].[GroupFeature]') 
    AND name = N'PK_GroupFeature')
ALTER TABLE [dbo].[GroupFeature]
 DROP CONSTRAINT [PK_GroupFeature]
GO

USE [Kulshan]
GO

/****** Object:  Index [PK_GroupFeature] 
   Script Date: 06/01/2010 17:00:48 ******/
ALTER TABLE [dbo].[GroupFeature] ADD
  CONSTRAINT [PK_GroupFeature] 
  PRIMARY KEY CLUSTERED 
(
    [GroupFeature_Id] ASC
)WITH (PAD_INDEX  = OFF, 
    STATISTICS_NORECOMPUTE  = OFF,
    SORT_IN_TEMPDB = OFF,
    IGNORE_DUP_KEY = OFF, ONLINE = OFF, 
    ALLOW_ROW_LOCKS  = ON,
    ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO

SQL Server から SQL Azureにマイグレーションしたいテーブル全てにクラスタインデックスを作成してしまえば、SQL Azureでの開発に一歩近づきます。

SQL Azure Team Blog

SELECT * AND SQL Azure – SQL Azure Team Blog – Site Home – MSDN Blogsを簡単に翻訳したエントリーです。

全ての良いDBAと開発者は、特別必要が無ければ全てのレスを返すクエリを発行すべきで無いことを知っています。

SELECT * FROM [Table]

SELECT文では、必要な列のみを返すようにします。SELECT *を使用すると余計なページングの原因となります。RFIDルックアップ、不必要なテーブルロック、カバードインデックスの作成意図の無効化等が発生します。結果としてパフォーマンスに悪影響が出ます。

さらに、SQL Azureには SELECT *を使用すべきでない理由があります。データ転送料に応じた課金を行うからです。もし、(Windows Azureアプリケーションではない)データセンターの外から使用する場合、クエリによって発生するデータ転送量に応じて料金を支払います。もし使用しない列も要求すると、使用していないデータの転送量にまで支払うことになります。

SELECT *クエリを削除することをあなたのTO-DOリストにあるのなら、SQL Azureですばらしい体験ができるでしょう。

 

(コメント欄より抜粋)

Entity FrameworkのようなORMでも、SELECT *は生成させるべきで無い。しかし、「Oraders.Where」や「.OrderBy」などを使用しても、特別にSELECT句「.select」を指定しない場合、SELECT *が生成されてしまいます。