IllegalArgumentException при заполнении ChronicleMap с высокой изменчивостью размера значения

Некоторое время назад я спросил этот вопрос об использовании ChronicleMap в качестве Map<String,Set<Integer>>. По сути, у нас есть коллекция, в которой среднее значение Set<Integer> может быть равно 400, а максимальная длина — 20 000. В ChronicleMap 2 это приводило к довольно серьезному сбою JVM. Я перешел на ChronicleMap 3.9.1 и теперь начал получать исключение (по крайней мере, это не сбой JVM):

java.lang.IllegalArgumentException: Entry is too large: requires 23045 chucks, 6328 is maximum.
    at net.openhft.chronicle.map.impl.CompiledMapQueryContext.allocReturnCode(CompiledMapQueryContext.java:1760)
    at net.openhft.chronicle.map.impl.CompiledMapQueryContext.allocReturnCodeGuarded(CompiledMapQueryContext.java:120)
    at net.openhft.chronicle.map.impl.CompiledMapQueryContext.alloc(CompiledMapQueryContext.java:3006)
    at net.openhft.chronicle.map.impl.CompiledMapQueryContext.initEntryAndKey(CompiledMapQueryContext.java:3436)
    at net.openhft.chronicle.map.impl.CompiledMapQueryContext.putEntry(CompiledMapQueryContext.java:3891)
    at net.openhft.chronicle.map.impl.CompiledMapQueryContext.doInsert(CompiledMapQueryContext.java:4080)
    at net.openhft.chronicle.map.MapEntryOperations.insert(MapEntryOperations.java:156)
    at net.openhft.chronicle.map.impl.CompiledMapQueryContext.insert(CompiledMapQueryContext.java:4051)
    at net.openhft.chronicle.map.MapMethods.put(MapMethods.java:88)
    at net.openhft.chronicle.map.VanillaChronicleMap.put(VanillaChronicleMap.java:552)

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

Итак, мой вопрос: как лучше всего решить эту проблему? Некоторые подходы, которые я рассматриваю, но все еще не уверен:

  1. Используйте ChronicleMapBuilder.maxChunksPerEntry или ChronicleMapBuilder.actualChunkSize. Тем не менее, как мне детерминистически выяснить, на что они должны быть установлены? Кроме того, это, вероятно, приведет к большой фрагментации и снижению производительности, если установить слишком высокое значение, верно?
  2. Имейте «максимальный размер коллекции» и разделите очень большие коллекции на множество меньших, установив соответствующий ключ. Например, если мой ключ XYZ, который дает Set<Integer> размера 10000, возможно, я мог бы разделить его на 5 ключей XYZ:1, XYZ:2 и т. д. каждый с набором размера 2000. Это похоже на взлом чего-то, что я мог бы просто настроить в Однако ChronicleMap приводит к большому количеству кода, который кажется ненужным. У меня был этот же план, упомянутый и в моем другом вопросе.

Другие мысли/идеи приветствуются!


person Depressio    schedule 13.12.2016    source источник
comment
Сколько записей вы указываете в своей карте через entries()?   -  person leventov    schedule 14.12.2016
comment
По-моему, несколько тысяч (точных цифр передо мной нет). Однако, прежде чем я создам карту, я могу выяснить точную статистику того, что я туда помещаю, потому что у меня уже есть Map<String,Set<Integer>> в памяти. entries() устанавливается в зависимости от размера карты; Я вычисляю средний вход по среднему размеру сетов (ну я использую averageValue() с сетом, ближайшим к среднему размеру, но выше).   -  person Depressio    schedule 14.12.2016


Ответы (1)


Если вы не укажете maxChunksPerEntry() вручную, максимальный размер записи будет ограничен размером уровня сегмента в кусках. Итак, вам нужно увеличить размер уровня сегмента. Первое, что вы можете попробовать сделать, это настроить actualSegments(1), если вы не собираетесь одновременно обращаться к карте из нескольких потоков внутри JVM. У вас есть дополнительный контроль над этими конфигурациями через ChronicleMapBuilder.actualChunkSize(), actualChunksPerSegmentTier() и entriesPerSegment().

По умолчанию ChronicleMapBuilder выбирает размер фрагмента от 1/8 до 1/4 настроенного среднего размера значения. Таким образом, если размер уровня вашего сегмента составляет 6328 фрагментов, ваш сегмент (ы) настроен на содержание около 1000 записей. Если ваш средний размер набора значений имеет 400 элементов, а максимальный — 20 000, разница между средним и максимальным должна быть примерно в 50 раз, но из трассировки стека видно, что одна из ваших записей более чем в 2000 раз больше, чем в среднем. Вероятно, вы что-то не учли.

Также для таких больших значений я предлагаю разработать и использовать более эффективный сериализатор значений памяти, потому что по умолчанию будет генерироваться много мусора. напр. он может использовать примитив IntSet, который реализует Set<Integer> из библиотек fastutil или Koloboke или Koloboke Compile.

Также я предлагаю использовать последнюю доступную сейчас версию, Карта хроник 3.9.1 уже устарела.

person leventov    schedule 14.12.2016
comment
Хорошо, так что я на самом деле провел некоторую отладку и получил реальные цифры для коллекции... было далеко от количества записей. entries() установлено на 18 236, средний размер коллекции — 440, а максимальный — 75 453. Ошибка при попытке добавить коллекцию размером 23 099 (даже не максимальная). Доступ к нему можно получить через несколько потоков в JVM (и других JVM), поэтому actualSegments(1) исключено. Я все еще не уверен, как детерминистически установить actualChunkSize() или любой другой связанный метод. - person Depressio; 15.12.2016
comment
Я отлаживал его дальше. Я облажался. На самом деле я не использую рассчитанную коллекцию среднего размера, а вместо этого использую значение по умолчанию 100 из-за некоторой недопустимой логики в моем коде. Наверное, поэтому что-то не так. Тем не менее, ваш комментарий о том, что вещи не совсем складываются, абсолютно верен... история была неправильной. - person Depressio; 15.12.2016