

Как загружать большие объемы данных? Часть 1.
Привет, друзья! Наша команда более 10 лет занимается вопросами эффективной передачи данных на мобильные устройства. Мы исследовали разные варианты: одни оказались слишком медленными, другие приводили к переполнению памяти на мобильном устройстве.
Хотим рассказать, как мы в команде «Форсайт. Мобильная платформа» сделали синхронизацию больших объемов данных, чтобы это работало, в том числе, на ТСД (терминал сбора данных). Для экономии батареи ТСД специально снабжают слабыми процессорами. Весь подбор инструментов и алгоритмов мы уже апробировали в продукте «Форсайт. Мобильная платформа» (ФМП).
Специфичные условия для мобильного приложения начнем РАЗБИРАТЬ С КРЫШИ, так будет проще подобраться к существу вопроса. В среднем, отличие общедоступных приложений от бизнес-приложений – в объеме потребляемых данных. У бизнес-приложений объем данных значительно больше. Но, как водится, ожидание бизнес-пользователей от приложения точно такие же, как и у всех: приложение должно работать не просто быстро, а моментально. А это значит, что нужно найти особые техники по ускоренной передаче данных. При подборе технологий для транспорта данных нам хотелось получить:
А) Стабильный механизм передачи данных.
Б) Самый быстрый/производительный протокол из возможных.
Поскольку мы производим спецшину по транспорту данных, для нас это означает, что мобильный пользователь будет обращаться в наш инструмент за «чемоданом» данных, и мы должны как можно быстрее передать ему этот «чемодан» целиком. У разработчиков возникает вопрос — насколько большой может быть «чемодан»? Насколько будет нескромен кейс, который нужно будет тащить? За ориентир мы взяли ½ миллиона записей табличных данных.
У вас может возникнуть вопрос: зачем тащить ½ миллиона записей на мобильное устройство?
ВЫ АРХИТЕКТУРНО НЕ ТАК ДЕЛАЕТЕ! Ведь можно подгружать данные по мере необходимости.
Сразу ответим — мы с этого начинали. Подгрузка данных — требует стабильного подключения к сети, это online‑приложение, у которого вскрылись свои проблемы.
70% пользователей сообщили: «нормально работает».
30% пользователей написали: «приложение работает плохо, зависает и не грузит».
В online время реакции приложения на действия пользователя не ровное, у многих пользователей есть выбросы в большую сторону. Увы, чистый online нам не помощник. Нужно обеспечить одинаковую работу у всех пользователей. Такова специфика бизнес‑приложений.
Снова смотрим на архитектуру перегрузки ½ миллиона записей. Будем исследовать, какой протокол даёт приемлемую надёжность и скорость. Для этапа загрузки БОЛЬШИХ данных мы стали применять термин «Первичная синхронизация». Процедура может быть сверхтяжёлая, и всё равно было нужно, чтобы она проходила гладко, не падала из‑за переполнения памяти, какой бы объем ни тащили в мобильное приложение.
Проводя исследования и сравнения различных форматов и протоколов, мы выделили:
Большие объемы способен передать потоковый алгоритм. Он же показал низкую чувствительность к объемам данных.
Максимальную стабильность в разных условиях связи имеет потоковый алгоритм передачи. Так как есть механизм дозагрузки пакета.
За счёт объявления в заголовке структуры данных и далее передачи массива значений через разделитель (без сжатия GZip), мы увидели сопоставимый размер данных, передаваемых по сети.
Что получаем? Давайте сравним и подытожим:
Распространённый подход (простой в реализации JSon) |
Потоковый (делать сложнее) |
Цепочка действий | |
● На сервере: вычитка данных с БД ● На сервере: сериализация в JSon для передачи данных по интернет-каналам. ● Передача по сети Internet ● На телефоне: десериализация (конвертация в команду SQL) ● На телефоне: запись в БД.
|
● На сервере: вычитка данных с БД ● Разделение объема на пакеты ● На сервере: сериализация пакетов для передачи данных по интернет-каналам (Приведение к формату). ● Как первый пакет готов Передача по сети Internet. ● На телефоне: десериализация пакета (конвертация в команду SQL) ● На телефоне: запись в БД. |
Вывод | |
Все шаги идут последовательно, следующий шаг выполняется только после успешного завершения предыдущего. Легко делать, но можно отметить довольно быструю деградацию с ростом объёма. |
Потоковая передача данных — хороший инструмент в сфере технологий для создания масштабируемых приложений. Но будут сложности с реализацией и отладкой. |

Посмотрите на схему в точку «А» на мобильном устройстве. Изначально тут мы с благими намерениями сделали несколько лишних вычислений, которые потом пришлось исправлять.
Перечислим лишние, если вы решите повторять алгоритм:
При разрыве соединения возобновление дозагрузки начинается со следующего байта. Хотели максимально сэкономить трафик. Подсчёт байтов реализовали через запись в промежуточный файл.
Собирали полный пакет ответа в промежуточном кеше, чтобы запустить запись в БД в рамках отдельной операции вставки. Узнали, что запись на файловую систему — относительно медленная операция.
Проводили на устройстве конвертацию формата из JSon в SQL команду.
Лишние действия замедляют процесс синхронизации. Далее покажем, как мы всё это исправили и получили более органичный результат:
Внутри формата JSon на сервере ФМП мы стали формировать записи в нотации SQL языка, чтобы не делать этого на мобильном устройстве, а сразу брать пакет и делать вставку в БД.
Возобновление дозагрузки стали делать по ID номеру строки, внутри пакета, перестали считать номера байтов и стали использовать ID номера строки.
Отказались от промежуточного формирования файла.
Мы рекомендуем избегать записей в промежуточный файл. Если задача записать в БД, проектируйте вставку (insert) без промежуточных вычислений и действий. В этом случае вы, как и мы, приблизитесь к скорости, фактически равной ширине предоставляемого канала.
У ФМП сложилась схема из двух частей:
А) серверная часть умеет отправлять данные в потоке
Б) мобильный фреймворк умеет принимать поток и складывать его в БД на телефоне.
Посмотрите на схему ниже.

Помимо потоковой загрузки данных, фреймворк реализует: защиту данных, загрузку файлов, аутентификацию пользователя, дельта загрузку и многое другое, обещаем про это тоже написать.
С ФМП для подключения потоковой передачи данных - от бизнес источника до приложения - программировать ничего не нужно. На стороне бизнес-системы определите выходные интерфейсы, и далее ФМП сделает всё необходимое автоматически. ФМП создаст необходимые объекты на сервере, а аналогичную работу проделает сам фреймворк в мобильном приложении. Потоковая передача будет работать, даже если бизнес-система не поддерживает поток, эту работу на себя возьмёт ФМП.
Посмотрите листинг кода на Kotlin для Android Фреймворка. Под столь компактный код упакованы:
Аутентификация
Создание БД
Создание структур в БД под хранение объекта данных
Обработка входного потока данных
Чтение данных из БД.
val fmp: FMP = FMP.Builder() // Создать конструктор FMP.
.api(FMP.API.V2) // Указать версию API сервера.
.address("https://HOST") // Адрес сервера платформы.
.environment("ENVIRONMENT") // Среда на сервере.
.project("PROJECT") // Проект внутри среды.
.username("USERNAME") // имя пользователя.
.deviceID("device_id") // Указать ID устройства.
.storage("/path/to/storage") // Указать рабочую директорию.
.build() // Создать FMP.
val auth_password: FMPResult = fmp.user.auth("password") // Аутентификация по паролю.
val resource: FMPResource = fmp.resource
.name("...") // Указать название ресурса на сервере.
.params("...") // Указать параметры ресурса.
.cacheByParams(true) // Использовать кэширование по параметрам.
.delta(true) // Использовать дельту.
.filter(true) // Использовать фильтрацию FMPQuery.
.build() // Получить FMPResource.
val download: FMPResult = resource.download() // Потоковая загрузка данных ресурса и сохранение в БД на телефоне.
val tableData: FMPResult>> = resource.database.select("SELECT * FROM mTable;") //
Получение данных из БД на телефоне.
Потоковая загрузка решает проблемы с большими объёмами и стабильно загружает данные. Но вы хотите ещё быстрее. Мы создали решение, которое отлично подходит для условий, если предоставляется широкий канал и покрытие очень хорошее. Суть ускорения в многопоточности, это позволяет ускорить получение данных.
Взгляните на схему, на ней за единицу времени удвоили производительность, можно запустить ещё несколько параллельных потоков, и тогда будет в три раза быстрее и так далее x4, x5…

Что можем порекомендовать: первым на загрузку ставьте самый большой по объёму справочник. Параллельно запрашивайте цепочку из меньших по объёму.
В схеме с многопотоковой загрузкой часто бывает, что время на загрузку коррелирует с объёмом самого большого справочника.
Ниже приведём
package ru.fsight.fmp.test
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.Assert
import ru.fsight.fmp.FMP
import ru.fsight.fmp.FMPQuery
import ru.fsight.fmp.FMPResource
import ru.fsight.fmp.FMPResult
class SandboxTest {
@Before
fun setUp() {
}
@After
fun cleanUp() {
}
@Test
fun `Initialization FMP`() {
val fmp: FMP = FMP.Builder()
.address("http://mobilefmp.dev.fs.fsight.world")
.environment("env_denis")
.project("proj_test")
.api(FMP.API.V2)
.deviceID("denistest")
.storage("./test")
.username("test")
.build()
}
@Test
fun `User authentication by password`() {
val fmp: FMP = FMP.Builder()
.address("http://mobilefmp.dev.fs.fsight.world")
.environment("env_denis")
.project("proj_test")
.api(FMP.API.V2)
.deviceID("denistest")
.storage("./test")
.username("test")
.build()
val auth: FMPResult = fmp.user.auth("testtest")
}
@Test
fun `Getting the resource data manually`() {
val fmp: FMP = FMP.Builder()
.address("http://mobilefmp.dev.fs.fsight.world")
.environment("env_denis")
.project("proj_test")
.api(FMP.API.V2)
.deviceID("denistest")
.storage("./test")
.username("test")
.debug(false)
.build()
val auth: FMPResult = fmp.user.auth("testtest")
val resource_1: FMPResource = fmp.resource
.name("GetPlanningPeriodsCount")
.params("{\"Count\":500000}") // 1C Basic
.build()
val resource_2: FMPResource = fmp.resource
.name("GetPlanningPeriodsCount_2")
.params("{\"Count\":500000}")
.build()
val resource_3: FMPResource = fmp.resource
.name("GetPlanningPeriodsCount_3")
.params("{\"Count\":500000}")
.build()
val start = System.currentTimeMillis()
var total1 = 0L
var total2 = 0L
var total3 = 0L
val t1 = Thread {
val start1 = System.currentTimeMillis()
val Download_1 = resource_1.download()
total1 = System.currentTimeMillis() - start1
}
val t2 = Thread {
val start2 = System.currentTimeMillis()
val Download_2 = resource_2.download()
total2 = System.currentTimeMillis() - start2
}
val t3 = Thread {
val start3 = System.currentTimeMillis()
val Download_3 = resource_3.download()
total3 = System.currentTimeMillis() - start3
}
// Parallel //параллельная
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
// Seq //последовательная
//t1.start()
//t1.join()
//t2.start()
//t2.join()
//t3.start()
//t3.join()
val total = System.currentTimeMillis() - start
println("Total = ${total}, 1 = ${total1}, 2 = ${total2}, 3 = ${total3}")
}
}
Хотим сказать, что с таким простым транспортом можно сконцентрироваться на создании интерфейса бизнес-приложения.
Затрат времени почти нет, код компактный и понятный.
Не нужно тратить время на апробации подходящих решений для надёжной загрузки больших объёмов.
Стабилизация приложения также занимает меньше времени. А это, как вы знаете, наиболее эмоционально напряжённая часть проекта. Из-за возможных остановок сервиса и перевыпуска релизных сборок.
Как итог — бизнес‑пользователь получает приложение быстрее, а значит дешевле.
Желаем всем добра и высокой скорости передачи данных.
Комментарии (6)
Oleg-Bachurin Автор
29.07.2025 07:57Здравствуйте, пользователю нужны эти данные в его работе. В нашем опыте это справочники, товарная матрица гипермаркета или иерархия оборудования с узлами и агрегатами и операциями. Задачи с большим объёмом данных проходим в бизнес аналитике.
Chapaev2023
29.07.2025 07:57это все в мобильнике? или пользователь тоже мобильный?
даже если так - посредине склада пользователю срочно требуется посмотреть номекулатуру в 100 000 записей о товаре?
и какие выводы можно сделать по такой объмной выборке ?
Oleg-Bachurin Автор
29.07.2025 07:57Здравствуйте, спасибо за цепочку, для некоторых задач подходящая. Если в первой точке, подготовите файл БД сразу пригодный для работы на телефоне. То лишние операции из цепочки можете оптимизировать. Будет быстро.
Oleg-Bachurin Автор
29.07.2025 07:57Спасибо за уточняющие вопросы. В нашем опыте, мобильным пользователям, в рабочую смену, работать со справочными позициями требуется поштучно. В конкретный момент времени при выполнении операции нужна одна запись из объёма. Выводы можно сделать такие, если требуется построить систему где у всех пользователей должно работать мгновенно (время реакции на действия до 1 сек) то данные должны быть с высокой доступностью. Будем рады если пригодиться наш опыт.
Chapaev2023
все равно непонятно зачем вашему пользователю 500 000 записей - он с ними что делает?