Skip to content

Instantly share code, notes, and snippets.

@joelverhagen
Created January 18, 2021 07:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joelverhagen/bca6e0f9ed8fa779fcabc5c2904505ae to your computer and use it in GitHub Desktop.
Save joelverhagen/bca6e0f9ed8fa779fcabc5c2904505ae to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
namespace EntityLimits
{
class Program
{
static async Task Main()
{
var connectionString = "UseDevelopmentStorage=true";
var account = CloudStorageAccount.Parse(connectionString);
var client = account.CreateCloudTableClient();
var table = client.GetTableReference("test");
await table.CreateIfNotExistsAsync();
await TryAsync(table);
}
private static async Task TryAsync(CloudTable table)
{
var batch = new TableBatchOperation();
if (table.Uri.Host != "127.0.0.1")
{
const int RealMaxEntitySize = 1024 * 1024;
batch.Add(TableOperation.InsertOrReplace(GetMaxSizeEntity("P", "A", RealMaxEntitySize)));
batch.Add(TableOperation.InsertOrReplace(GetMaxSizeEntity("P", "B", RealMaxEntitySize)));
batch.Add(TableOperation.InsertOrReplace(GetMaxSizeEntity("P", "C", RealMaxEntitySize - 1307)));
}
else
{
const string pk = "A";
var EmulatorMaxEntitySize = 393251;
// pk length 1: max entity size = 393251
// pk length 2: max entity size = 393250
// pk length 3: max entity size = 393253
// pk length 4: max entity size = 393254
// pk length 5: max entity size = 393256
batch.Add(TableOperation.InsertOrReplace(GetMaxSizeEntity(pk, "A", EmulatorMaxEntitySize)));
batch.Add(TableOperation.InsertOrReplace(GetMaxSizeEntity(pk, "B", EmulatorMaxEntitySize)));
batch.Add(TableOperation.InsertOrReplace(GetMaxSizeEntity(pk, "C", EmulatorMaxEntitySize)));
batch.Add(TableOperation.InsertOrReplace(GetMaxSizeEntity(pk, "D", EmulatorMaxEntitySize)));
batch.Add(TableOperation.InsertOrReplace(GetMaxSizeEntity(pk, "E", EmulatorMaxEntitySize)));
batch.Add(TableOperation.InsertOrReplace(GetMaxSizeEntity(pk, "F", EmulatorMaxEntitySize)));
batch.Add(TableOperation.InsertOrReplace(GetMaxSizeEntity(pk, "G", EmulatorMaxEntitySize)));
batch.Add(TableOperation.InsertOrReplace(GetMaxSizeEntity(pk, "I", EmulatorMaxEntitySize - 2265)));
}
try
{
await table.ExecuteBatchAsync(batch);
Console.WriteLine("success!");
}
catch (StorageException ex)
{
Console.WriteLine(ex);
}
}
private static DynamicTableEntity GetMaxSizeEntity(string pk, string rk, int maxEntitySize)
{
const int MaxBinarySize = 64 * 1024; // 64 KiB
var entity = new DynamicTableEntity
{
PartitionKey = pk,
RowKey = rk,
};
var propertyNames = new[] { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
var remaining = maxEntitySize - entity.GetEntitySize();
if (remaining > maxEntitySize)
{
throw new ArgumentOutOfRangeException(nameof(maxEntitySize));
}
while (remaining > 0)
{
var binarySize = Math.Min(remaining - TableEntityExtensions.MinPropertySize, MaxBinarySize);
if (binarySize < 0)
{
break;
}
entity.Properties[propertyNames[entity.Properties.Count]] = new EntityProperty(new byte[binarySize]);
remaining = maxEntitySize - entity.GetEntitySize();
}
Console.WriteLine($"({entity.PartitionKey}, {entity.RowKey}): entity size = {entity.GetEntitySize()}");
if (Math.Abs(entity.GetEntitySize() - maxEntitySize) > 3)
{
throw new InvalidOperationException();
}
return entity;
}
}
public static class TableEntityExtensions
{
public static readonly int MinPropertySize = GetEntityPropertySize("A", new EntityProperty(new byte[0]));
public static int GetEntitySize(this ITableEntity entity)
{
if (entity == null)
{
throw new ArgumentNullException(nameof(entity));
}
if (entity is DynamicTableEntity dte)
{
return GetEntitySize(dte.PartitionKey, dte.RowKey, dte.Properties);
}
var properties = entity.WriteEntity(operationContext: null);
return GetEntitySize(entity.PartitionKey, entity.RowKey, properties);
}
/// <summary>
/// Source: https://docs.microsoft.com/en-us/archive/blogs/avkashchauhan/how-the-size-of-an-entity-is-caclulated-in-windows-azure-table-storage
/// </summary>
private static int GetEntitySize(string partitionKey, string rowKey, IEnumerable<KeyValuePair<string, EntityProperty>> properties)
{
if (partitionKey == null)
{
throw new ArgumentNullException(nameof(partitionKey));
}
if (rowKey == null)
{
throw new ArgumentNullException(nameof(rowKey));
}
if (properties == null)
{
throw new ArgumentNullException(nameof(properties));
}
var size = 4 + (partitionKey.Length + rowKey.Length) * 2;
foreach (var property in properties)
{
size += GetEntityPropertySize(property.Key, property.Value);
}
// See: https://github.com/MicrosoftDocs/azure-docs/issues/68661
size += 88;
return size;
}
private static int GetEntityPropertySize(string name, EntityProperty value)
{
int size;
switch (value.PropertyType)
{
case EdmType.String when value.StringValue == null:
return 0;
case EdmType.DateTime when !value.DateTime.HasValue:
return 0;
case EdmType.Guid when !value.GuidValue.HasValue:
return 0;
case EdmType.Double when !value.DoubleValue.HasValue:
return 0;
case EdmType.Int32 when !value.Int32Value.HasValue:
return 0;
case EdmType.Int64 when !value.Int64Value.HasValue:
return 0;
case EdmType.Boolean when !value.BooleanValue.HasValue:
return 0;
case EdmType.Binary when value.BinaryValue == null:
return 0;
case EdmType.String:
size = value.StringValue.Length * 2 + 4;
break;
case EdmType.DateTime:
size = 8;
break;
case EdmType.Guid:
size = 16;
break;
case EdmType.Double:
size = 8;
break;
case EdmType.Int32:
size = 4;
break;
case EdmType.Int64:
size = 8;
break;
case EdmType.Boolean:
size = 1;
break;
case EdmType.Binary:
size = value.BinaryValue.Length + 4;
break;
default:
throw new NotImplementedException();
};
return 8 + name.Length * 2 + size;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment