At Vertigo, we recently did some work on a Windows Azure application that needed a document database. We wound up using RavenDB. Getting everything running in Azure took a little bit of work. There are a few posts on this topic here and here, but they weren’t quite up to date and did not include the RavenDB Management Studio. So I wanted to share what we learned and how we got RavenDB running in Windows Azure.
Download
You can download:
- Source on github.com (comming soon)
- Direct Download (comming soon)
Goal
At the end of the day, what I want to do is run RavenDB in a Windows Azure Worker Role (with the RavenDB admin studio working) and use the RavenDB client in an ASP.NET MVC 3 or 4 application.
Getting Started
Before we start, we need to get a few software pre-reqs out of the way:
We built the sample by doing the following:
- Start Visual Studio 2010
- Create a new Windows Azure Project
- Add an ASP.NET MVC 3 (or 4) Web Role and an Worker Role:
- Create a new Internet Application:
- Now that we have our Solution and Projects setup, let’s add RavenDB to the Azure Worker Role project (named RavenDbWorkerRole).
- Select the RavenDbWorkerRole project and right click and click Manage NuGet Packages…
- Search for RavenDB and click Install.
- Now we need to add the Raven Studio Silverlight application to the project. In the
Solution Explorer for the RavenDb Azure Worker Role project, right click and select
Add | Existing Item:
- Add Raven.Studio.xap:
- We need to make 2 key changes to get the Silverlight app to be bundled correctly.
First we need to set the Build Action to Embedded Resource. Second,
we need to change the Copy to Output Directory to Copy Always.
- OK, before we add some code, let’s get the ports setup for our Worker Role. We want
to run the Worker Role in the local Azure Dev Fabric using a custom endpoint. Right
click and select Properties on the RavenDbWorkerRole configuration
in the Windows Azure Roles:
- Click Endpoints:
- Click Add Endpoint:
- Enter the following:
Name: Raven
Type: Input
Protocol: http
Public Port: 8080
Private Port: 8080
As shown in the following:
- Now let’s add a local cache. Click Local Storage:
- Create a LocalStorage entry using the following:
Name: RavenCache
Size (MB): 1024
Clean on role recycle: unchecked
As shown in the following:
- Before we can write some code, we need to add a few additional Assembly references.
The first is the RavenDB Database Server. NuGet adds the required client .NET Assemblies
for calling into RavenDB, but we need one more Assembly for the server database.
Right click on the RavenDbWorkerRole project and click Add Reference and Browse
to the NuGet packages folder. In packages navigate to RavenDB.1.0.531\server as
shown below:
- We need to add one more RavenDB server Assembly:
- Add now add Raven.Storage.Managed.dll:
- Next we need to add a reference for Windows Azure Cloud Drive
- Modify WorkerRole.cs
using System; using System.Diagnostics; using System.Net; using System.Threading; using Microsoft.WindowsAzure; using Microsoft.WindowsAzure.ServiceRuntime; using Microsoft.WindowsAzure.StorageClient; using Raven.Database; using Raven.Database.Config; using Raven.Database.Server; namespace RavenDbWorkerRole { public class WorkerRole : RoleEntryPoint { private DocumentDatabase documentDatabase = null; private HttpServer httpServer = null; public override void Run() { Trace.WriteLine("RavenDbWorkerRole entry point called", "Information"); while (true) { Thread.Sleep(10000); Trace.WriteLine("Working", "Information"); } } public override bool OnStart() { Trace.WriteLine("RavenDbWorkerRole: OnStart() called", "Information"); // Set the maximum number of concurrent connections ServicePointManager.DefaultConnectionLimit = 12; string connectionString = "StorageAccount"; CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) => { if ((RoleEnvironment.IsAvailable) && (!RoleEnvironment.IsEmulated)) { connectionString = RoleEnvironment.GetConfigurationSettingValue(configName); } else { connectionString = "UseDevelopmentStorage=true"; } configSetter(connectionString); }); CloudStorageAccount storageAccount = CloudStorageAccount.FromConfigurationSetting(connectionString); LocalResource localCache = RoleEnvironment.GetLocalResource("RavenCache"); CloudDrive.InitializeCache(localCache.RootPath, localCache.MaximumSizeInMegabytes); // let's create the cloud drive CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient(); blobClient.GetContainerReference("drives").CreateIfNotExist(); CloudDrive cloudDrive = storageAccount.CreateCloudDrive( blobClient .GetContainerReference("drives") .GetPageBlobReference("ravendb.vhd") .Uri.ToString() ); try { // create a 1GB Virtual Hard Drive cloudDrive.Create(1024); } catch (CloudDriveException /*ex*/ ) { // the most likely exception here is ERROR_BLOB_ALREADY_EXISTS // exception is also thrown if the drive already exists } string driveLetter = cloudDrive.Mount(25, DriveMountOptions.Force); if (!driveLetter.EndsWith("\\")) { driveLetter += "\\"; } var config = new RavenConfiguration { DataDirectory = driveLetter, AnonymousUserAccessMode = AnonymousUserAccessMode.All, HttpCompression = true, DefaultStorageTypeName = "munin", Port = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["Raven"].IPEndpoint.Port, PluginsDirectory = "plugins" }; StartRaven(config); return base.OnStart(); } private void StartRaven(RavenConfiguration config) { try { documentDatabase = new DocumentDatabase(config); documentDatabase.SpinBackgroundWorkers(); httpServer = new HttpServer(config, documentDatabase); try { httpServer.Start(); } catch (Exception ex) { Trace.WriteLine("StartRaven Error: " + ex.ToString(), "Error"); if (httpServer != null) { httpServer.Dispose(); httpServer = null; } } } catch (Exception ex) { Trace.WriteLine("StartRaven Error: " + ex.ToString(), "Error"); if (documentDatabase != null) { documentDatabase.Dispose(); documentDatabase = null; } } } private void StopRaven() { if (httpServer != null) { httpServer.Dispose(); httpServer = null; } if (documentDatabase != null) { documentDatabase.Dispose(); documentDatabase = null; } } } }
Notes
We discovered a few caveats along the way:
- The RavenDB management studio, which is a Silverlight application, will take some time to download when running from Azure. Give it about a minute to load as the .XAP file is about 3MB.
- What about performance? Take a look here.
- If you run into Could not find transactional storage type: Raven.Storage.Managed.TransactionalStorage coming from Raven.Storage.Managed you most likely need to add an Assembly reference to Raven.Storage.Esent.dll.
- If you get the following runtime exception "Could not find transactional storage type: Raven.Storage.Managed.TransactionalStorage from Raven.Storage.Managed" then you are probably missing an Assembly reference to Raven.Storage.Managed.dll.
Next up, I will show how to consume data from RavenDB in an Azure Web Role.