Azure SQL Database Elastic Scale をやっと触ってみる

サンプルはこの手順通りで動きました。
Azure SQL Database Elastic Scale の概要

10/30(木) 三木会で SQL Database のお話しをさせていただきました - お だ のスペース
で上がってた質問に回答するためコードを少しいじってみました。

サンプルの 4:Execute sample Multi-Shard Query のクエリを変えてうごかします。

internal static class MultiShardQuerySample
{
  public static void ExecuteMultiShardQuery(RangeShardMap<int> shardMap, string credentialsConnectionString)
  {
    // Get the shards to connect to
    IEnumerable<Shard> shards = shardMap.GetShards();
    // Create the multi-shard connection
    using (MultiShardConnection conn = new MultiShardConnection(shards, credentialsConnectionString))
    {
      // Create a simple command
      using (MultiShardCommand cmd = conn.CreateCommand())
      {
        // Because this query is grouped by CustomerID, which is sharded,
        // we will not get duplicate rows.
/*
        cmd.CommandText = @"
          SELECT 
            c.CustomerId, 
            c.Name AS CustomerName, 
            COUNT(o.OrderID) AS OrderCount
          FROM 
            dbo.Customers AS c INNER JOIN 
            dbo.Orders AS o
            ON c.CustomerID = o.CustomerID
          GROUP BY 
            c.CustomerId, 
            c.Name
          ORDER BY 
            OrderCount";
*/
        cmd.CommandText = "select count(*) from Customers";
...

実行結果はこんな感じ

Enter an option [1-6] and press ENTER: 4
     $ShardName
     ----------
 12  ElasticScaleStarterKit_Shard1
 6   ElasticScaleStarterKit_Shard0
 14  ElasticScaleStarterKit_Shard2

(3 rows returned)

基本 Multi Shared Query は基本それぞれの DB に対してクエリ投げてそれを一元的に受け取れるだけのようです。

公式のドキュメントがあったので貼っときます。
Azure SQL Database Elastic Scale *1
Multi-Shared Queries(MSQ) に、「各シャードに同じクエリ投げて UNION ALL 風味で返します」ってな感じの事が書かれています。

クエリの制約は特に無さそう(SQL Database が対応していれば)ですが、Multi Shared Query の場合は、集計関数/ソートを使う時にプログラム側でも考慮が必要ですね。

で、ここからが本題。
シャードの管理とかデータの分割とかややこしい事は置いといて、SQL Database 以外でも使えるのかというのを試してみました。
今回の構成は

  • Shared Map Manager(ElasticScaleStarterKit_ShardMapManagerDb)
    localhost\MSSQL2014
  • Shared0(ElasticScaleStarterKit_Shard0)
    localhost\MSSQL2012
  • Shared1(ElasticScaleStarterKit_Shard1)
    localhost\MSSQL2014Express
  • Shared2(ElasticScaleStarterKit_Shard2)
    localhost\MSSQL2012Express

という4台構成にしました。

このサンプル良く出来ていて殆ど変更を加えることなく実現出来ます。
変更する箇所は、 App.config の IntegratedSecurity を true にします。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <connectionStrings>
  </connectionStrings>
  <appSettings>
    <!-- 
      The server name of the database to connect to. Do not include the https:// prefix of the Azure SQL DB server URL.
      Typically, the server name looks like this: abcdefghij.database.windows.net
      
      Edit this field before running the application.
    -->
    <add key="ServerName" value="localhost\MSSQL2014" />
    <!-- 
      Credentials for connecting to your Azure SQL DB server.
      
      Edit these fields before running the application.
    -->
    <add key="UserName" value="" />
    <add key="Password" value="" />
    
    <!--
      The database edition to use when creating databases for this sample in Azure SQL DB.
    -->
    <add key="DatabaseEdition" value="Basic" />
    <!--
      Set to true to enable Windows Authentication instead of SQL Authentication.
      This is useful only if connecting to a local SQL Server database instead of Azure SQL DB.
    -->
    <add key="IntegratedSecurity" value="true" />
  </appSettings>
  <system.diagnostics>
    <trace>
      <listeners>
        <add type="System.Diagnostics.TextWriterTraceListener" name="TextWriter" initializeData="trace.log" />
      </listeners>
    </trace>
  </system.diagnostics>
</configuration>

CreateShardSample.cs に GetServerName を追加、CreateOrGetEmptyShard を変更します。

private static readonly string[] SERVER_NAMES = new String[] { 
  @"localhost\MSSQL2012"
  , @"localhost\MSSQL2014Express"
  , @"localhost\MSSQL2012Express" };
private static string GetServerName(RangeShardMap<int> shardMap) {
  var cnt = shardMap.GetShards().Count();
  return SERVER_NAMES[cnt % 3];
}
/// <summary>
/// Creates a new shard, or gets an existing empty shard (i.e. a shard that has no mappings).
/// The reason why an empty shard might exist is that it was created and initialized but we 
/// failed to create a mapping to it.
/// </summary>
private static Shard CreateOrGetEmptyShard(RangeShardMap<int> shardMap)
{
  // Get an empty shard if one already exists, otherwise create a new one
  Shard shard = FindEmptyShard(shardMap);
  if (shard == null)
  {
    // No empty shard exists, so create one
    // Choose the shard name
    string databaseName = string.Format(ShardNameFormat, shardMap.GetShards().Count());
    string serverName = GetServerName(shardMap);
    // Only create the database if it doesn't already exist. It might already exist if
    // we tried to create it previously but hit a transient fault.
    if (!SqlDatabaseUtils.DatabaseExists(serverName, databaseName))
    {
      SqlDatabaseUtils.CreateDatabase(serverName, databaseName);
    }
    // Create schema and populate reference data on that database
    // The initialize script must be idempotent, in case it was already run on this database
    // and we failed to add it to the shard map previously
    SqlDatabaseUtils.ExecuteSqlScript(
      serverName, databaseName, InitializeShardScriptFile);
    // Add it to the shard map
    ShardLocation shardLocation = new ShardLocation(serverName, databaseName);
    shard = ShardManagementUtils.CreateOrGetShard(shardMap, shardLocation);
  }
  return shard;
}

ここでシャード毎の接続サーバーを切り替えます。
これは、シャードを作成する際に走るとこで、Shard Map Manager の [__ShardManagement].[ShardsGlobal] テーブルにサーバー情報が入ります。

最後にサーバー名とDB名を確認するために MultiSharedQuerySample.cs の ExtractDatabaseName を変更します。

private static string ExtractDatabaseName(string shardLocationString)
{
  return shardLocationString;
}

でビルド実行し以下の手順で動かします。

  1. Create shard map manager, and add a couple shareds
  2. Add another shard
  3. Insert sample rows using Data-Dependent Routing
    何回か 3 を繰り返してデータ作成
  4. Execute sample Multi-Shard Query

これの結果が、

Enter an option [1-6] and press ENTER: 4
 CustomerId  CustomerName           OrderCount  $ShardName
 ----------  ------------           ----------  ----------
 101         Northwind Traders      1           [DataSource=localhost\MSSQL2014E
xpress Database=ElasticScaleStarterKit_Shard1]
 114         Lucerne Publishing     1           [DataSource=localhost\MSSQL2014E
xpress Database=ElasticScaleStarterKit_Shard1]
 127         Contoso Ltd.           1           [DataSource=localhost\MSSQL2014E
xpress Database=ElasticScaleStarterKit_Shard1]
 130         Humongous Insurance    1           [DataSource=localhost\MSSQL2014E
xpress Database=ElasticScaleStarterKit_Shard1]
 152         Lucerne Publishing     1           [DataSource=localhost\MSSQL2014E
xpress Database=ElasticScaleStarterKit_Shard1]
 159         Coho Winery            1           [DataSource=localhost\MSSQL2014E
xpress Database=ElasticScaleStarterKit_Shard1]
 167         Coho Winery            1           [DataSource=localhost\MSSQL2014E
xpress Database=ElasticScaleStarterKit_Shard1]
 187         Humongous Insurance    1           [DataSource=localhost\MSSQL2014E
xpress Database=ElasticScaleStarterKit_Shard1]
 197         ProseWare, Inc.        1           [DataSource=localhost\MSSQL2014E
xpress Database=ElasticScaleStarterKit_Shard1]
 165         ProseWare, Inc.        2           [DataSource=localhost\MSSQL2014E
xpress Database=ElasticScaleStarterKit_Shard1]
 147         Contoso Ltd.           2           [DataSource=localhost\MSSQL2014E
xpress Database=ElasticScaleStarterKit_Shard1]
 129         ProseWare, Inc.        2           [DataSource=localhost\MSSQL2014E
xpress Database=ElasticScaleStarterKit_Shard1]
 47          ProseWare, Inc.        1           [DataSource=localhost\MSSQL2012
Database=ElasticScaleStarterKit_Shard0]
 48          Alpine Ski House       1           [DataSource=localhost\MSSQL2012
Database=ElasticScaleStarterKit_Shard0]
 61          Microsoft Corp.        1           [DataSource=localhost\MSSQL2012
Database=ElasticScaleStarterKit_Shard0]
 79          AdventureWorks Cycles  1           [DataSource=localhost\MSSQL2012
Database=ElasticScaleStarterKit_Shard0]
 15          AdventureWorks Cycles  1           [DataSource=localhost\MSSQL2012
Database=ElasticScaleStarterKit_Shard0]
 40          Alpine Ski House       2           [DataSource=localhost\MSSQL2012
Database=ElasticScaleStarterKit_Shard0]
 200         Lucerne Publishing     1           [DataSource=localhost\MSSQL2012E
xpress Database=ElasticScaleStarterKit_Shard2]
 214         AdventureWorks Cycles  1           [DataSource=localhost\MSSQL2012E
xpress Database=ElasticScaleStarterKit_Shard2]
 224         Lucerne Publishing     1           [DataSource=localhost\MSSQL2012E
xpress Database=ElasticScaleStarterKit_Shard2]
 226         Alpine Ski House       1           [DataSource=localhost\MSSQL2012E
xpress Database=ElasticScaleStarterKit_Shard2]
 227         Microsoft Corp.        1           [DataSource=localhost\MSSQL2012E
xpress Database=ElasticScaleStarterKit_Shard2]
 229         Microsoft Corp.        1           [DataSource=localhost\MSSQL2012E
xpress Database=ElasticScaleStarterKit_Shard2]
 232         ProseWare, Inc.        1           [DataSource=localhost\MSSQL2012E
xpress Database=ElasticScaleStarterKit_Shard2]
 247         Northwind Traders      1           [DataSource=localhost\MSSQL2012E
xpress Database=ElasticScaleStarterKit_Shard2]
 264         Microsoft Corp.        1           [DataSource=localhost\MSSQL2012E
xpress Database=ElasticScaleStarterKit_Shard2]
 267         Lucerne Publishing     1           [DataSource=localhost\MSSQL2012E
xpress Database=ElasticScaleStarterKit_Shard2]
 276         Northwind Traders      1           [DataSource=localhost\MSSQL2012E
xpress Database=ElasticScaleStarterKit_Shard2]
 278         ProseWare, Inc.        1           [DataSource=localhost\MSSQL2012E
xpress Database=ElasticScaleStarterKit_Shard2]
 285         Contoso Ltd.           1           [DataSource=localhost\MSSQL2012E
xpress Database=ElasticScaleStarterKit_Shard2]
 274         Coho Winery            2           [DataSource=localhost\MSSQL2012E
xpress Database=ElasticScaleStarterKit_Shard2]

(32 rows returned)
Current Shard Map state:
        ElasticScaleStarterKit_Shard0 contains key range [0:100)
        ElasticScaleStarterKit_Shard1 contains key range [100:200)
        ElasticScaleStarterKit_Shard2 contains key range [200:300)

とこんな感じになりました。

SqlDatabaseUtils.cs で使ってる Connection や Command を Oracle とかに差し替えたら Oracle でもいけそうな気がします。

*1:ja-jp にすると日本語になりますが、訳が微妙な気もする