Chapter 8. Designing and tuning for scalability and high performance - Microsoft Azure SQL Database Step by Step (2014)

Microsoft Azure SQL Database Step by Step

Chapter 8. Designing and tuning for scalability and high performance

Eric Boyd

When developing applications and systems intended for real use, performance is an important consideration. Today’s users have short attention spans and need immediate results, which means their applications must be responsive and deliver results quickly. Data lies at the core of many systems, and often that data is stored in relational databases like SQL Database. Optimizing data access can often provide significant improvements and benefits to application performance.

In this chapter, you will optimize and tune database performance for Microsoft Azure SQL Database. This includes optimizing execution speed and performance, as well as reliability. To demonstrate these concepts throughout this chapter, a reference application is needed. In the second section, “Creating a RESTful Web API,” you will create an ASP.NET Web API that works with data from SQL Database using both ADO.NET and Entity Framework (EF). Then you will improve the reliability and performance of this Web API by managing database connections and connection errors, reducing latency and considering other optimizations like using the most appropriate storage service for your data. Later in the chapter, you learn how to scale up SQL Database using SQL Database Premium. The last sections of the chapter guide you through scaling your SQL Database out with a partitioning strategy known as sharding.

Entity Framework is a Microsoft data access framework that simplifies mapping database objects to .NET objects. EF uses ADO.NET internally for data access, but the additional object relational mapping that EF performs adds performance overhead. When optimizing data access performance, one common technique is to reduce the number of abstractions between your application code and databases. Using lower-level data access technologies like ADO.NET directly often improves data access performance. In addition to performance optimization, many existing applications use ADO.NET for data access. Therefore, both ADO.NET and EF are discussed in this chapter.

Image Note

This chapter uses EF and Web API to demonstrate performance concepts. Chapter 10, “Building cloud solutions,” delves into much more detail on both of these important technologies.


Achieving high performance in the cloud

Virtually infinite amounts of hardware and computing resources is one of the major value propositions for moving to the cloud. While the quantity of computing resources owned and managed by a cloud vendor is ultimately finite, at peak loads, most organizations would demand only a small fraction of the available resources. However, most applications, especially typical enterprise applications, are not designed in a way that can make use of this large pool of hardware. Most applications are not designed to scale horizontally (often called scaling out)—that is, to chunk up and distribute the load across many servers and storage nodes. Instead, most applications depend on having control of the hardware and scaling vertically (often called scaling up)—that is, to increase the capacity and performance of centralized computing resources by purchasing larger and more powerful servers and storage devices.

Cloud vendors such as Microsoft provide some ability to scale up. At the time of this writing, Microsoft Azure compute instances range in size from a shared 1-GHz CPU with 768 MB of RAM to a 16-by-2.6 GHz CPU instance with 112 GB of RAM. The Microsoft Azure Platform as a Service (PaaS) services, such as SQL Database, also have some ability to scale up. For example, a single SQL Database can range in size from a 100-MB database to 150 GB, and with the Preview availability of SQL Database Premium, the computing resources for a single SQL Database server can be scaled. (You will learn more about SQL Database Premium in the section “Scaling up SQL Database.”)

Microsoft Azure provides some capability to scale computing resources up; unfortunately, there will always be a physical limit and upper bound to the amount of computing capacity you can get from a single resource, whether that’s a server, storage device, or service. To scale big, you must scale out. And to scale out, your applications must be architected in a way that allows them to be distributed across multiple instances of computing hardware.

In addition to scalability, because you don’t control the hardware configuration that SQL Database is running on, you can’t scale the server hardware up with the same control as you could in your own data center, and because SQL Database is a multitenant service with multiple customers sharing the same physical compute resources, the performance characteristics will quite likely be different than that of your own private data center. As a result, you might have to tune database performance differently than you would in your own data center.

Creating a RESTful web API

To get started, you’ll create the WineCloudDb database with a Wine table and a Customer table. You will then create a new ASP.NET Web Application project. The ASP.NET Web Application will contain ASP.NET Web APIs using ADO.NET for the Customer Web API and Entity Framework for the Wine Web API. Throughout the chapter, you will build upon this project as you learn how to design and optimize for performance, reliability, and scalability.

Creating the sample database

For the Web APIs in this chapter, you will use the script shown in Listing 8-1 to create WineCloudDb and populate it with a few wines and customers. Then you will create ASP.NET Web APIs that return wines and customers.


Image Note

In other chapters, the WineCloudDb database included Wine, Customer, and Order tables. This chapter does not make use of the Order table, which is why it is omitted here.


LISTING 8-1 Script to create the sample WineCloudDb database for the Web APIs


CREATE TABLE Wine (
WineId int PRIMARY KEY,
Name nvarchar (50),
Category nvarchar (15),
Year int,
Price money)

CREATE TABLE Customer (
CustomerId int PRIMARY KEY,
FirstName nvarchar(50),
LastName nvarchar(15),
FavoriteWineId int,
CONSTRAINT FK_Customer_Wine FOREIGN KEY (FavoriteWineId) REFERENCES Wine (WineId))

INSERT INTO Wine VALUES
(1, 'Chateau Penin', 'Bordeaux', 2008, 34.90),
(2, 'McLaren Valley', 'Cabernet', 2005, 48.50),
(3, 'Mendoza', 'Merlot', 2010, 42.00),
(4, 'Valle Central', 'Merlot', 2009, 52.00)

INSERT INTO Customer VALUES
(1, 'Jeff', 'Hay', 4),
(2, 'Mark', 'Hanson', 3),
(3, 'Jeff', 'Phillips', 2)


This script creates the WineCloudDb tables and loads some sample data into them. Once you create this sample data, you will build Web APIs that return the wine and customer data.

To create the WineCloudDb database, follow these steps:

1. From the Windows Start screen, launch SQL Server Management Studio (SSMS). You can either scroll through the app tiles to find it (in the Microsoft SQL Server 2012 category) or just type sql server management studio to run a search, and then click on the tile. After a brief moment, the Connect To Server dialog appears.

2. In the Connect To Server dialog, do the following:

a. For Server Name, type <servername>.database.windows.net. This is the fully qualified name to the SQL Database server, where <servername> should be replaced by the name assigned to your server.

b. For Authentication, select SQL Server Authentication from the drop-down list. (SQL Database does not support Windows Authentication.)

c. For Login and Password, type the user name and password you assigned the server when you created it.

d. Click the Connect button.

3. In the Object Explorer, expand the Databases node.

4. If the WineCloudDb database exists from a previous chapter, delete it now by doing the following:

a. Right-click the WineCloudDb database, and choose Delete.

b. In the Delete Object dialog, click OK.

5. Create the database by completing the following steps:

a. In the Object Explorer, right-click the server name and choose New Query to open a new query window connected to the master database.

b. In the query window, type CREATE DATABASE WineCloudDb.

c. Press F5 (or click the Execute button in the toolbar) to create the database.

d. Close the query window without saving the script.

6. In the Object Explorer, right-click the Databases node and choose Refresh. The WineCloudDb database you just created should now appear.

7. Right-click the WineCloudDb database, and choose New Query to open a new query window connected to the WineCloudDb database.

8. Type the code shown in Listing 8-1 into the query window (or paste it in from the listing file downloaded from the book’s companion website).

9. Press F5 (or click the Execute button in the toolbar) to create the database schema and populate some data.

You now have a new WineCloudDb database that will serve as the data source for your Web APIs.


Image Note

You needed to create the database and populate it in two separate query windows because SQL Database does not support the USE statement found in SQL Server for switching the connection from the master database to the WineCloudDb database. See Chapter 3, “Differences between SQL Server and Microsoft Azure SQL Database,” for more information on differences between SQL Database and SQL Server.


Creating a new solution

Visual Studio projects are contained inside a Microsoft Visual Studio solution, so you’ll start by creating an empty solution. Then you’ll add an ASP.NET Web Application project. The ASP.NET Web Application project will contain your ASP.NET Web APIs. This solution will contain only one project, but Visual Studio requires the project to be in a solution, and it will implicitly create one if it doesn’t already exist. So you’ll be proactive and create one from the beginning.

To create the new solution, follow these steps:

1. Start Visual Studio 2013.

2. Click the FILE menu, and then choose New | Project to display the New Project dialog.

3. On the left of the New Project dialog, expand Templates, Other Project Types, and choose Visual Studio Solutions.

4. Select the Blank Solution template, name the solution WineSolution, and choose any desired location for the solution, as shown in Figure 8-1.

Image

FIGURE 8-1 The New Project dialog for creating the Blank Solution

5. Click OK to create the solution.

The Solution Explorer now shows the new WineSolution. (If the Solution Explorer is not visible, click the VIEW menu and choose Solution Explorer.) Now that you have an empty solution, you’re ready to create a new ASP.NET Web API project.

Creating an ASP.NET Web API project

When creating new ASP.NET projects using Visual Studio, it is recommended that you use the ASP.NET Web Application project type. This type of project can contain Model-View-Controller (MVC), Web Forms, and Web API project items. In the following procedures, you will create ASP.NET Web APIs in an ASP.NET Web Application project.

To create a new ASP.NET Web API project, follow these steps:

1. Right-click WineSolution in Solution Explorer, and choose Add | New Project to display the Add New Project dialog.

2. On the left of the New Project dialog, expand Installed, expand Visual C#, and choose Web.

3. Choose the ASP.NET Web Application template, which is typically selected by default.

4. Name the project WineCloudWebApi and click OK, as shown in Figure 8-2, to display the New ASP.NET Project dialog.

Image

FIGURE 8-2 Creating a new ASP.NET Web Application project

5. Choose the Web API template and click OK, as shown in Figure 8-3.

Image

FIGURE 8-3 Creating a new ASP.NET Web API project

You have now created an empty ASP.NET Web Application project with references to the ASP.NET Web API assemblies. The project also references the ASP.NET MVC assemblies, which makes it possible to run both Web API and MVC applications in the same ASP.NET Web Application project.

Adding an Entity Framework Code First Web API controller

Now that you have created an ASP.NET Web Application project for your WineCloudDb Web APIs, you are ready to add Web API controllers. Web API controllers are similar to MVC controllers in that they respond to HTTP requests with HTTP responses. The first Web API Controller you’ll add will use Entity Framework Code First for data access to the WineCloudDb database.

In earlier versions of Entity Framework, the entity model and database mappings could be configured only in an EDMX file, typically using the graphical Entity Data Model (EDM) designer in Visual Studio. With the Code First feature added in EF 4, you can choose instead to create your own Plain Old CLR Object (POCO) classes and configure the database conventions and mapping in code. This approach provides a number of advantages over using EDMX files. One of the most significant advantages is loose coupling of models from the persistence framework. This makes it easier to test your entity models in isolation from your database, add additional properties and methods, and use them across multiple application layers.

Creating a POCO class for use with Entity Framework is quite simple, and there are just a few rules you must follow. First, the class needs to be public. Second, the properties need to be public. And last, the data types of the model need to match the columns in your table. Entity Framework Code First is convention based, which means a number of patterns are built into Entity Framework. If these patterns are followed in your entity model and database design, Entity Framework can automatically map your entity model to the database without any manual configuration. The entity model for the Wine table, as shown in Listing 8-2, contains properties that match the columns in the Wine table, but there are no dependencies on Entity Framework. Entity Framework knows the WineId property maps to the primary key of the Wine table, because the naming follows the convention of class name followed by “Id” and the data type of the property is numeric or a GUID. Listing 8-3 contains the context class you will customize to override EF’s default pluralization naming strategy.


Image More Info

You can learn more about the Entity Framework conventions at http://msdn.microsoft.com/en-us/data/jj679962.aspx.


By default, EF will create a database and tables using the Entity Framework conventions and the POCO entity model classes. However, this database initialization strategy can be easily disabled so that you get to enjoy a combination of both database-first and code-first experiences with EF. That is, you don’t need to let EF “reverse-engineer” a database from your code if you want to use code first; you can still create the database on your own and use code first at the same time. It’s easy to tell Entity Framework not to create the database and tables by callingDatabase.SetInitializer<WineDbContext>(null) at the end of the Application_Start method in the Global.asax.cs.

One of the default Entity Framework Code First conventions is to pluralize table names. This means that, by default, Entity Framework expects that Wine entities are stored in a table named Wines (plural). However, the WineCloudDb database has a Wine table (singular), not a Winestable. Again, it’s easy to override this default behavior by overriding the EF context’s OnModelCreating method and calling modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(). Removing this default convention will match the entity class name to a table name, which will map the Wine entity POCO class to the same-named Wine table in the database.

LISTING 8-2 The Wine.cs model class


using System;

namespace WineCloudWebApi.Models
{
public class Wine
{
public int WineId { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public int? Year { get; set; }
public Decimal? Price { get; set; }
}
}


LISTING 8-3 The WineDbContext.cs Entity Framework context class


using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace WineCloudWebApi.Models
{
public class WineDbContext : DbContext
{
public WineDbContext() : base("name=WineDbContext") {}

public DbSet<Wine> Wines { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}


To create a Web API using Entity Framework for the Wine table, follow these steps:

1. Right-click the Models folder beneath the WineCloudWebApi project in the Solution Explorer, and choose Add | Class to display the Add New Item dialog.

2. Name the class Wine.cs and click Add, as shown in Figure 8-4.

Image

FIGURE 8-4 Adding a Wine.cs model class

3. Replace the template code generated automatically by Visual Studio with the code shown in Listing 8-2.

4. Build the WineSolution by selecting Build | Build Solution in the menu at the top or pressing Ctrl+Shift+B.


Image Note

The next step in this procedure is to add a new Web API controller using the Wine model class you just created. Visual Studio finds the model classes using the built assemblies within the solution. This requires you to build the solution now so that Visual Studio can find the model class in the next step.


5. Right-click the Controllers folder in the WineCloudWebApi project in the Solution Explorer, and choose Add | Controller to display the Add Scaffold dialog.

6. Choose the Web API 2 Controller With Actions, Using Entity Framework controller, and click Add, as shown in Figure 8-5. This scaffold automatically creates Web API methods in the new controller class that retrieve (GET) and update (PUT, POST, DELETE) entities.

Image

FIGURE 8-5 Adding the Entity Framework Web API 2 controller

7. In the Add Controller dialog, supply the information for the new Wine controller by doing the following:

a. For the Controller name, type WineController.

b. Leave the Use Async Controller Actions check box unselected.

c. For the Model class, select Wine (WineCloudWebApi.Models) from the drop-down list.

d. For the Data context class, click the New Data Context button and type WineCloudWebApi.Models.WineDbContext in the New Data Context Type text box and click Add, as shown in Figure 8-6.

Image

FIGURE 8-6 Creating the Entity Framework data context

e. The Add Controller dialog should now appear as shown in Figure 8-7. Click Add to create the controller.

Image

FIGURE 8-7 Adding and configuring the WineController class with Entity Framework

8. Double-click the Web.config file beneath the WineCloudWebApi project in the Solution Explorer, and locate the WineDbContext connection string setting in the connectionStrings section.

9. Change the WineDbContext connection string to Server=tcp:<servername>.database.windows.net,1433; Database=WineCloudDb; User ID=<username>@<servername>; Password=<password>; Trusted_Connection=False; Encrypt=True; Connection Timeout=30;.

a. Replace <servername> with the name of the SQL Database server that contains the WineCloudDb database.

b. Replace <username> and <password> with the user name and password you assigned the server when you created it.

10. Double-click the WineDbContext.cs file beneath the Models folder in the Solution Explorer and replace the template code with the code in Listing 8-3. The code adds an override of the OnModelCreating method that removes the default pluralizing table convention. This tells EF to look for a table named Wine (singular) and not Wines (plural) for storing rows of Wine entities in the database.

11. Double-click the Global.asax.cs file beneath the WineCloudWebApi project in the Solution Explorer.

12. Make the following changes to remove the default database initialization strategy, and tell EF not to attempt to create the database (because the database already exists):

a. Add the following two using statements at the top of the file: using System.Data.Entity; using WineCloudWebApi.Models;

b. Add the following line of code at the end of the Application_Start method: Database.SetInitializer<WineDbContext>(null);

13. Build the WineSolution by selecting Build | Build Solution in the menu at the top or pressing Ctrl+Shift+B.

You have now created a RESTful Web API for the Wine table using ASP.NET Web API and Entity Framework, and you’re ready to test it.


Image Note

You can learn more about Entity Framework and ASP.NET Web API in Chapter 10. In Chapter 10, you will learn how to build a multitier web and mobile application in Microsoft Azure using SQL Database.


Testing the Wine Web API

Now that you have created the Wine Web API, you should test it to verify that it returns the correct data from the Wine table in the WineCloudDb.

To test the Wine Web API, follow these steps:

1. Select the WineSolution in the Solution Explorer, and press F5 or click Debug | Start Debugging. This opens Internet Explorer (or your default debugging web browser) at the default page of the WineCloudWebApi project, as shown in Figure 8-8.

Image

FIGURE 8-8 The default page from the WineCloudWebApi project

2. Append the URL in the browser’s address bar with api/Wine, and press Enter. This executes the GetWines method on the WineController and responds with the list of wines from the WineCloudDb database. Internet Explorer’s default behavior asks if you would like to save or open the results from the Web API call, as shown in Figure 8-9.

Image

FIGURE 8-9 The Wine.json file returned by the Wine API


Image Note

ASP.NET Web API implements a feature called content negotiation. The HTTP specification (RFC 2616) defines content negotiation as “the process of selecting the best representation for a given response when there are multiple representations available.” The most common way to handle content negotiation is with HTTP Accept request headers. Browsers have different default Accept headers. If you make a GET request to the Wine API in multiple browsers, you are likely to get a different response. In Internet Explorer, you will often get a JSON-formatted response; in Chrome, you will often get an XML-formatted response.


3. Click Open to view the list of wines returned in the JSON results. (If prompted for how to open this type of file, click More Options and choose Notepad.)

4. Append the URL in the browser’s address bar with /2, and press Enter. This executes the GetWine method on the WineController and responds with the Wine record for WineId 2.

5. Click Open to view the results.

You have now created a Wine Web API using ASP.NET Web API and Entity Framework Code First. You have also tested it in the browser and verified that it returns results from the WineCloudDb database. Next you will add a Web API Controller for the Customer table using raw ADO.NET rather than Entity Framework. It is common to have enterprise applications that use ADO.NET because they were developed prior to the introduction of Entity Framework. Raw ADO.NET is also commonly used when trying to boost data-access performance. As a result, in the following section you will use ADO.NET to build a Web API controller for the Customer table.

Adding an ADO.NET Web API controller

Thus far, you created a Visual Studio solution and added an ASP.NET Web Application project to that solution. You then created an ASP.NET Web API controller for the Wine table using Entity Framework Code First. Now, you’ll create an ASP.NET Web API controller for the Customertable using raw ADO.NET. Listing 8-4 shows the POCO model class for the Customer entity, and Listing 8-5 shows its corresponding Web API controller class.

LISTING 8-4 The Customer.cs model class


namespace WineCloudWebApi.Models
{
public class Customer
{
public int CustomerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int? FavoriteWineId { get; set; }
}
}


LISTING 8-5 The CustomerController.cs Web API controller class


using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.Web.Http;
using System.Web.Http.Description;
using WineCloudWebApi.Models;

namespace WineCloudWebApi.Controllers
{
public class CustomerController : ApiController
{
// GET api/Customer
public IList<Customer> GetCustomers()
{
IList<Customer> customers = new List<Customer>();
var connectionString =
ConfigurationManager.ConnectionStrings["WineDbContext"].ConnectionString;

using (var connection = new SqlConnection(connectionString))
{
var commandText = "SELECT * FROM Customer";
using (var command = new SqlCommand(commandText, connection))
{
connection.Open();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
var customer = new Customer
{
CustomerId = Convert.ToInt32(reader["CustomerId"]),
FirstName = reader["FirstName"].ToString(),
LastName = reader["LastName"].ToString(),
FavoriteWineId = reader["FavoriteWineId"] as int?
};
customers.Add(customer);
}
}
}
}
return customers;
}

// GET api/Customer/5
[ResponseType(typeof(Customer))]
public IHttpActionResult GetCustomer(int id)
{
Customer customer = null;
var connectionString =
ConfigurationManager.ConnectionStrings["WineDbContext"].ConnectionString;

using (var connection = new SqlConnection(connectionString))
{
var commandText = "SELECT * FROM Customer WHERE CustomerId = @CustomerId";
using (var command = new SqlCommand(commandText, connection))
{
command.Parameters.AddWithValue("@CustomerId", id);
connection.Open();
using (var reader = command.ExecuteReader())
{
if (reader.Read())
{
customer = new Customer
{
CustomerId = Convert.ToInt32(reader["CustomerId"]),
FirstName = reader["FirstName"].ToString(),
LastName = reader["LastName"].ToString(),
FavoriteWineId = reader["FavoriteWineId"] as int?
};
}
}
}
}

if (customer == null)
{
return NotFound();
}
return Ok(customer);
}
}
}


To create a Customer Web API using ADO.NET, follow these steps:

1. Right-click the Models folder in the WineCloudWebApi project in the Solution Explorer, and choose Add | Class to display the Add New Item dialog.

2. Name the class Customer.cs, and click Add.

3. Replace the template code generated automatically by Visual Studio with the code shown in Listing 8-4 earlier, and build the WineSolution by selecting Build | Build Solution in the menu at the top or pressing Ctrl+Shift+B.

4. Right-click the Controllers folder in the WineCloudWebApi project in the Solution Explorer, and choose Add | Controller to display the Add Scaffold dialog.

5. Choose the Web API 2 Controller - Empty controller, and click Add. This displays the Add Controller dialog.

6. Type CustomerController for Controller name, and click Add.

7. Replace the template code with the code shown in Listing 8-5 earlier, and build the WineSolution by selecting Build | Build Solution in the menu at the top or pressing Ctrl+Shift+B.

You have now added a new ASP.NET Web API controller for the Customer table using ADO.NET instead of Entity Framework. The Customer Web API provides methods to get customer data, but it doesn’t provide methods for adding or updating customers. Similar to the Wine entity model, the Customer entity model, as shown in Listing 8-4, doesn’t have any data-access dependencies and is a simple POCO class. The Customer controller, as shown in Listing 8-5, queries the Customer table using the ADO.NET SqlConnection, SqlCommand, and SqlDataReader classes. It creates and populates instances of the Customer entity model class and returns the Customer objects. ASP.NET Web API then serializes the Customer objects into the appropriate response formats and sends them back to the requestor.

Testing the Customer Web API

Now that you have created a Customer Web API, you should test it to verify that it returns the correct data from the Customer table in the WineCloudDb database:

1. Select the WineSolution in the Solution Explorer, and press F5 or click Debug | Start Debugging. This opens Internet Explorer or your default debugging web browser at the default page of the WineCloudWebApi project.

2. Append the URL in the browser’s address bar with api/Customer, and press Enter. This executes the GetCustomers method on the WineController and responds with the list of Customers from the WineCloudDb database. Internet Explorer’s default behavior asks if you would like to save or open the results from the Web API call.

3. Click Open to view the results.

4. Append the URL in the browser’s address bar with /3, and press Enter. This executes the GetCustomer method on the CustomerController and responds with the Customer record for CustomerId 3.

5. Click Open to view the results.

You have now set up an ASP.NET Web API project to get data from the Wine and Customer tables in the WineCloudDb database. Your project has Web APIs that use both Entity Framework Code First and raw ADO.NET. With this project now in place, we will build upon it in the following sections of this chapter to demonstrate performance tuning and scalability with SQL Database.

Managing SQL Database connections

Database connections in Microsoft Azure SQL Database can have different characteristics and behaviors than you’ve likely experienced with SQL Server on-premises. To provide reliability and great user experiences, these behaviors must be accounted for in your applications that use SQL Database. In addition to these different connection behaviors, you should also implement general best practices for interacting with databases, regardless of whether they are on-premises or in the cloud.

Opening late, closing early

There are limits on most compute resources, both in the cloud and on-premises. In SQL Database, one aspect that is limited is the number of database connections you can have open at any given time. To be a well-behaved consumer of SQL Database resources and connections, you should open database connections only when you need to interact with a SQL Database and as late as possible. When you are finished interacting with the database, you should close and release the connection. Entity Framework abstracts away the database connections, and you expect Entity Framework to be a good consumer of database resources. But when you are explicitly opening database connections using raw ADO.NET, you should open the database connections as close to the database interactions as possible. In Listing 8-5, you can see the call to connection.Open()occurs immediately before the SqlCommand is executed. Because the connection is wrapped in a using statement, as soon as the body of the using statement is exited, the connection will be disposed of, which closes the connection and releases its resources.

Pooling connections

There is overhead and a performance penalty when establishing new database connections. To help minimize this performance penalty, ADO.NET can pool connections. Connection pooling works by keeping a client’s connections open in a pool of managed connections. When the client needs to open a connection, ADO.NET checks for an existing connection in the pool with a connection string that matches. If a connection already exists, it returns that connection to the client to interact with the database. If a connection doesn’t exist, a new connection is established. When the client is finished with the connection and closes the connection, instead of destroying the connection, it is returned to the pool. Only connections with the exact same configuration and connection strings can be pooled, and the connection pool is scoped to an AppDomain. By default, ADO.NET uses connection pooling, and it is a generally recommended best practice to dramatically reduce the cost of opening connections.

Recovering from connection faults

SQL Database connections can be unexpectedly terminated for many reasons. The network architecture of SQL Database includes firewalls, load balancers, gateways, and database nodes. There are many components between the client and the database. Errors in these components can cause connections to terminate unexpectedly. SQL Database might also terminate connections when failing over to another node in the three-node clusters. In addition to errors and failover, SQL Database analyzes the activity of connections and the resource-consumption metrics of the database node. If SQL Database determines that it needs to throttle a connection to keep the overall service healthy, and to avoid negatively affecting other tenants of SQL Database, it may throttle and terminate the connection. These types of connection terminations are referred to as transient faults. Transient faults are temporary issues or errors that are expected to recover quickly. If your application is aware of these transient faults, it can try to reconnect over a short period of time and the connection loss might not even be noticeable to the user. If not, the application will throw an exception and the user will likely be presented with an error.

Not all terminated connections are caused by transient errors. However, errors raised by SQL Database return an error number you can use to determine what type of error occurred. Your application can compare the error number to the known transient error numbers. If there’s a match, your application can try to reconnect and retry the transaction; if there’s not a match, the error is not temporary and will need to be handled differently. If that sounds like a lot of work, don’t worry—the Microsoft Azure Customer Advisory Team, in conjunction with Microsoft Patterns & Practices, has simplified this by creating the Transient Fault Handling Application Block. This library is part of the Enterprise Library family of application blocks, and it can be integrated into your application to simplify the recovery during transient faults. It can be used with SQL Database and other Microsoft Azure services that might have transient errors, including Service Bus and Storage.

In the following sections, you will integrate the Transient Fault Handling Application Block into the WineCloudWebApi project. Specifically, you’ll modify the Wine and Customer Web APIs to recover gracefully from transient fault conditions.

Adding the Transient Fault Handling Application Block

To get started with the Transient Fault Handling Application Block, you must add a reference to it and integrate it into your project. In the following steps, you will add a reference to the Transient Fault Handling Application Block using NuGet. If you’ve not used NuGet before, there are a few basic facts we should tell you. It is a package manager that simplifies adding references to your projects with all of their associated dependencies. NuGet also makes it easy to update your dependencies and libraries when new versions are released. Documentation about NuGet and a repository of the publicly available NuGet packages are available at www.nuget.org.

Follow these steps to add a reference to the Transient Fault Handling Application Block:

1. Right-click the WineCloudWebApi project in the Solution Explorer, and choose Manage NuGet Packages to display the Manage NuGet Packages dialog.

2. Choose Online on the left, type transient fault sql database in the search box in the upper right corner, and press Enter.

3. Select Enterprise Library - Transient Fault Handling Application Block - Microsoft Azure SQL Database Integration from the search results, and click Install as shown in Figure 8-10.

Image

FIGURE 8-10 Installing the Transient Fault Handling Application Block using the Manage NuGet Packages dialog

4. In the License Acceptance dialog, click I Accept as shown in Figure 8-11. This downloads and adds a reference to Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling.Data.dll and its dependencies.

Image

FIGURE 8-11 Accepting the license for the Transient Fault Handling Application Block

5. When installation completes, click Close.

You have now added a reference to the Transient Fault Handling Application Block using NuGet and are now ready to get started integrating it into the WineCloudWebApi project. In the next two sections, you will modify the Wine and Customer Web APIs to recover from transient errors.

Using the Transient Fault Handling Application Block with ADO.NET

The Transient Fault Handling Application Block provides components and classes you can use to configure how transient faults are detected and handled in your application. Table 8-1 shows the major components of the Transient Fault Handling Application Block.

Image

TABLE 8-1 Major components of the Transient Fault Handling Application Block

Detection strategies are used by the Transient Fault Handling Application Block to determine if an error is transient and whether the failed service call should be retried. Detection strategies are classes that implement the ITransientErrorDetectionStrategy interface, and the Transient Fault Handling Application Block has implementations for SQL Database, Service Bus, Storage, and Caching. The procedures in this chapter use the SqlDatabaseTransientErrorDetectionStrategy class to detect SQL Database transient error conditions, but you can implement your own detection strategy using the ITransientErrorDetectionStrategy interface.

Retry strategies are used to define the frequency and number of times the Transient Fault Handling Application Block will retry and attempt to automatically recover from a transient error. Retry strategies are classes that derive from the RetryStrategy class. Table 8-2 shows the retry strategies that are implemented in the Transient Fault Handling Application Block.

Image

TABLE 8-2 Retry strategies in the Transient Fault Handling Application Block

Using a detection strategy and a retry strategy, you create a retry policy using the RetryPolicy class. The retry policy will inspect errors that occur using the detection strategy and execute the retry strategy if an error is identified as a transient error. The Transient Fault Handling Application Block provides multiple ways for you to integrate it into your applications. You can use new classes that encapsulate existing classes to make them transient-fault and retry aware. ReliableSqlConnection is a class that encapsulates SqlConnection. You can use it in place ofSqlConnection to automatically handle retrying when transient faults occur. There are also extension methods for existing classes, including the SqlConnection and SqlCommand classes. These extension methods can be used to utilize retry policies, as shown in the following procedures.

To handle transient faults in the GetCustomers method, follow these steps:

1. Expand the Controllers folder beneath the WineCloudWebApi project in Solution Explorer.

2. Double-click CustomerController.cs, and add the following using statement to the list of using statements at the top: using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling;

3. Locate the GetCustomers method, and type the following two lines of code just below the assignment of the connectionString variable:

var retryStrategy = new Incremental(5, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2)); var retryPolicy = new RetryPolicy<SqlDatabaseTransientErrorDetectionStrategy>(retryStrategy);

4. Replace connection.Open(); with connection.OpenWithRetry(retryPolicy);

5. Replace command.ExecuteReader() with command.ExecuteReaderWithRetry(retryPolicy)

To handle transient faults in the GetCustomer method, follow these steps:

1. Locate the GetCustomer method, and type the following two lines of code just below the assignment of the connectionString variable:

var retryStrategy = new Incremental(5, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2)); var retryPolicy = new RetryPolicy<SqlDatabaseTransientErrorDetectionStrategy>(retryStrategy);

2. Replace connection.Open(); with connection.OpenWithRetry(retryPolicy);

3. Replace command.ExecuteReader() with command.ExecuteReaderWithRetry(retryPolicy)

You are now handling transient fault conditions that might occur when using SQL Database using the Incremental retry strategy in the Customer Web API. The Incremental retry strategy works with three parameters.

The first parameter defines the number of times to retry. In the preceding procedure, you configured the Incremental retry strategy to retry five times. The second and third parameters together define the amount of time between each retry attempt. The second parameter defines the time to wait before the first retry, and the third parameter defines the amount of time to add to each subsequent retry attempt. In the preceding procedure, you set the second parameter to one second and the third parameter to two seconds. This will retry after one second, three seconds, five seconds, seven seconds, and nine seconds.

By defining a retry policy using the Transient Fault Handling Application Block, and by using extension methods on the SqlConnection and SqlCommand classes provided by the Transient Fault Handling Application Block, the Customer Web API is now resilient to transient error conditions, and minimal code changes were needed. In the next section, you will handle transient error conditions with Entity Framework.

Using the Transient Fault Handling Application Block with Entity Framework

In the previous section, you implemented retry logic when transient error conditions occurred using the Transient Fault Handling Application Block. You did that using extension methods on the SqlConnection and SqlCommand classes provided by the Transient Fault Handling Application Block. For the Customer Web API, that solution works really well because the data-access code is raw ADO.NET that uses these classes. The Wine Web API uses Entity Framework for data access, and the ADO.NET SqlConnection and SqlCommand classes are not exposed. Therefore, handling transient error conditions using Entity Framework requires a slightly different solution.

In the following procedure, you will use the same RetryPolicy class you used earlier with the extension methods on the SqlConnection and SqlCommand classes. The RetryPolicy class has an ExecuteAction method that is designed to execute code that encapsulates ADO.NET. This is useful when working with object relational mapping (ORM) and data-access platforms such as Entity Framework. Listing 8-6 shows a modified WineController class with Entity Framework calls that result in queries to SQL Database surrounded by the ExecuteAction method. The manual changes you need to apply to the code generated automatically by the scaffolding is indicated in bold type. Any exceptions that bubble up to the RetryPolicy will be inspected for transient error conditions and retried appropriately.

LISTING 8-6 The WineController.cs class using the Transient Fault Handling Application Block to handle transient error conditions


using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Description;
using WineCloudWebApi.Models;

namespace WineCloudWebApi.Controllers
{
public class WineController : ApiController
{
private WineDbContext db = new WineDbContext();
private RetryPolicy<SqlDatabaseTransientErrorDetectionStrategy> _retryPolicy;

public WineController()
{
var retryStrategy =
new Incremental(5, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2));
_retryPolicy =
new RetryPolicy<SqlDatabaseTransientErrorDetectionStrategy>(retryStrategy);
}


// GET api/Wine
public IQueryable<Wine> GetWines()
{
IQueryable<Wine> wines = null;
_retryPolicy.ExecuteAction(() =>
{
wines = db.Wines;
});

return wines;
}

// GET api/Wine/5
[ResponseType(typeof(Wine))]
public IHttpActionResult GetWine(int id)
{
Wine wine = null;
_retryPolicy.ExecuteAction(() =>
{
wine = db.Wines.Find(id);
});


if (wine == null)
{
return NotFound();
}
return Ok(wine);
}

// PUT api/Wine/5
public IHttpActionResult PutWine(int id, Wine wine)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

if (id != wine.WineId)
{
return BadRequest();
}

db.Entry(wine).State = EntityState.Modified;
try
{
_retryPolicy.ExecuteAction(() =>
{
db.SaveChanges();
});

}
catch (DbUpdateConcurrencyException)
{
if (!WineExists(id))
{
return NotFound();
}
else
{
throw;
}
}

return StatusCode(HttpStatusCode.NoContent);
}

// POST api/Wine
[ResponseType(typeof(Wine))]
public IHttpActionResult PostWine(Wine wine)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

db.Wines.Add(wine);
_retryPolicy.ExecuteAction(() =>
{
db.SaveChanges();
});


return CreatedAtRoute("DefaultApi", new { id = wine.WineId }, wine);
}

// DELETE api/Wine/5
[ResponseType(typeof(Wine))]
public IHttpActionResult DeleteWine(int id)
{
Wine wine = null;
_retryPolicy.ExecuteAction(() =>
{
wine = db.Wines.Find(id);
});


if (wine == null)
{
return NotFound();
}

db.Wines.Remove(wine);
_retryPolicy.ExecuteAction(() =>
{
db.SaveChanges();
});


return Ok(wine);
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}

private bool WineExists(int id)
{
bool wineExists = false;
_retryPolicy.ExecuteAction(() =>
{
wineExists = db.Wines.Count(e => e.WineId == id) > 0;
});


return wineExists;
}
}
}


To handle transient faults in the Wine controller class, follow these steps:

1. Expand the Controllers folder beneath the WineCloudWebApi project in Solution Explorer.

2. Double-click the WineController.cs file.

3. Update the code as indicated by the bolded sections of Listing 8-6. Specifically:

a. Add a using statement for Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling.

b. Add a private field to store the retry policy.

c. Add a constructor that creates the retry policy.

d. Wrap each line of code that invokes a query or calls SaveChanges inside the retry policy’s ExecuteAction method.

You have now handled transient error conditions using both ADO.NET and Entity Framework. The Transient Fault Handling Application Block is a huge help in doing this in a simple, quick, and succinct way. Without it, you would have to write the code to identify when an error is transient and temporary by comparing error numbers from SQL exceptions to a list of known transient errors. You would then have to write the code to retry a specified number of times at specified intervals, before implementing all of it in your application’s data-access code. Using the Transient Fault Handling Application Block instead will greatly accelerate your development.

Transient error conditions are not something you should ignore. If your application does not account for these conditions, your application could experience dropped connections and throttling, resulting in errors and bad experiences for your users, when it could be avoided by retrying and continuing on. This is a common area of frustration and source of reliability issues for new users of SQL Database, and it can be avoided at the outset by integrating the Transient Fault Handling Application Block into your applications from the start.

Reducing network latency

In addition to differences in connection management, as discussed in the previous section, latency can also be a big performance and reliability challenge for SQL Database users. Latency can be defined as the time delay between cause and effect. With SQL Database, the time it takes for a query to get from a client to SQL Database, and to get the computed results from SQL Database back to the client, is known as latency. In on-premises data centers, a common way to reduce latency between web servers and database servers is to directly connect these servers using high-speed gigabit and fiber optic network connections. However, in the public cloud, you don’t have control over the infrastructure and cannot make those hardware and network tweaks. But you can still optimize your services and applications to minimize latency.

Keeping services close

One way to reduce network latency is to minimize the distance between services that communicate with each other. In Microsoft Azure, you choose which data center regions you provision and deploy your services in. To reduce latency, it is best to deploy your SQL Database servers and the applications that interact with your SQL Database servers in the same data center region. You may not be able to keep the physical distance short in all cases. If you have applications running in your on-premises data center that need to interact with SQL Database, you have a physical distance between the services that you cannot reduce. In these cases, if latency becomes an issue, you might need to consider techniques like caching and batching to reduce the latency.

Minimizing round trips

Whenever there is a distance between services and the potential for latency, even in your own on-premises data center, you want to reduce the “chattiness” and network round trips in your applications.

These are some things you can do to reduce network round trips to SQL Database:

Image Encapsulate complex data access in stored procedures if the data access results in multiple round trips and queries to SQL Database. For example, if a query depends on results from a previous query, instead of making multiple rounds trips to the database from the client, combining those in a single stored procedure will reduce the network latency.

Image Batch using table-valued parameters by passing in a table-valued parameter to a stored procedure instead of making multiple calls with single parameters to the same stored procedure.

Image Use client-side storage and caching to reduce network traffic when retrieving lookup data and data that changes infrequently. In addition to client-side storage, you can also use distributed caching services like Microsoft Azure Cache to keep data in memory and share the cached data across multiple nodes.

Image Avoid retrieving metadata at runtime to reduce roundtrips. This includes using classes like SqlCommandBuilder that query metadata at runtime.

Effectively using SQL Database

You’ll often find nonrelational and binary data stored in relational databases like SQL Server. This is common in enterprise applications, where developers view SQL Server as a managed data-storage platform for all data. Storing everything in SQL Server simplifies application development because developers need to understand only a single platform and learn a single set of client libraries. In addition to development experience, another reason SQL Server is often used for all kinds of data is because it is commonly architected to be always highly available, and that makes it easy to build a system with multiple distributed clients or multiple load-balanced web servers connected back to the single SQL Server platform. However, to achieve this level of availability, significant infrastructure and hardware investments are made, and much time is invested into the architecture and design of the SQL Server installation.

When you consider performance, scalability, and cost, relational databases are usually not the best places to store unstructured, nonrelational, and binary data. (The FILESTREAM feature in SQL Server represents an exception to this rule of thumb, but FILESTREAM, unfortunately, is not supported in SQL Database.) One of the ways you can increase the performance of SQL Database is to use it to store and serve relational data and only relational data, and use one of the alternative storage services to manage unstructured or semistructured nonrelational data.

Using the best storage service

There are multiple data services available in Microsoft Azure that are better suited for managing nonrelational and binary data than SQL Database. Microsoft Azure Storage has three storage services for other types of data: Table, Blob, and Queue. These services are all intended for nonrelational data. This type of data is not the best fit for relational databases like SQL Database.

Table storage works well for large quantities of structured, nonrelational data. Table storage is a schema-less, entity store where you can store an object with multiple key-value pairs. Logging and telemetry data is typically nonrelational and not analyzed in same structure as it was stored in initially. With this in mind, this kind of data is a good fit for Table storage.

Blob storage is a massively scalable file server service in Microsoft Azure. It is intended to store binary files, including documents and media. If you are storing binary large objects (BLOBs) in a database using the varbinary(max) data type (again, FILESTREAM is not supported), Blob storage is a good alternative solution for storing that data. Using Blob storage will reduce the load on SQL Database and will also reduce the amount of data stored in SQL Database. In addition to having performance benefits, Blob storage is significantly cheaper than SQL Database.

Queue storage is a service designed for queuing messages for asynchronous processing. This is often done using a database table with status columns. You can reduce load and contention on SQL Database by using Queue storage for these scenarios.

When data is accessed frequently and changed infrequently, that data is a great candidate for caching. Microsoft Azure provides a Cache service that can be deployed to your own compute instances in Microsoft Azure, or you can consume a managed Cache service. In simple cases, storing and accessing data from the local file system might meet your needs.

Optimizing queries

Many performance-tuning techniques and principles for SQL Database are the same as SQL Server in your own data center. In SQL Database, queries must be optimized and tuned just like in SQL Server on-premises. Query tuning is a very big topic and is outside the scope of this book; however, there is already a lot of good content written on this topic in other books. The basic principles of analyzing execution plans, optimizing queries to reduce disk I/O and wait time, and tuning indexes are the same as in SQL Server. However, identifying slow queries and queries that are good candidates for optimization is a little different than it is in SQL Server. SQL Database does not contain SQL Server functionality that requires elevated permissions or has the potential to impact the performance and reliability of other tenants; therefore, SQL Profiler cannot be used with SQL Database. Knowing how to identify queries that are slow and can be improved in SQL Database is very valuable and is discussed in Chapter 9, “Monitoring and managing SQL Database.”

Scaling up SQL Database

SQL Database is a shared, multitenant, relational database service that can have variable performance characteristics. At the time of this writing, a new service named SQL Database Premium is in Preview. You can use it to attain consistent and predictable performance by reserving compute resources exclusively for your database. SQL Database Premium has the two service levels shown in Table 8-3.

Image

TABLE 8-3 Service levels of SQL Database Premium


Image Note

Microsoft regularly releases new features for Microsoft Azure. These features often begin as Preview services, which don’t come with service level agreements (SLAs) or warranties, and they are typically not supported by Microsoft customer support. While in Preview, services often have reduced pricing (and sometimes are free). When a service progresses from Preview to General Availability (GA), the service gets service level agreements, warranties, support, and full pricing. For more information about Microsoft Azure preview services, visithttp://azure.microsoft.com/en-us/services/preview/.


To sign up for the SQL Database Premium Preview, follow these steps:

1. Log in to the Microsoft Azure Account management portal at https://account.windowsazure.com.

2. Click the Preview Features button as shown in Figure 8-12 to display the Microsoft Azure preview features.

Image

FIGURE 8-12 The Preview Features link in the Microsoft Azure account portal

3. Click the Try It Now button to the right of Premium For SQL Database, as shown in Figure 8-13, to activate the SQL Database Premium preview.

Image

FIGURE 8-13 Activating the SQL Database Premium preview feature in the Microsoft Azure account portal

4. Choose the subscription you want to use with SQL Database Premium in the Add Preview Feature dialog, as shown in Figure 8-14, and click the check mark in the bottom right corner.

Image

FIGURE 8-14 Selecting the subscription in the Add Preview Feature dialog

You have now requested to be signed up for the SQL Database Premium preview. Requests are queued and approved based on current capacity and demand. You will receive an email informing you when your request has been approved and SQL Database Premium preview has been activated in your subscription. Once the preview has been activated, you can then request a SQL Database Premium quota for your SQL Database servers.

To request a SQL Database Premium quota, follow these steps:

1. Log in to the Microsoft Azure portal at https://manage.windowsazure.com. This brings you to the main portal page showing ALL ITEMS.

2. Click SQL DATABASES in the vertical navigation pane on the left.

3. Click the SERVERS link at the top of the page. This displays a list of your Microsoft Azure SQL Database servers.

4. In the NAME column, click the server that you want to request a Premium database quota for.

5. Navigate to the server home screen by clicking on the cloud with the lightning bolt icon.

6. Click Request Premium Database Quota in the Premium Database section as shown in Figure 8-15.

Image

FIGURE 8-15 Requesting a SQL Database Premium quota in the Microsoft Azure management portal

You have now requested to have a SQL Database Premium quota added to your SQL Database server. Requests are queued and approved based on current capacity and demand. The status of a Premium Database Quota request is displayed in the Premium Database section of the server home screen where you initiated the request. After you initiate the request and prior to it being approved, a Pending Approval message will be displayed, as shown in Figure 8-16. You can cancel the request by clicking the Cancel Request link. When your request is approved and you have a SQL Database Premium quota on your server, you will also receive an email notification. Once you have a SQL Database Premium quota, you can create a new database or associate an existing database with your SQL Database Premium quota.

Image

FIGURE 8-16 Finding the status of a SQL Database Premium quota request in the Microsoft Azure management portal

To upgrade an existing database to SQL Database Premium, follow these steps:

1. Log in to the Microsoft Azure portal at https://manage.windowsazure.com, and click SQL Databases in the vertical navigation pane on the left.

2. In the NAME column, click the database you want to upgrade to SQL Database Premium. This database must be in your SQL Database server with an approved SQL Database Premium quota.

3. Click the SCALE link at the top of the page, and configure SQL Database Premium:

a. For Edition, select Premium. This upgrades your database from the Web and Business editions to the SQL Database Premium edition.

b. For Reservation Size, you can select either P1 or P2. P1 is 1 core, 8 GB of memory and 150 IOPS. P2 is equivalent to two P1s. The page should appear similar to Figure 8-17.

Image

FIGURE 8-17 Selecting the SQL Database Premium reservation size

4. Click the SAVE button at the bottom of the page.

You have now scaled up your SQL Database using the preview of SQL Database Premium. With SQL Database Premium, you reserve a portion of the SQL Database server compute resources just for your database. This resource reservation provides consistent and predictable performance characteristics for a SQL Database.

Partitioning data

Scaling relational databases is not a trivial task. Stateless application and web servers are typically simple to scale. By deploying a new server and adding it to a load balancer, you instantly add capacity and scale your application. But the relationship characteristic and the ACID (Atomic, Consistent, Isolated, and Durable) properties of relational databases make it difficult to distribute a database across multiple compute nodes. If you need to scale a relational database across multiple compute nodes, you must partition the data. The following sections will describe approaches and techniques for partitioning databases.

Scaling out with functional partitions

One of the most common ways databases get partitioned is by function. Partitioning by function splits up data into multiple databases that each have closely related tables. Using the WineCloudDb database as an example, if you partitioned that database by function, you might split it into aCustomer database and a Catalog database. The Customer database would contain the Customer table and the Catalog database would contain the Wine table. If you also had an eCommerce application to sell the wine, you might have an Order database with Order and OrderDetail tables. Functional partitioning is also referred to as vertical partitioning. It is generally easier to implement and use than horizontal partitioning, which will be described in the next section.

To partition your data functionally, you need to remove database-enforced relationships and foreign keys between tables that will be in different databases. You can then split your tables into multiple databases, but without the database-level constraints, your applications will need to enforce the relationships between the tables that span databases. Any queries that join data across the tables that have been split into multiple databases need to be split, and the database-connection strings need to be updated to direct the application to the appropriate database. Splitting databases into functional partitions can get complex and time consuming if there are lots of relationships between tables, and lots of queries and applications that need to be updated.

One of the goals when partitioning data is to balance and equally distribute load across multiple compute nodes. Although partitioning data by function can help split up the load across multiple compute nodes, it is unlikely that your load will be distributed evenly across functional partitions. In the next section, you’ll explore another data-partitioning technique call sharding. Sharding is a technique that makes it easier to achieve an even load distribution.

Scaling out with shards

To equally distribute the load on relational databases across multiple compute nodes, you need to consider partitioning data within a single table. Partitioning a single table across multiple databases is referred to as horizontal partitioning or sharding. In the following procedure, you will partition the WineCloudDb into multiple shards based on customers. The WineCloudDb data set is small, but it will be sufficient to demonstrate sharding. You will split the database into two databases: the first with one customer record, and the second one with two customer records. When sharding databases, you must have an algorithm that determines what records will go in each database shard. You can partition customers into multiple shards in a number of ways. You can partition customers by the first letter of their last name, and that would result in 26 databases shards. But remember that the goal is to distribute load as evenly as possible, and using the first letter of last names is unlikely to be an even distribution, because some letters are more common than others.

In our WineCloudDb example, you will partition by a range of CustomerId values. The customer with a CustomerId of 1 will go into one database shard, the customers with a CustomerId ranging from 2 to 3 will go into another database shard. When sharding databases, you also need to have an index or map of how your data is partitioned so that, first, you can find your records and, second, you have a way to rearrange your records within the database shards if you find that your distribution is not equal and needs to be rebalanced. In the following example, the shard map is implemented in code for simplicity and readability. Typically, though, you implement the shard map in a persistent data store separate from your code.

Similar to functional partitions, it can be challenging to shard data when you have relationship constraints and foreign keys. Often, reference and lookup data will get duplicated and stored in each shard. (The Microsoft Azure SQL Data Sync service can help you maintain multiple copies of reference data tables across multiple shards; see Chapter 7, “Microsoft Azure SQL Data Sync,” for more information.) In the WineCloudDb example, you will store the Wine table in each shard. If the customer table has relationships to other customer-related tables, you typically partition those tables and store those related records in the same database shard as the customer record. In the WineCloudDb example, the Customer table doesn’t have additional customer-related tables, which makes sharding this database a lot simpler.

Creating the shard databases

For the Web APIs in this chapter, you will use the script shown in Listing 8-7 to create two WineCloudDb database shards and populate them each with the same set of wines. Then you will populate them each with different customers, and create ASP.NET Web APIs that return customers from the sharded databases.

LISTING 8-7 Script to create sharded databases


CREATE TABLE Wine (
WineId int PRIMARY KEY,
Name nvarchar (50),
Category nvarchar (15),
Year int,
Price money)

CREATE TABLE Customer (
CustomerId int PRIMARY KEY,
FirstName nvarchar(50),
LastName nvarchar(15),
FavoriteWineId int,
CONSTRAINT FK_Customer_Wine FOREIGN KEY (FavoriteWineId) REFERENCES Wine (WineId))

INSERT INTO Wine VALUES
(1, 'Chateau Penin', 'Bordeaux', 2008, 34.90),
(2, 'McLaren Valley', 'Cabernet', 2005, 48.50),
(3, 'Mendoza', 'Merlot', 2010, 42.00),
(4, 'Valle Central', 'Merlot', 2009, 52.00)


To create the WineCloudDb database shards, follow these steps:

1. From the Windows Start screen, launch SSMS. You can either scroll through the app tiles to find it (in the Microsoft SQL Server 2012 category) or just type sql server management studio to run a search, and then click on the tile. After a brief moment, the Connect To Server dialog appears.

2. In the Connect To Server dialog, do the following:

a. For Server Name, type <servername>.database.windows.net. This is the fully qualified name to the SQL Database server, where <servername> should be replaced by the name assigned to your server.

b. For Authentication, select SQL Server Authentication from the drop-down list. (SQL Database does not support Windows Authentication.)

c. For Login and Password, type the user name and password you assigned the server when you created it.

d. Click the Connect button.

3. Create the databases.

a. In the Object Explorer, right-click the server name and choose New Query to open a new query window connected to the master database.

b. In the query window, type: CREATE DATABASE WineCloudDbShard1
GO
CREATE DATABASE WineCloudDbShard2
GO

c. Press F5 (or click the Execute button in the toolbar) to create the two databases.

d. Close the query window without saving the script.

4. In the Object Explorer, right-click the Databases node and choose Refresh. The WineCloudDbShard1 and WineCloudDbShard2 databases you just created should now appear.

5. Right-click the WineCloudDbShard1 database, and choose New Query to open a new query window connected to the WineCloudDbShard1 database.

6. Type the code shown in Listing 8-7 into the query window (or paste it in from the listing file downloaded from the book’s companion website).

7. Press F5 (or click the Execute button in the toolbar) to create the database objects for the first shard, and then close the query window without saving the script.

8. Right-click the WineCloudDbShard1 database, and choose New Query to open a new query window connected to the WineCloudDbShard1 database.

9. Type the following code into the query window:

INSERT INTO Customer VALUES (1, 'Jeff', 'Hay', 4)

10. Press F5 (or click the Execute button in the toolbar) to add a customer row for Jeff Hay to the WineCloudDbShard1 database, and then close the query window without saving the script.

11. Right-click the WineCloudDbShard2 database, and choose New Query to open a new query window connected to the WineCloudDbShard2 database.

12. Type the code shown in Listing 8-7 into the query window (or paste it from the listing file downloaded from the book’s companion website).

13. Press F5 (or click the Execute button in the toolbar) to create the database objects for the second shard, and then close the query window without saving the script.

14. Right-click the WineCloudDbShard2 database, and choose New Query to open a new query window connected to the WineCloudDbShard2 database.

15. Type the following code into the query window:

INSERT INTO Customer VALUES
(2, 'Mark', 'Hanson', 3),
(3, 'Jeff', 'Phillips', 2)

16. Press F5 (or click the Execute button in the toolbar) to add two customer rows for Mark Hanson and Jeff Phillips to the WineCloudDbShard2 database, and then close the query window without saving the script.

You now have two new databases named WineCloudDbShard1 and WineCloudDbShard2 that will serve as the data source for your customer Web APIs. Each database has the same reference data in the Wine table, but the Customer table is partitioned horizontally between them.

Using shards with ADO.NET and ASP.NET Web API

Now that you have two database shards set up for the WineCloudDb database, you are ready to modify the Customer Web API you created earlier in this chapter to retrieve data from these multiple databases.

Listing 8-8 shows the classes used for working with the individual shard databases. The Shard class defines a shard database with the range of IDs that it will contain using the BeginId and the EndId properties. The Shard class also has a ConnectionString property that has a connection string for the database shard. The ShardRoot class represents the logical root that tracks the multiple shard databases that collectively form the single logical database. The CustomerShard class is the logical root implementation for the WineCloudDb database shards. It uses the Shard andShardRoot classes to collect the multiple customer databases into one logical container. The GetShard method on the CustomerShard class makes it easy to retrieve the database shard for a specified customer.

LISTING 8-8 Customer data sharding classes


using System.Collections.Generic;
using System.Linq;

namespace WineCloudWebApi.Data
{
public class Shard
{
public int Id { get; set; }
public int BeginId { get; set; }
public int EndId { get; set; }
public string ConnectionString { get; set; }
}

public class ShardRoot
{
public ShardRoot() { Shards = new List<Shard>(); }
public IList<Shard> Shards { get; private set; }
}

public class CustomerShard
{
private static CustomerShard _instance;
private static object _lockObj = new object();

public static CustomerShard Instance
{
get
{
if (_instance == null)
{
lock (_lockObj)
{
if (_instance == null)
{
_instance = new CustomerShard();
}
}
}
return _instance;
}
}

public ShardRoot ShardRoot { get; private set; }

public CustomerShard()
{
ShardRoot = new ShardRoot();
ShardRoot.Shards.Add(new Shard
{
Id = 1,
BeginId = 1,
EndId = 1,
ConnectionString =
"Server=tcp:<ServerName>.database.windows.net,1433;" +
"Database=WineCloudDbShard1;User ID=<UserName>@<ServerName>;" +
"Password=<Password>;Trusted_Connection=False;" +
"Encrypt=True;Connection Timeout=30;"
});
ShardRoot.Shards.Add(new Shard
{
Id = 2,
BeginId = 2,
EndId = 3,
ConnectionString =
"Server=tcp:<ServerName>.database.windows.net,1433;" +
"Database=WineCloudDbShard2;User ID=<UserName>@<ServerName>;" +
"Password=<Password>;Trusted_Connection=False;" +
"Encrypt=True;Connection Timeout=30;"
});
}

public Shard GetShard(int id)
{
return ShardRoot.Shards
.FirstOrDefault(shard => shard.BeginId <= id && shard.EndId >= id);
}
}
}


The CustomerController shown in Listing 8-9 has been modified to retrieve customers from the multiple databases using the CustomerShard class. The changes to the GetCustomer method are simple. Using the ID passed into the GetCustomer method, the database where that customer exists is returned by the GetShard method on the CustomerShard class. Then, using the same logic as in the previous CustomerController examples in this chapter, the customer table is queried and the Customer is returned. The changes required to get a single record as needed for theGetCustomer method are minimal.

However, the changes required for the GetCustomers method are a little more complex. Whenever a database is sharded into multiple databases, querying data that spans the multiple databases can be challenging. To query across the databases, you must query each database and merge the results. This approach is commonly referred to as fan-out querying. If you have many databases, you do not want to execute those queries in a series, waiting for the previous query to return, because that increases response time and provides a slower experience for users. Instead, you want to execute those queries simultaneously and aggregate the results as they are returned in parallel. The GetCustomers method in Listing 8-9 shows a simple fan-out query implementation using the Task Parallel Library. In this example, each database shard is queried for customers in parallel and the results from each query are merged and returned as one list of customers.

LISTING 8-9 CustomerController with support for Customer shards


using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description;
using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling;
using WineCloudWebApi.Data;
using WineCloudWebApi.Models;

namespace WineCloudWebApi.Controllers
{
public class CustomerController : ApiController
{
// GET api/Customer
public IList<Customer> GetCustomers()
{
var customers = new List<Customer>();

Parallel.ForEach(CustomerShard.Instance.ShardRoot.Shards,
new ParallelOptions
{ MaxDegreeOfParallelism = CustomerShard.Instance.ShardRoot.Shards.Count },
shard =>
{
var shardCustomers = new List<Customer>();
var connectionString = shard.ConnectionString;
var retryStrategy =
new Incremental(5, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2));
var retryPolicy =
new RetryPolicy<SqlDatabaseTransientErrorDetectionStrategy>(retryStrategy);

using (var connection = new SqlConnection(connectionString))
{
var commandText = "SELECT * FROM Customer";
using (var command = new SqlCommand(commandText, connection))
{
connection.OpenWithRetry(retryPolicy);
using (var reader = command.ExecuteReaderWithRetry(retryPolicy))
{
while (reader.Read())
{
var customer = new Customer
{
CustomerId = Convert.ToInt32(reader["CustomerId"]),
FirstName = reader["FirstName"].ToString(),
LastName = reader["LastName"].ToString(),
FavoriteWineId = reader["FavoriteWineId"] as int?
};
shardCustomers.Add(customer);
}
}
}
}
customers.AddRange(shardCustomers);
});

return customers;
}

// GET api/Customer/5
[ResponseType(typeof(Customer))]
public IHttpActionResult GetCustomer(int id)
{
Customer customer = null;

Shard customerShard = CustomerShard.Instance.GetShard(id);
if (customerShard != null)
{
var connectionString = customerShard.ConnectionString;
var retryStrategy =
new Incremental(5, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2));
var retryPolicy =
new RetryPolicy<SqlDatabaseTransientErrorDetectionStrategy>(retryStrategy);

using (var connection = new SqlConnection(connectionString))
{
var commandText = "SELECT * FROM Customer WHERE CustomerId = @CustomerId";
using (var command = new SqlCommand(commandText, connection))
{
command.Parameters.AddWithValue("@CustomerId", id);
connection.OpenWithRetry(retryPolicy);
using (var reader = command.ExecuteReaderWithRetry(retryPolicy))
{
if (reader.Read())
{
customer = new Customer
{
CustomerId = Convert.ToInt32(reader["CustomerId"]),
FirstName = reader["FirstName"].ToString(),
LastName = reader["LastName"].ToString(),
FavoriteWineId = reader["FavoriteWineId"] as int?
};
}
}
}
}
}

if (customer == null)
{
return NotFound();
}
return Ok(customer);
}
}
}


To use the database shards with the customer Web API, follow these steps:

1. Open the WineSolution in Visual Studio 2013.

2. Right-click the WineCloudWebApi project in Solution Explorer, and choose Add | New Folder.

3. Name the new folder Data, and press Enter.

4. Right-click on the newly created Data folder, and choose Add | Class to display the Add New Item dialog.

5. Name the new class CustomerShard.cs, and click Add.

6. Replace the template code with the code shown earlier in Listing 8-8. In the connection strings contained in the code, do the following:

a. Replace <ServerName> with the name of the SQL Database server that contains the shard databases.

b. Replace <UserName> and <Password> with the user name and password you assigned the server when you created it.

7. Expand the Controllers folder beneath the WineCloudWebApi project in Solution Explorer.

8. Double-click the CustomerController.cs, and replace the code with the code shown earlier in Listing 8-9.

9. Build the WineSolution by selecting Build | Build Solution in the menu at the top or pressing Ctrl+Shift+B.

You have now split the WineCloudDb database into multiple database shards, splitting on customer records. You can test the customer API using the same steps from the “Testing the Customer Web API” section.

Listing 8-8 and Listing 8-9 show a simple implementation of integrating database sharding into your application. In Listing 8-8, there are classes to manage and group the sharded databases; in Listing 8-9, the CustomerController has a simple implementation of fan-out querying using the Task Parallel Library. This implementation works for scenarios that are small and not overly complex, but in larger and more complex scenarios you will likely need additional capabilities to track database shards and update data across shards. There are existing libraries you can use to help with these additional capabilities. The Microsoft Azure Customer Advisory Team creates guidance, frameworks, and reference applications based on what they have learned from real-world customer engagements. Microsoft Azure CAT has developed a reference application called Cloud Service Fundamentals in Windows Azure that includes a library named Microsoft.AzureCat.Patterns.Data.SqlAzureDalSharded that contains classes that will help you when sharding relational databases. You can reuse this library in your applications, and it is available athttp://code.msdn.microsoft.com/windowsazure/Cloud-Service-Fundamentals-4ca72649.


Image Note

EF can also be used when sharding databases. Using the same approach shown in Listing 8-9, you can use the Task Parallel Library to execute queries across multiple databases in parallel. However, instead of using raw ADO.NET objects to access your database, you use EF objects. You construct an EF DbContext for each database, instead of opening a new SqlConnection. Using the DbContext object, you then query the appropriate DbSet property using LINQ To Entities. Sharding with EF is possible, but if you are trying to boost performance, the performance overhead that comes with using EF may encourage you to use raw ADO.NET instead.


Summary

This chapter introduced you to optimizing performance and scaling Microsoft Azure SQL Database. You created an ASP.NET Web API that was used as a reference application throughout the chapter, and you improved the reliability and performance of this Web API by managing database connections and connection errors and reducing latency in Microsoft Azure. You then considered other optimizations, like using the most appropriate storage service for your data and query optimization. Later in the chapter, you scaled up SQL Database using SQL Database Premium. And in the last section, you scaled your SQL Database with partitioning and sharding strategies.

Designing highly available, high-performance, scalable systems is a very large topic. This chapter provided an understanding of the most important concepts, principles, and techniques for achieving high performance and scale with SQL Database, but there is a lot more for you to learn on your own.