Search
Write a publication
Pull to refresh
6
0
Воронская Дарья @seal_android

Android dev, performance engineer in eXpress

Send message

Спасибо за обратную связь)

Мы с созданием транзакций экспериментировали, есть такие опции, у каждой свои плюсы/минусы:

1. передавать через DI инстанс RoomDatabase. Это не очень правильно, и тестировать потом несколько больно. Но это самый очевидный вариант.
2. Создать класс обертку, который проксирует создание транзакции, и передавать уже его. Тестировать проще, и нет возможности через инстанс db что-то ненужное вызвать

class RoomTransactionMaker(private val db: RoomDatabase) {
    suspend fun <T> withTransaction(dbCall: suspend () -> T): R = db.withTransaction(dbCall)
}
  1. Создать в dao пустой метод для транзакций. через аннотацию @Transaction. Не надо ничего провайдить, если для dao реализуется наследование, то можно этот метод положить в базовый класс. Но использование такого метода не в том месте сложнее заметить, чем провайдинг инсанса transactionMaker, и кого-то наличие такого метода может подтолкнуть к использованию транзакций чаще, чем стоило бы.

        @Transaction
        open suspend fun withTransaction(dbCall: suspend () -> Unit) {
            dbCall()
        }


Спасибо за такую обширную обратную связь)

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

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

Для нас в какой-то момент возникла потребность сформировать список инструментов, которые мы используем, чтобы любой из наших разработчиков мог без лишних раздумий осознавая плюсы и минусы того или иного решения оптимизировать код, не опираясь на "а вот в том кейсе у нас прям быстро всё стало", ведь в другом месте это может не сработать. Нам нужно было понять:
1. Какие могут быть проблемы от внедрения решений
2. Сколько примерно времени может выполняться "наихудший" вариант
3. На какое примерное улучшение мы можем расчитывать при совершении оптимизации
4. Как зависит время выполнения функций в зависимости от входных данных (например, от количества полей)

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

Хитрые случаи с индексами не рассматривала, спасибо за наводку, будет куда поглядеть в будущем. 🤗

К вопросу о замерах.
Я вижу два способа корректно сравнить скорости двух запросов:
1. Сравнивать на холодную - замерять будет дольше, и сами замеры будут включать в себя время по чтению данных из диска.
2. Предварительно загнать данные в кеш (прогреть код), и тогда замеры соответствено будут включать в себя чтение данных только из кеша, а последовательное выполнение не будет сильно влиять на разницу.

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


К вопросу об отправке.

После нажатия на "отправить" у юзера стартуют два "таймера", улучшать нужно оба
1. Когда из поля ввода пропадет текст, и появится в виде сообщения в чате
2. Когда сообщение будет окончательно отправлено. Визуально во многих мессенджерах выглядит как смена на сообщении иконки "часы" на "галочку" и включает в себя время на взаимодействие с сервером.

1. Время первого процесса нужно снизить до состояния "ощущается мгновенно". Мы его снизили до желаемого уровня в основном за счёт оптимизации БД.
2. Ко времени второго процесса требования не такие жесткие. Его мы снизили до медианного времени в 320 мс исключительно оптимизацией БД за счет уменьшения времени между нажатием на "отправить" и отправлением запроса на сервер (между этими событиями много операций с БД). Возможно здесь мы еще будем делать улучшения. 🤔

Сообщения в мессенджере, где в одном чате могут сидеть сотни/тысячи человек, подвержена быстрым изменениям, некоторые из которых отменяют предыдущие. Сообщение мы получили, его тут же кто-то отредактировал/создал тред/добавил-отменил реакцию/атач загрузился на 0.1% - это всё может произойти буквально одновременно, и всё это ведёт к изменению ui чата, может повлиять на еще какую-то логику - и это всё довольно сложно поддерживать, если опираться на разные источники информации об изменениях.
Чтобы избежать проблем с подобными сущностями на разных уровнях, мы придерживаемся концепции "единственный источник правды". Если мы должны что-то отобразить, получить какую-то информацию - то идти мы должны непосредственно к этому источнику. От многих багов это решение нас спасло
Для нас источник правды - БД. Поэтому решение что-то обновить в обход БД - выходит за рамки архитектуры. Обновлять другие сообщения не через БД - с учетом высокой реактивности - для нас стрелять себе в ногу.

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

Спасибо за обратную связь)

Соглашусь с Вашим посылом. Логику внутри SQL запроса в мобильном приложении считаю оправданной в 2х случаях:

1. Если эта логика не содержит контекста. Например, в запросе select exists (select 1 from table_name where id = :id)) нет контекста, нет нюансов, которые надо помнить, нет бизнес логики как таковой. Есть только сущности, поля, которые описаны в дб, и стандартные функции, которые SQL предоставляет. Если у DAO есть такая функция, она всё еще не знает, зачем она используется. Другое дело, если мы будем писать функцию, которая будет определять, например, существует ли чат, у которого тип = канал, и есть непрочитанные сообщения. Если мы всё это условие опишем в SQL запросе, то да - размывание бизнес логики, сложность в тестировании, поддержки - всё это будет.
2. Если заранее ясно, что в конкретном месте нам важен хороший перформанс, который можно получить сложным запросом, и последствия размывания логики мы осознанно готовы принимать. У каждого проекта будут свои правила по тому, в какой момент считать, что стоит перенести логику в запрос.

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity

Specialization

Mobile Application Developer