Ip2Location (and IPinfoDB) Performance Tips

by Brian Hitney 9. March 2010 14:00

I’ve done a number of talks lately on Worldmaps and typically in side conversations/emails, people are curious about the databases and converting IP addresses to geographic locations.   And, often when you dive into using the data, it seems there are a number of performance considerations and I thought I’d share my input on these topics.

First up, the data.  Worldmaps uses two databases for IP resolution.  The primary/production database is Ip2Location.  I’ve found this database to be very accurate.  For development/demo purposes, I use IPinfoDB.  I haven’t had too much time to play with this database yet, but so far seems accurate also.   The latter is free, whereas Ip2Location is not.

In either case, the schema is nearly identical:

image

The BeginIp and EndIp columns are a clustered primary key.  In the case of IPinfoDB, there is no EndIp field (and it’s not really needed).  When performing a resolution, a string IP address is converted into a 64 bit integer and then used in searching the table.  That’s why having a clustered key on the BeginIp (and optionally EndIp) is crucial to performance.

But it doesn’t stop there.   The examples posted in the database’s respective home pages are accurate and simple, but need to be refactored for performance.  For example, to do a simple resolution on Ip2Location, according to their docs:

SELECT * FROM dbo.Ip2Location WHERE @IpNum BETWEEN BeginIp and EndIp

And for IPInfoDB:

SELECT TOP 1 * FROM IPInfoDB where BeginIp <= @IpNum ORDER BY BeginIp DESC

Both of these methods are perfectly fine, particularly for use as generic samples.  The second one is on the right track, but it doesn’t work for joins so if you’re querying over a range, you’d need to refactor.  And in the first example, using a BETWEEN operator forces a clustered index scan when joining, killing the performance.

If we run the first example across my minified Ip2LocationSmall table, we’ll see something like this (and this is running off of SQL Azure – the perf is pretty great compared to localhost!):

image

We can also look at the time:

image

Ouch!  Now, it doesn’t seem too bad, but imagine doing thousands of these requests per minute, or doing large joins. 

The goal then is to provide some hints that will optimize the query, particularly for joins.  Our indexes are correct, so we can rework the query to get rid of the BETWEEN operator – we can sacrifice a little readability and do something like:

SELECT *   
FROM (
    select
        ( select MAX(beginip)
          from dbo.Ip2LocationSmall
          where BeginIp <= @IpNum
        ) as IP_Begin           
    ) as foo
INNER JOIN dbo.Ip2LocationSmall iploc
ON iploc.BeginIp = foo.IP_Begin

The result:

image

And the time shows some improvement:

image

But the REAL benefit comes when we need to join.   Suppose I’d like to get a list of the countries for a given map (which is a parameter called MapId):

SELECT DISTINCT(ip.CountryCode)
FROM MapHits hits
INNER JOIN dbo.Ip2LocationSmall ip
    ON hits.IpNum BETWEEN ip.BeginIp AND ip.EndIp
WHERE MapId = @MapId

The query returns 95 rows, and executes in 16 seconds:

image

image

In this case, we can refactor this using the method above to something like:

SELECT
    DISTINCT(CountryCode)   
FROM (
     select IpNum,
        ( select MAX(beginip)
          from Ip2LocationSmall
          where BeginIp <= IpNum
        ) as IP_Begin
      from dbo.MapHits as hits
      where MapId = @MapId
  ) as foo
INNER JOIN Ip2LocationSmall iploc
ON iploc.BeginIp = foo.IP_Begin

Again, not as pretty looking, but boy what a difference:

image

image

We went from 16,500 milliseconds to 260 – over 60x the performance!  Mike @AngryPets would be proud.   The reason for the perf gain is we were able to eliminate the nested loop, which is (in this case) scanning the entire clustered index for the matching rows.

The second benefit is the ability to switch schemas easily between IP2Location and IPinfoDB, and we can additionally lose the EndIp column which trims the size of the table.

Tags: , , ,

SQL Azure | SQL | Tech Tips | Development

Another Geek Dinner, Thursday 3/4

by Brian Hitney 1. March 2010 14:12

Prior to the MSDN Event in Charlotte on Friday, Glen and I will head over to Ruby Tuesday on 3/4 around 6pm.  If you’re in Charlotte and would like to hang out, please stop over.   If you have Azure related questions, stop on over!

Ruby Tuesday
8905 Red Oak Blvd, Charlotte, NC
http://www.bing.com/maps/?v=2&ss=ypid.YN529x9301109&encType=1

Tags:

Events

Geek Dinner Wednesday

by Brian Hitney 1. March 2010 12:00

After the MSDN Event on Wednesday 3/3/2010, Glen and I will head over the Carolina Ale House around the corner from the event.  Attending Wednesday’s event?   Stop by after the event – we’ll start around 6pm!

Brier Creek Ale House
7981 Skyland Ridge Pkwy, Raleigh, NC
http://www.bing.com/maps/?v=2&ss=ypid.YN523x12588626&encType=1

Tags:

Events

Azure Miniseries #4: Monitoring Applications

by Brian Hitney 26. February 2010 03:58

In this screencast, we'll take a look at monitoring Azure applications by capturing event logs and performance counters. We'll also look at using PowerShell to deploy and configure applications using the management API. Finally, we'll take a sneak peek at Azure Diagnostics Manager, a tool from Cerebrata that allows you explore event logs and look at performance counters visually.

Get Microsoft Silverlight

Here are some links from the screencast:

Finally, let’s get into some code snippets! Watch for wrap on the PowerShell lines, and note the single quote ` character as the line continuation:

Creating a self-signed certificate command:

makecert -r -pe -a sha1 -n "CN=Azure Service Test" -ss My -len 2048 -sp "Microsoft Enhanced RSA and AES Cryptographic Provider" -sy 24 AzureServiceTest.cer

Uploading a new deployment to the staging slot, and starting it (requires Azure CmdLets):

$cert = Get-Item cert:\CurrentUser\My\{thumbprint}
$sub = "{subscription GUID}"
$servicename = "{service name}"

$package = "CloudApp.cspkg"
$config = "ServiceConfiguration.cscfg"

[DateTime]$datelabel = Get-Date
$lbl = $datelabel.ToString("MM-dd-yyyy-HH:mm")

Write-Host "Label for deployment: " $lbl

Add-PSSnapin AzureManagementToolsSnapIn

Get-HostedService $servicename -Certificate $cert -SubscriptionId $sub |
New-Deployment -Slot Staging $package $config -Label $lbl |
Get-OperationStatus -WaitToComplete

Get-Deployment staging -serviceName $servicename -SubscriptionId $sub -Certificate $cert |
Set-DeploymentStatus running |
Get-OperationStatus -WaitToComplete

Increasing the number of instances:

Add-PSSnapin AzureManagementToolsSnapIn

$cert = Get-Item cert:\CurrentUser\My\{thumbprint}
$sub = "{subscription GUID}"
$servicename = "{service name}"
$storage = "{storage name}"

#get storage account name and key
$key = (Get-StorageKeys -ServiceName $storage -Certificate $cert -SubscriptionId $sub).Primary
$deployId = (Get-HostedService $servicename -SubscriptionId $sub -Certificate $cert | Get-Deployment Production).DeploymentId       

Get-HostedService $servicename -Certificate $cert -SubscriptionId $sub |
    Get-Deployment -Slot Staging |
    Set-DeploymentConfiguration {$_.RolesConfiguration["WebRole1"].InstanceCount += 1}

Updating the performance counters – specifically, adding total processor time, ASP.NET req/sec, and memory usage to be polled every 30 seconds, and uploaded every 1 minute:

Add-PSSnapin AzureManagementToolsSnapIn

$cert = Get-Item cert:\CurrentUser\My\{thumbprint}
$sub = "{subscription GUID}"
$servicename = "{service name}"
$storage = "{storage name}"

#get storage account name and key
$key = (Get-StorageKeys -ServiceName $storage -Certificate $cert -SubscriptionId $sub).Primary
$deployId = (Get-HostedService $servicename -SubscriptionId $sub -Certificate $cert | Get-Deployment Production).DeploymentId       

# rate at which counters are polled
$rate = [TimeSpan]::FromSeconds(30)

Get-DiagnosticAwareRoles -StorageAccountName $storage -StorageAccountKey $key -DeploymentId $deployId |
foreach {
    $role = $_
    write-host $role
    Get-DiagnosticAwareRoleInstances $role -DeploymentId $deployId `
        -StorageAccountName $storage -StorageAccountKey $key |

    foreach {
        $instance = $_

        $config = Get-DiagnosticConfiguration -RoleName $role -InstanceId $_ -StorageAccountName $storage `
             -StorageAccountKey $key -BufferName PerformanceCounters -DeploymentId $deployId            
        $processorCounter = New-Object Microsoft.WindowsAzure.Diagnostics.PerformanceCounterConfiguration `
                -Property @{CounterSpecifier='\Processor(_Total)\% Processor Time'; SampleRate=$rate }
        $memoryCounter = New-Object Microsoft.WindowsAzure.Diagnostics.PerformanceCounterConfiguration `
                -Property @{CounterSpecifier='\Memory\Available Mbytes'; SampleRate=$rate }
        $requestsCounter = New-Object Microsoft.WindowsAzure.Diagnostics.PerformanceCounterConfiguration `
                -Property @{CounterSpecifier='\ASP.NET Applications(__Total__)\Requests/Sec'; SampleRate=$rate }
        $config.DataSources.Clear()
        $config.DataSources.Add($processorCounter)
        $config.DataSources.Add($memoryCounter)
        $config.DataSources.Add($requestsCounter)
        Set-PerformanceCounter -PerformanceCounters $config.DataSources -RoleName $role `
             -InstanceId $instance -DeploymentId $deployId `
             -TransferPeriod 1 `
             -StorageAccountName $storage -StorageAccountKey $key                     
    }  
}

And finally, the webrole.cs class from the screencast:

public class WebRole : RoleEntryPoint
    {
        public override bool OnStart()
        {
            DiagnosticMonitorConfiguration diagConfig =
                DiagnosticMonitor.GetDefaultInitialConfiguration();

            diagConfig.PerformanceCounters.DataSources.Add(
                new PerformanceCounterConfiguration()
                {
                    CounterSpecifier = @"\Processor(_Total)\% Processor Time",
                    SampleRate = TimeSpan.FromSeconds(5)
                });

            diagConfig.PerformanceCounters.DataSources.Add(
                new PerformanceCounterConfiguration()
                {
                    CounterSpecifier = @"\Memory\Available Mbytes",
                    SampleRate = TimeSpan.FromSeconds(5)
                });

            diagConfig.PerformanceCounters.ScheduledTransferPeriod =
                TimeSpan.FromMinutes(1);

            diagConfig.Logs.ScheduledTransferLogLevelFilter = LogLevel.Information;
            diagConfig.Logs.ScheduledTransferPeriod = TimeSpan.FromMinutes(1);

            DiagnosticMonitor.Start("DiagnosticsConnectionString", diagConfig);

            System.Diagnostics.Trace.TraceInformation("Done configuring diagnostics.");

            // For information on handling configuration changes
            // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.
            RoleEnvironment.Changing += RoleEnvironmentChanging;

            return base.OnStart();
        }

        public override void OnStop()
        {
            System.Diagnostics.Trace.TraceWarning("Onstop called.");
            base.OnStop();
        }

        private void RoleEnvironmentChanging(object sender, RoleEnvironmentChangingEventArgs e)
        {
            // If a configuration setting is changing
            if (e.Changes.Any(change => change is RoleEnvironmentConfigurationSettingChange))
            {
                // Set e.Cancel to true to restart this role instance
                e.Cancel = true;
            }
        }
    }

Tags: , , ,

Azure | Microsoft | Tech Tips

Azure Miniseries #3: ServiceConfig vs web.config

by Brian Hitney 22. February 2010 03:32

One of the challenges developers will face when developing Windows Azure web applications is: where do I put my settings?  In the ServiceConfiguration file or the web.config?

There isn’t one correct answer.  The challenge of keeping everything in the web.config is that it makes changes and deployment much more difficult.  Because the web.config is part of the deployment, any change to the file also requires a redeployment.  If you use a build system that targets your dev/stage/QA/prod environments automatically and can provide the correct settings in the web.config for you, this might mitigate the problem.

The answer then is to migrate these settings to the ServiceConfiguration file as it requires no changes to our deployment package.  In this screencast I’ll show you some strategies for doing that for components that are more difficult to migrate, like the SqlMembershipProvider…

Get Microsoft Silverlight

Link to original post and download links.

Tags: , ,

Azure | Microsoft | Tech Tips

Azure Deployment Follow-up

by Brian Hitney 20. February 2010 18:04

In my recent screencast on Azure deployment, I focused mainly on deploying manually through the Azure web interface.   You can also use the management API to deploy/configure/reconfigure your applications programmatically from your custom code or via powershell scripts.

The one thing you cannot do via the web interface that you can do programmatically is change the WAD (Windows Azure Diagnostics) performance counter and logging information.  This is really useful because many times, you profile an application for a given period of time and not all the time.

To do this, you need to first configure a certificate that will be used to control access to the management API and send it with every request to the REST-based API (for example, when uploading a package).  The easiest way to create a self signed certificate is with the makecert utility included in the Windows SDK:

makecert -r -pe -a sha1 -n "CN=Azure Service Management" -ss My -len 2048 -sp "Microsoft Enhanced RSA and AES Cryptographic Provider" -sy 24 AzureServiceManagement.cer

The public key is then uploaded to your Azure account via the web interface:

image

If you’re looking for powershell examples, check out the Azure Powershell CmdLets in the resources I list below.   For example, you could write a simple powershell script to take your deployment, upload it to Azure Storage, update the staging slot, and start the service by doing something like:

$cert = Get-Item cert:\CurrentUser\My\{thumbprint}
$sub = "{subscription GUID}"
$servicename = "service name"
$package = "CloudApp.cspkg"
$config = "ServiceConfiguration.cscfg"

[DateTime]$datelabel = Get-Date
$lbl = $datelabel.ToString("MM-dd-yyyy-HH:mm")

Write-Host "Label for deployment: " $lbl

Add-PSSnapin AzureManagementToolsSnapIn

Get-HostedService $servicename -Certificate $cert -SubscriptionId $sub |
New-Deployment -Slot Staging $package $config -Label $lbl |
Get-OperationStatus -WaitToComplete

Get-Deployment staging -serviceName $servicename -SubscriptionId $sub -Certificate $cert |
Set-DeploymentStatus running |
Get-OperationStatus -WaitToComplete

Alternatively, check out the Windows Azure code samples (reference below) for doing this via C#.  Two immediate possibilities are:  ease in deploying applications, configuring performance counters, and scaling applications by adjusting the number of instances on the fly.

Resources:

MSDN API Reference

Azure Powershell CmdLets

Windows Azure Code Samples – Specifically, CSManage.exe

Tags:

Azure Miniseries #2: Deployment

by Brian Hitney 13. February 2010 13:39

In my first Azure Miniseries post, I showed setting up a new cloud service project and migrating an existing ASP.NET application into Azure.   Before I dive into other topics, I figured I’d jump to the end and discuss deployment – getting your Azure application into the cloud.  

Get Microsoft Silverlight

Link to original post with download links.

Tags: , ,

Azure | Microsoft | Technology

SQL Azure Logins

by Brian Hitney 13. February 2010 05:00

SQL Azure currently has fairly limited management capabilities.  When you create a database, you receive an administrator account that is tied to your login (you can change the SQL Azure password, though).  Because there is no GUI for user management, there’s a temptation to use this account in all your applications, but I highly recommend you create users for your application that have limited access. 

If you limit access to only stored procedures, you need to specify execute permissions.  Assuming you want your connection to have execute permissions on all stored procedures, I recommend a new role that has execute permissions.  That way, you can simply add users to this role and as you add more stored procedures, it simply works.  To create this role, you can do something like this:

CREATE ROLE db_executor
GRANT EXECUTE TO db_executor

Now in the master database (currently, you need to do this in a separate connection – just saying ‘use master’ won’t work) you can create your login for the database:

CREATE LOGIN MyUserName 
    WITH PASSWORD = 'Password';

In your application database, you need to create a user – in this case, we’ll just create a user with the same name as the login:

CREATE USER MyUserName FOR LOGIN MyUserName;

Next, we’ll specify the appropriate roles.  Depending on your needs, you may need only datareader.  I recommend db_owner only if necessary.

-- read/write/execute permissions
EXEC sp_addrolemember N'db_datareader', N'MyUserName'
EXEC sp_addrolemember N'db_datawriter', N'MyUserName'
EXEC sp_addrolemember N'db_executor', N'MyUserName'

-- only if you need dbo access:
EXEC sp_addrolemember N'db_owner', N'MyUserName'

You can continue to customize as necessary, as long as you are familiar with the appropriate T-SQL. 

Tags: , ,

Azure | SQL | SQL Azure

WCF in an Azure WorkerRole

by Brian Hitney 12. February 2010 04:16

The other day, a colleague got in touch with me looking for help in getting a WCF service working in an Azure WorkerRole.   It would work locally, but not deployed in the cloud.   This is a common problem I’ve run into – for example, calling Open() on a ServiceHost will work locally, but no in the cloud due to permissions.

I wasn’t much help in getting John’s situation resolved, but he pinged me about it a couple days later with the solution.  The first is to make sure your service has the correct behavior to respond to any address:

[ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]

The next was to make sure you explicitly set the SecurityMode in the binding:

NetTcpBinding binding = new NetTcpBinding(SecurityMode.None); 

Webroles are different as they are hosted in IIS and limited to HTTP.

Also, there are some good demos mentioned in this post on the MSDN forums that points to the Azure All-In-One demos on CodePlex. 

Tags: , ,

Azure | Tech Tips

Azure Miniseries #1: Migration

by Brian Hitney 12. February 2010 03:54

I’m starting to put together some short form screencasts on Windows Azure related topics.  I’ll use my blog to dive into specifics or display code samples/downloads where appropriate – but first up is a quick look at getting a project setup and migrating existing applications into an Azure webrole.

Get Microsoft Silverlight

Tags: ,

Azure | Microsoft | Technology

your host...

Brian Hitney
Developer Evangelist
Microsoft Corp.

About Me

My Worldmap