Надежный словарь Service Fabric для параллельного чтения

У меня есть надежный словарь, разделенный на кластер из 7 узлов. [60 разделов]. Я настроил слушателя удаленного взаимодействия, как это.

var settings = new FabricTransportRemotingListenerSettings
        {
            MaxMessageSize = Common.ServiceFabricGlobalConstants.MaxMessageSize,
            MaxConcurrentCalls = 200
        };

        return new[]
        {
            new ServiceReplicaListener((c) => new FabricTransportServiceRemotingListener(c, this, settings))
        };

Я пытаюсь сделать нагрузочный тест, чтобы доказать, что словарь "чтение" производительность не будет снижаться под нагрузкой. У меня есть "читать" из словаря метод, как это..

using (ITransaction tx = this.StateManager.CreateTransaction())
        {
            IAsyncEnumerable<KeyValuePair<PriceKey, Price>> items;
            IAsyncEnumerator<KeyValuePair<PriceKey, Price>> e;

            items = await priceDictionary.CreateEnumerableAsync(tx,
                (item) => item.Id == id, EnumerationMode.Unordered);                
            e = items.GetAsyncEnumerator();

            while (await e.MoveNextAsync(CancellationToken.None))
            {
                var p = new Price(
                    e.Current.Key.Id,
                    e.Current.Key.Version, e.Current.Key.Id, e.Current.Key.Date,
                    e.Current.Value.Source, e.Current.Value.Price, e.Current.Value.Type,
                    e.Current.Value.Status);

                intermediatePrice.TryAdd(new PriceKey(e.Current.Key.Id, e.Current.Key.Version, id, e.Current.Key.Date), p);
            }
        }
return intermediatePrice;

Каждый раздел имеет около 500 000 записей. Каждый "ключ" в словаре составляет около 200 байт, а "значение" - около 600 байт. Когда я вызываю это "чтение" непосредственно из браузера [вызывая REST API, который, в свою очередь, вызывает службу с отслеживанием состояния], это занимает 200 миллисекунд. Если я запускаю тестирование с нагрузкой, скажем, 16 параллельных потоков, работающих на один и тот же раздел и одну и ту же запись, это в среднем занимает около 600 миллисекунд на вызов. Если я увеличу количество параллельных потоков loadtest до 24 или 30, это займет около 1 секунды для каждого вызова. Мой вопрос заключается в том, может ли обслуживаемый матричный словарь обрабатывать параллельные операции "чтения" так же, как SQL-сервер может обрабатывать параллельные параллельные чтения, не влияя на пропускную способность?

2 ответа

Если вы проверите Замечания о надежном словаре метода CreateEnumerableAsync, вы увидите, что он был разработан для одновременной работы, поэтому параллелизм не является проблемой.

Возвращенный перечислитель можно безопасно использовать одновременно с чтением и записью в надежный словарь. Это представляет собой последовательный вид снимка

Проблема в том, что одновременно не означает быстро

Когда вы делаете свой запрос таким образом, он будет:

  1. необходимо сделать снимок коллекции до того, как он начнет ее обрабатывать, иначе вы не сможете писать в нее во время обработки.
  2. Вы должны просмотреть все значения в коллекции, чтобы найти искомый элемент и записать эти значения, прежде чем что-либо возвращать.
  3. Загрузите данные с диска, если еще нет в памяти, в памяти хранятся только ключи, значения сохраняются на диске, когда они не требуются, и могут быть выгружены для освобождения памяти.
  4. Следующие запросы, вероятно, (я не уверен, но я предполагаю) не будут повторно использовать предыдущий, ваша коллекция могла измениться с момента последнего запроса.

Когда у вас есть огромное количество запросов, выполняемых этими способами, многие факторы будут иметь место:

  • Диск: загрузка данных в память,
  • CPU: сравнение значений и планирование потоков
  • Память: сохранение снимка для обработки

Лучший способ работы с Reliable Dictionary - это получение этих значений с помощью ключей, поскольку он точно знает, где хранятся данные для определенного ключа, и не добавляет эти дополнительные издержки для их поиска.

Если вы действительно хотите использовать его таким образом, я бы порекомендовал вам создать его в виде индексной таблицы, в которой данные, индексируемые по id, хранятся в одном словаре, а в другом словаре ключом является искомое значение, а значением является ключ к главный диктон Это было бы намного быстрее.

Исходя из кода, который я вижу, все, что вы читаете, выполняется на первичных репликах - поэтому у вас есть 7 узлов и 60 экземпляров службы, которые обрабатывают запросы. Если я все сделаю правильно, есть 60 реплик, которые обрабатывают запросы.

У вас есть 7 узлов и 60 реплик - поэтому, если мы представим, что они более или менее равномерно распределены между узлами, у нас будет 8 реплик на узел.

Я не уверен насчет физической конфигурации каждого узла, но если мы на мгновение предположим, что каждый узел имеет 4 vCPU, то вы можете себе представить, что когда вы делаете 8 одновременных запросов на одном узле, все эти запросы теперь должны выполняться с использованием 4 vCPU. Эта ситуация заставляет рабочие потоки бороться за ресурсы - если это просто, это значительно замедляет обработку.

Причина, по которой этот эффект так заметен, заключается в том, что вы сканируете IReliableDictionary вместо того, чтобы получать элементы по ключу, используя TryGetValueAsync, как это должно быть.

Вы можете попытаться изменить свой код для использования TryGetValueAsync и разница будет очень заметной.

Другие вопросы по тегам