Run away from EFcore… (2019-2022)

4 декабря 2019 года. Пеппи пишет:

За последние пять дней я посвятил много времени поиску альтернатив EFCore. Это соображение, о котором мы уже давно говорим внутри компании, по следующим причинам:

  • Мы уже отключаем отслеживание изменений EF, поскольку накладные расходы слишком велики и непредсказуемы в зависимости от того, как мы потребляем объекты базы данных.
  • То, как мы обновляем объекты, очень опасно, иногда требуется вызовstore.Refresh()чтобы получить «live» версию, которую можно правильно изменить и обновить.
  • Производительность запуска ядра EF непредсказуема, особенно при большом количестве миграций.
  • Миграцию сложно организовать, и она ограничена серверной частью SQLite.
Процесс преобразования Базы Данных, при котором меняется схема (таблицы и их колонки; вспомогательные вещи такие как индексы и пр.) - и называется миграцией.

Первым шагом было решить, стоит ли придерживаться SQLite.

За:

  • Мы можем использовать все существующие конструкции с минимальными изменениями
  • SQLite испытан и протестирован с точки зрения надежности, многопоточности и производительности.
  • У нас уже есть нативные библиотеки, которые настроены и работают корректно
  • При необходимости мы можем вернуться к ядру EF.

Против:

  • Миграции SQLite очень ограничены
  • Типы данных, предоставляемые SQLite, очень ограничены
  • Библиотеки ORL обычно включают SQLite в последнюю очередь и, как таковые, имеют давние ошибки (столкнулись с этим с ядром EF, требуя ручных обходных путей)

Попытка 1: Dapper + LiteDB

В качестве первоначальной попытки двигаться вперед с учетом простоты я попытался перейти на dapper + LiteDB. LiteDB - это очень легкий (и не сильно поддерживаемый и не используемый) проект, который позволяет быстро тестировать вещи, не меняя слишком сильно наши модели.

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

Все запросы необходимо преобразовать в необработанный SQL.

Хотя это не фатально (в оригинале «While this isn't a killer»), это похоже на шаг назад с точки зрения качества кода, удобочитаемости и ремонтопригодности. Rider будет проверять SQL, чтобы в какой-то степени гарантировать правильность, но это зависит от имени таблицы, присутствующего в запросах (которого нет в моей ветке, чтобы использовать наш существующий макет магазина).

Ошибки SQLite преобладают

Я столкнулся с этой проблемой. У нее многолетняя история, и нет никаких признаков того, что ее можно будет исправить без локальных обходных путей. Это не обязательно вина Dapper - это недостаток в связи между sqlite native / wrappers и сопоставлениями Dapper, который не может быть легко разрешен без обходного пути, который также повлияет на другие механизмы базы данных.

На этом этапе я перестал смотреть в сторону dapper, поскольку это не казалось правильным направлением, и оставил много проблем, которые у нас есть с ядром EF.

Попытка 2: Realm

Realm - это легкая база данных с архитектурой «нулевого копирования». Все запросы к базе данных возвращают live IQueryable<T>экземпляры, и все модели и отношения лениво упорядочиваются при доступе. Это означает очень низкие накладные расходы на память и распределение при извлечении. Это также означает, что вам не нужно указывать отношения для включения при извлечении - у вас есть доступ ко всем из них с бесконечной глубиной рекурсии. Вот некоторые хорошие дальнейшие чтения , чтобы получить до скорости на точках неудобств, которые могут столкнуться с царством. (прим. ред. мне очень трудно понять тут контекст)

Для справки, это моя вторая попытка использовать realm. На этот раз я был более уверен в себе, так как в течение 2018 года я думал о том, как мы можем обойти ограничения, которые на нас влияют:

RealmObject не может быть получен более одного раза

Одним из примеров, требующих изменений, является DatabasedKeyBinding : KeyBinding : RealmObject. Эту проблему довольно легко решить, несколько сгладив наследование . Другой был LegacyScoreInfo, который очень хорошо приведен в порядок , что привело к лучшему качеству кода и более определенному поведению, чем раньше.

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

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

Realm является потокобезопасным, но с оговорками

Каждому потоку нужен свой собственный контекст области. Это то же самое, что и EF, и слоты красиво у нас DatabaseContextFactoryбез проблем. Предостережение заключается в том, что объекты, полученные в одном потоке, не могут быть доступны в любом другом потоке.

Это то, что мы делаем везде. Рассмотрим простой класс:

public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip
{
    private readonly BeatmapInfo beatmap;
    private readonly RulesetInfo ruleset;

    public DifficultyIcon(BeatmapInfo beatmap, RulesetInfo ruleset = null, bool shouldShowTooltip = true)
    {
        this.beatmap = beatmap ?? throw new ArgumentNullException(nameof(beatmap));

        this.ruleset = ruleset ?? beatmap.Ruleset;
    }

    [BackgroundDependencyLoader]
    private void load(OsuColour colours)
    {
        iconContainer.Children = new Drawable[]
        {
            new CircularContainer
            {
                Child = new Box
                {
                    RelativeSizeAxes = Axes.Both,
                    Colour = colours.ForDifficultyRating(beatmap.DifficultyRating),
                    //                                   ^ invalid in realm
                },
            },
            new ConstrainedIconContainer
            {
                Icon = ruleset?.CreateInstance()?.CreateIcon()
                //     ^ invalid in realm
            }
        };
    }
}

Другой пример: мы храним Bindable<BeatmapInfo>на OsuGameуровне. Без особого внимания из-за перекрестного доступа вся игра ломается.

Я много читал о том, как следует использовать realm, где отдается предпочтение модели разработки мобильных приложений, в которой вы:

  • При необходимости выполняете асинхронные запросы, но получаете результаты в основном потоке (асинхронный запрос возможен в области)
  • Используете возвращенный запрос в основном потоке. Потребляйте только то, что вам нужно отображать (например, элементы, видимые на экране), и выполняйте отложенную загрузку из запроса.
  • Накладные расходы на доступ к свойствам области достаточно низки, поэтому производительность не является проблемой и не превышает времени одного кадра.

Для нас мы используем другую модель с нашим BackgroundDependencyLoaderасинхронным потоком. Мы ожидаем, что визуальные элементы или элементы пользовательского интерфейса в некоторых случаях будут загружаться дольше, чем кадр, и не хотим, чтобы их блокировал основной поток пользовательского интерфейса. Это совершенно другой подход, но он не будет меняться в ближайшее время, поскольку это одна из основных функций/парадигм osu-framework.

Итак, у нас есть два варианта использования области в нашей экосистеме:

Отсоединить все объекты от базы данных

Копирование данных из области в версию в памяти полностью решит эту проблему без побочных эффектов, но ограничивает полезность и преимущества в производительности области (и в глазах некоторых людей это откровенное богохульство).

Хотя я согласен, что нам определенно не следует делать это повсюду, поскольку накладные расходы на копирование в память на самом деле довольно велики по сравнению с ядром EF, но у них есть свое место. В моей реализации есть два варианта использования, которые требуют этого:

  • Преобразование карт, которое изменяетBeatmapInfo,но не влияет на версию из базы данных.
  • RulesetStoreи RulesetInfoв целом, который передается и доступен по всей игре как связываемый, но (в настоящее время) никогда не меняется. Накладные расходы при использовании объекта с поддержкой области здесь были бы выше, чем отсоединение/копирование, даже если потоки (не стримы, а именно потоки обработки данных, прим. ред.) не были проблемой.

Я реализовал это с помощью Automapper с помощью следующей реализации:

private static readonly IMapper mapper = new MapperConfiguration(c =>
{
    c.ShouldMapField = fi => false;
    c.ShouldMapProperty = pi => pi.SetMethod != null && pi.SetMethod.IsPublic;

    c.CreateMap<BeatmapDifficulty, BeatmapDifficulty>();
    c.CreateMap<BeatmapInfo, BeatmapInfo>();
    c.CreateMap<BeatmapMetadata, BeatmapMetadata>();
    c.CreateMap<BeatmapSetFileInfo, BeatmapSetFileInfo>();

    c.CreateMap<BeatmapSetInfo, BeatmapSetInfo>()
     .ForMember(s => s.Beatmaps, d => d.MapFrom(s => s.Beatmaps))
     .ForMember(s => s.Files, d => d.MapFrom(s => s.Files))
     .MaxDepth(2);

    c.CreateMap<DatabasedKeyBinding, DatabasedKeyBinding>();
    c.CreateMap<DatabasedSetting, DatabasedSetting>();
    c.CreateMap<FileInfo, FileInfo>();
    c.CreateMap<ScoreFileInfo, ScoreFileInfo>();
    c.CreateMap<SkinInfo, SkinInfo>();
    c.CreateMap<RulesetInfo, RulesetInfo>();
}).CreateMapper();

public static T Detach<T>(this T obj) where T : RealmObject
{
    if (!obj.IsManaged)
        return obj;

    return mapper.Map<T>(obj);
}

Спецификации сопоставления должны быть сделаны для каждой модели, включая элементы коллекции и глубину поиска. Я считаю, что вышеизложенное может быть дополнительно оптимизировано путем дальнейшего уменьшения включений, поскольку я считаю, что есть несколько циклических ссылок, присутствующих на глубине поиска 2.

Повторно извлекать объекты в каждом используемом потоке

Это рекомендуемый подход, и realm фактически предоставляет API для передачи объектов между потоками обработки данных. Хотя API не так хорошо работает для нас, потому что он требует явной реализации при каждом использовании, мы все же можем использовать эту концепцию путем повторной выборки через первичный ключ из каждого потока.

Сделать это вручную было бы довольно сложно, поэтому я выступил с другим предложением...

Введите RealmWrapper

В итоге я создал класс для управления проходящими объектами Realm внутри osu!:

public class RealmWrapper<T> : IEquatable<RealmWrapper<T>>
    where T : RealmObject, IHasPrimaryKey
{
    public string ID { get; }

    private readonly ThreadLocal<T> threadValues;

    public readonly IDatabaseContextFactory ContextFactory;

    public RealmWrapper(T original, IDatabaseContextFactory contextFactory)
    {
        ContextFactory = contextFactory;
        ID = original.ID;

        var originalContext = original.Realm;

        threadValues = new ThreadLocal<T>(() =>
        {
            var context = ContextFactory?.Get();

            if (context == null || originalContext?.IsSameInstance(context) != false)
                return original;

            return (T)context.Find(typeof(T).Name, ID);
        });
    }

    public T Get() => threadValues.Value;

    public RealmWrapper<TChild> WrapChild<TChild>(Func<T, TChild> lookup)
        where TChild : RealmObject, IHasPrimaryKey => new Realm_support_r<TChild>(lookup(Get()), ContextFactory);

    public static implicit operator T(RealmWrapper<T> wrapper)
        => wrapper?.Get().Detach();

    public bool Equals(RealmWrapper<T> other) => other != null && other.ID == ID;
}

Это ядро реализации области и, вероятно, самый важный класс. Это позволяет нам использовать область с минимальными изменениями (на начальном этапе), но также расширяет область применения, чтобы включить наблюдаемые изменения области и оптимизацию с нулевым копированием по мере необходимости. В моей ветке (на момент написания этой статьи, 2019 год) RealmWrapperправильно используется BeatmapCarouselи MusicController, поскольку это сценарии, в которых важна производительность.

Все хранилища данных должны вернуть RealmWrapper<T>(BeatmapManagerуже есть в моей ветке). Потребители, которые хотят извлечь выгоду из объекта, поддерживаемого областью, должны принять a RealmWrapper<T>вместо a T, а затем "развернуть" его локально Get(), используя его.

Что делает эта галочка:

  • Вызов Get()гарантирует, что у вас есть объект с поддержкой области, который можно использовать в текущем потоке, путем повторного поиска с использованием первичного ключа.
  • Неявное приведение к Tпозволяет использовать его во всех существующих случаях использования. Это приведет к принудительному копированию в память Detach(), так что это приведет к накладным расходам.

Дальнейшие улучшения, которые я хотел бы сделать RealmWrapper<T>:

  • Предлагаю переименовать Live<T>или LiveData<T>лучше обозначить, что это такое.
  • ThreadLocalнаверное перебор. Я думаю, что хранения последнего экземпляра достаточно (и это будет более эффективным с точки зрения памяти / производительности).
  • Добавим вывод журнала производительности, когда происходит неявное отключение.
  • Нужно рассмотреть возможность переноса IsSameInstanceпроверки в Get()зависимости от направления управления жизненным циклом экземпляра области.
  • Добавьте поддержку для наблюдения за изменениями. Может быть, сделаем это через IBindableили подобное, если сможем.
  • Добавим методы для запуска операции записи в базовый объект.
  • Добавим неявныйctor,который создает неуправляемое переходное RealmWrapperотверстие WrapAsUnmanaged. Это позволяет классам, предоставленным моделям (особенно тестам), делать это без явного изменения кода для обертывания внутри RealmWrapperкласса.

Перфоманс

Используя вместе два вышеуказанных метода, моя ветка находится в состоянии, пригодном для импорта карт и игры. Некоторые вспомогательные функции, такие как выбор текущего скина в настройках, по-прежнему приводят к сбою игры. Я хотел заставить работать выбор песни и игровой цикл, чтобы я мог протестировать новую структуру и убедиться, что мы не регрессировали, прежде чем продолжить.

Запуск игры

Мы экономим около 1,5 секунды при запуске, которые раньше использовались ядром EF в многоядерной системе. Я считаю, что выгода будет больше при меньшем количестве ядер.

https://user-images.githubusercontent.com/191335/70146836-5e622180-16e6-11ea-8dcf-bd17a7649f78.png — большое разрешение
https://user-images.githubusercontent.com/191335/70146836-5e622180-16e6-11ea-8dcf-bd17a7649f78.png — большое разрешение
https://user-images.githubusercontent.com/191335/70146956-ada85200-16e6-11ea-85f8-4f66bbe460d9.png — оригинал
https://user-images.githubusercontent.com/191335/70146956-ada85200-16e6-11ea-85f8-4f66bbe460d9.png — оригинал

Использование стартовой памяти также ниже, чем у EF:

Run away from EFcore… (2019-2022), image #3

... хотя кажется, что после более длительной игровой сессии все наладилось (требует дальнейшего изучения)

Получение собственности через Realm

Realm предназначен для работы на невращающихся твердых носителях. Чтобы протестировать область при наихудшем возможном сценарии, я провел несколько тестов с интенсивным чтением на USB-накопителе со средней скоростью.

  • TestUnmanagedRealmWithConstruction проверяет накладные расходы на создание неуправляемогоRealmObject
  • TestUnmanagedRealm проверяет накладные расходы на получение свойств из неуправляемого файлаRealmObject.
  • TestManagedRealm проверяет накладные расходы на получение свойств из управляемого объекта RealmObject.
  • TestManagedRealmWithFetch проверяет накладные расходы на выборку с RealmObjectиспользованием первичного ключа (в таблице с 10 000 строками). В основном то, что мы будем делать в каждом потоке при RealmWrapper<T>первом доступе .
  • TestBasic и TestBasicWithConstruction тестируют то же самое, что и неуправляемый тест выше, но без получения RealmObject.

Без внешней нагрузки:

|                             Method |       Mean |     Error |    StdDev |  Gen 0 |  Gen 1 | Gen 2 | Allocated |
|----------------------------------- |-----------:|----------:|----------:|-------:|-------:|------:|----------:|
| TestUnmanagedRealmWithConstruction |   827.3 ns |  16.52 ns |  20.90 ns | 0.0544 | 0.0267 |     - |     288 B |
|                 TestUnmanagedRealm |   637.7 ns |   4.02 ns |   3.56 ns | 0.0496 |      - |     - |     264 B |
|                   TestManagedRealm | 2,098.5 ns |  24.94 ns |  23.33 ns | 0.0839 |      - |     - |     456 B |
|          TestManagedRealmWithFetch | 9,945.9 ns | 143.28 ns | 127.01 ns | 0.1984 | 0.0916 |     - |    1096 B |
|          TestBasicWithConstruction |   378.2 ns |   2.17 ns |   1.93 ns | 0.0472 |      - |     - |     248 B |
|                          TestBasic |   626.3 ns |   3.51 ns |   3.28 ns | 0.0496 |      - |     - |     264 B |

В худшем случае разница в производительности поиска свойств составляет примерно один порядок.

Давайте попробуем еще раз с высокой внешней нагрузкой записи, вызванной dd if=/dev/urandom of=/Volumes/UNTITLED/test-output bs=1024 count=40000000000(100%-ное насыщение ввода-вывода):

|                             Method |        Mean |     Error |    StdDev |  Gen 0 |  Gen 1 | Gen 2 | Allocated |
|----------------------------------- |------------:|----------:|----------:|-------:|-------:|------:|----------:|
| TestUnmanagedRealmWithConstruction |    859.1 ns |  16.77 ns |  22.96 ns | 0.0544 | 0.0267 |     - |     288 B |
|                 TestUnmanagedRealm |    626.3 ns |   9.26 ns |   8.66 ns | 0.0496 |      - |     - |     264 B |
|                   TestManagedRealm |  1,953.2 ns |  39.06 ns |  73.36 ns | 0.0839 |      - |     - |     456 B |
|          TestManagedRealmWithFetch | 10,317.6 ns | 284.44 ns | 338.60 ns | 0.1984 | 0.0916 |     - |    1096 B |
|          TestBasicWithConstruction |    384.3 ns |   2.53 ns |   2.36 ns | 0.0472 |      - |     - |     248 B |
|                          TestBasic |    632.4 ns |  12.45 ns |  11.64 ns | 0.0496 |      - |     - |     264 B |

К счастью, похоже, что либо области, либо файлового кеша ОС достаточно, чтобы гарантировать небольшое отклонение производительности. С другой стороны, запись происходит заметно медленнее (во время настройки теста при вставке строк это заметно), но этого следовало ожидать и пока что выходит за рамки тестирования (запись обычно будет асинхронной, поэтому она не актуальна).

В более оптимальном сценарии вот те же тесты на моем рабочем столе с поддержкой SSD:

|                             Method |        Mean |     Error |    StdDev |  Gen 0 |  Gen 1 | Gen 2 | Allocated |
|----------------------------------- |------------:|----------:|----------:|-------:|-------:|------:|----------:|
| TestUnmanagedRealmWithConstruction |    839.4 ns |  15.75 ns |  16.17 ns | 0.0544 | 0.0267 |     - |     288 B |
|                 TestUnmanagedRealm |    642.6 ns |   8.02 ns |   7.11 ns | 0.0496 |      - |     - |     264 B |
|                   TestManagedRealm |  2,036.5 ns |  37.74 ns |  33.45 ns | 0.0839 |      - |     - |     456 B |
|          TestManagedRealmWithFetch | 10,130.0 ns | 113.70 ns | 100.79 ns | 0.1984 | 0.0916 |     - |    1096 B |
|          TestBasicWithConstruction |    384.5 ns |   2.14 ns |   1.67 ns | 0.0472 |      - |     - |     248 B |
|                          TestBasic |    613.3 ns |   5.80 ns |   5.14 ns | 0.0496 |      - |     - |     264 B |

Можно с уверенностью сказать, что для рабочих нагрузок чтения, по крайней мере, на macOS в моих условиях тестирования, нет проблем с более медленным вводом-выводом. Вероятно, это следует протестировать на Windows для сравнения, но я думаю, что он должен вести себя достаточно разумно, чтобы не создавать проблем.

Я добавлю больше результатов тестов, чтобы узнать, как это повлияет на использование в osu! по мере того, как они становятся доступными для меня, но ниже показано, как может выглядеть наихудший сценарий (это около 10 фильтров, работающих при выборе песни, выполняющих> 100 тыс. сравнений и множество других поисков свойств)

Run away from EFcore… (2019-2022), image #4

Использование дискового пространства

Прямо сейчас realm занимает больше места на диске, чем я ожидал. Изначально после импорта 564 сетов карт размер базы данных области составлял 2 ГБ. Это было несколько смягчено за счет уменьшения количества транзакций во время процесса импорта (196 МБ после этого). Выполнение ручного сжатия базы данных области приводит ее в разумные рамки, примерно на 75% от размера нашей базы данных SQLite:

Run away from EFcore… (2019-2022), image #5

В Интернете существует документация о раздувании (bloat) файлов базы данных, и это потребует дальнейшего изучения и изучения, но, поскольку это может быть решено с помощью ручного сжатия после больших процессов импорта, это не мешает.

Остающиеся задачи

  • (Выполнено) Убедиться, что приведенные выше соображения по производительности находятся в допустимых пределах для нашего использования.
  • (Выполнено) Исправить Realm.Refresh(),чтобы он запускался регулярно в основном потоке, а не запускался каждыеGet()
  • (Выполнено) Обновить ItemAdded/ ItemRemovedудалить расписания
  • (Выполнено) Добавить недостающие PrimaryKey, включая исправление айдиRulesetInfo
  • (Выполнено) Изучить производительность использования longпервичного ключа вместо GUID (теперь realm эффективно поддерживает guid, поэтому мы будем придерживаться этого)
  • (Выполнено) Изучить миграции
  • (Выполнено) Обновить другие хранилища, чтобы использовать RealmWrapper
  • (Выполнено) Исправить оставшиеся проблемы с наследованием (APIBeatmap : BeatmapMetadataи DummyRulesetInfo : RulesetInfo)
  • (Выполнено) При необходимости проверьте сериализацию / инвертирование использования JsonIgnore в JsonProperty на моделях
  • (Выполнено) Узнать, почему асинхронные запросы отсутствуют в API .net
  • Контрольные нагрузки записи
  • Выбрать стратегию миграции EF core -> realm
Статус задач актуален на 4 января 2022 года.

Заключительное слово

Учтите, что нижний текст написан в 2019 году, сейчас положение дел отличается

Я потратил на это около 50 часов (~ 40 из них в realm). Я все еще планирую продолжить исследование Realm, поскольку оно, по крайней мере, объективно кажется лучшим решением, чем ядро EF.

До сих пор наши модели и магазины были довольно хаотичными. В некоторых случаях я даже не уверен, как все работает правильно. Сфера потоковой модели использует силы некоторой степени согласованности и структуры, которые помогут нам лучше определить использование данных.

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

Прогресс до 2021 года по этой задаче

  • Конфигурация клавиш
  • Настройки режимов игры
  • Режимы игры(еще не использованы)
  • Файлы
  • Скины
  • Карты (модели завершены, но еще не использованы)
  • Скоры

9 декабря 2019 года…

  • Все тесты пройдены.
  • Цикл импорта/удаления/обновления карт работает должным образом. Ссылки на файлы тоже.
  • Большая часть кода EF была удалена.

Дела идут хорошо, поэтому я начал изучать проблему с размером файла realm. Проблема здесь в том, что мы держим контексты областей открытыми во всех фоновых потоках на неопределенный срок.

Первой моей мыслью было их убрать. Я добавил отслеживание использования чтения, чтобы мы могли использовать финализатор в DatabaseUsage(новом базовом классе), чтобы закрыть экземпляр. К сожалению, realm не позволяет закрывать открытые экземпляры, кроме как в исходном потоке.

Это не работает RealmWrapper<T>, где мы действительно не можем удалить вручную через using.

RealmWrapper не может быть решением

Во всяком случае, в нынешнем виде. Два пути вперед:

Возврат IDisposableиспользования изRealmWrapper

Get()Метод должен быть сделано, чтобы вернуть одноразовое использование. Его можно удалить после использования, чтобы показать, что базовый контекст области может быть освобожден. Возможно, становится слишком сложным до такой степени, что это, вероятно, не то, что мы ищем.

Отстраняться с большей охотой

В настоящее время я склоняюсь к тому, что мы должны отсоединять / копировать данные из области всякий раз, когда они должны быть сохранены в поле / свойстве или переданы по потоку. Вероятно, это должно быть задано для всех Managerметодов извлечения классов и должно обозначаться Getпрефиксом или аналогичным ему.

Если компонент желает использовать данные из области более эффективным или сложным образом, он будет обращаться к контексту области напрямую или использовать методы, предоставляемые классами-менеджерами с префиксом, объясняющим это. Такое использование не будет безопасным для разных потоков, а обработка будет зависеть от потребителя.

Текущий путь вперед:

  • Отсоединение области сравнения от sqlite в качестве проверки работоспособности. В моих исходных тестах оно выглядело выше, чем ожидалось, поэтому я хотел бы подтвердить, что у нас все в порядке.
  • Рассмотрите возможность создания собственного метода копирования на основе реализации java copyFromRealm. При этом используется уже кэшированный словарь свойств, хранящийся в области, что должно быть более эффективным и обеспечивать лучший охват.
  • Уменьшите использование RealmWrapperклассов вне менеджеров. Это потребует экспериментирования в MusicControllerи в BeatmapCarouselчастности, для более эффективного запроса и использовать данные между потоками.
  • (Выполнено) Последующие действия / отслеживание сообщенной проблемы взаимоблокировки

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

Также я хотел бы отметить, что те же проблемы существуют с EF-core и не относятся к области. До сих пор мы в основном отключали все данные во всех случаях.

1404 views·1 share