Created
January 18, 2021 07:05
-
-
Save joelverhagen/bca6e0f9ed8fa779fcabc5c2904505ae to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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