Как добиться более 10 вставок в секунду с помощью лазурных таблиц хранения

Я пишу простой WorkerRole, который добавляет тестовые данные в таблицу. Код вложений выглядит следующим образом.

var TableClient = this.StorageAccount.CreateCloudTableClient();
TableClient.CreateTableIfNotExist(TableName);
var Context = TableClient.GetDataServiceContext();

this.Context.AddObject(TableName, obj);
this.Context.SaveChanges();

Этот код запускается для каждого запроса клиента. Я тестирую 1-30 клиентских потоков. У меня много попыток с различным количеством экземпляров разных размеров. Я не знаю, что я делаю неправильно, но не могу получить больше 10 вставок в секунду. Если кто-то знает, как увеличить скорость, пожалуйста, сообщите мне. Благодаря

UPDATE

  • Удаление CreateTableIfNotExist не влияет на мои тесты вставки.
  • Режим переключения для ожидания100Continue = "false" useNagleAlgorithm = "false" делает короткий эффект времени при вставке скорости скачка до 30-40 ипс. Но затем, через 30 секунд, скорость вставки упадет до 6 ips с 50% таймаутами.

Ответ 1

Чтобы ускорить работу, вы должны использовать пакетные транзакции (транзакции Entity Group Transactions), позволяющие фиксировать до 100 элементов в рамках одного запроса:

foreach (var item in myItemsToAdd)
{
    this.Context.AddObject(TableName, item);
}
this.Context.SaveChanges(SaveChangesOptions.Batch);

Вы можете комбинировать это с Partitioner.Create (+ AsParallel), чтобы отправить несколько запросов на разные потоки/ядра на каждую из 100 позиций, чтобы сделать вещи очень быстро.

Но прежде чем все это делать, прочитайте ограничения использования пакетных транзакций (100 элементов, 1 раздел на транзакцию,...).

Update:

Поскольку вы не можете использовать транзакции, здесь приведены некоторые другие советы. Посмотрите этот поток MSDN об улучшении производительности при использовании хранилища таблиц. Я написал код, чтобы показать вам разницу:

    private static void SequentialInserts(CloudTableClient client)
    {
        var context = client.GetDataServiceContext();
        Trace.WriteLine("Starting sequential inserts.");

        var stopwatch = new Stopwatch();
        stopwatch.Start();

        for (int i = 0; i < 1000; i++)
        {
            Trace.WriteLine(String.Format("Adding item {0}. Thread ID: {1}", i, Thread.CurrentThread.ManagedThreadId));
            context.AddObject(TABLENAME, new MyEntity()
            {
                Date = DateTime.UtcNow,
                PartitionKey = "Test",
                RowKey = Guid.NewGuid().ToString(),
                Text = String.Format("Item {0} - {1}", i, Guid.NewGuid().ToString())
            });
            context.SaveChanges();
        }

        stopwatch.Stop();
        Trace.WriteLine("Done in: " + stopwatch.Elapsed.ToString());
    }

Итак, при первом запуске я получаю следующий вывод:

Starting sequential inserts.
Adding item 0. Thread ID: 10
Adding item 1. Thread ID: 10
..
Adding item 999. Thread ID: 10
Done in: 00:03:39.9675521

Для добавления 1000 предметов требуется более 3 минут. Теперь я изменил app.config на основе советов на форуме MSDN (максимальное соединение должно быть 12 * число ядер процессора):

  <system.net>
    <settings>
      <servicePointManager expect100Continue="false" useNagleAlgorithm="false"/>
    </settings>
    <connectionManagement>
      <add address = "*" maxconnection = "48" />
    </connectionManagement>
  </system.net>

И после запуска приложения снова я получаю этот вывод:

Starting sequential inserts.
Adding item 0. Thread ID: 10
Adding item 1. Thread ID: 10
..
Adding item 999. Thread ID: 10
Done in: 00:00:18.9342480

От 3 минут до 18 секунд. Какая разница! Но мы можем сделать еще лучше. Вот какой код вставляет все элементы с помощью Partitioner (вставки будут происходить параллельно):

    private static void ParallelInserts(CloudTableClient client)
    {            
        Trace.WriteLine("Starting parallel inserts.");

        var stopwatch = new Stopwatch();
        stopwatch.Start();

        var partitioner = Partitioner.Create(0, 1000, 10);
        var options = new ParallelOptions { MaxDegreeOfParallelism = 8 };

        Parallel.ForEach(partitioner, options, range =>
        {
            var context = client.GetDataServiceContext();
            for (int i = range.Item1; i < range.Item2; i++)
            {
                Trace.WriteLine(String.Format("Adding item {0}. Thread ID: {1}", i, Thread.CurrentThread.ManagedThreadId));
                context.AddObject(TABLENAME, new MyEntity()
                {
                    Date = DateTime.UtcNow,
                    PartitionKey = "Test",
                    RowKey = Guid.NewGuid().ToString(),
                    Text = String.Format("Item {0} - {1}", i, Guid.NewGuid().ToString())
                });
                context.SaveChanges();
            }
        });

        stopwatch.Stop();
        Trace.WriteLine("Done in: " + stopwatch.Elapsed.ToString());
    }

И результат:

Starting parallel inserts.
Adding item 0. Thread ID: 10
Adding item 10. Thread ID: 18
Adding item 999. Thread ID: 16
..
Done in: 00:00:04.6041978

Voila, с 3m39s мы упали до 18 с, и теперь мы даже упали до 4s.