Как стать автором
Обновить

Комментарии 40

34 лайка за пост, который я сам не до конца понимаю ибо мимо крокодил, фронт-энд не знаю.

В этой матрице где-то сбой...

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

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

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

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

Простыня при недогрузе будет грузится заново после обновления страницы и при медленном и нестабильном соединении обрываться в разных местах. А когда json поделен на несколько небольших частей будут загружаться только те которые не попали в кэш браузера.

понятно, что мы о разных сценариях работы. я имел в виду не критику такого подхода, а более хороший вариант - перейти на что-нибудь, не вида json/xml.

JSONL например

Очень сложно.

Просто отправляйте отдельные элементы по SSE и всё тут. 100% поддержка чем угодно, скорость, никакой магии и выдумок.

Все не читал, но в некоторых языках есть инструменты именно что потокового чтения (как раз чтобы при загрузке гигабайтных json не упасть по ограничению памяти и не дожидаться окончательного чтения по сети), полностью аналогичные чтению XML. При этом объекты получить можно на любом уровне (например если в корне - массив с миллиародом элементов, то читать по одному объекту массива, для более сложных случаев - спускаться до нужного уровня, по пути обрабатывая данные). В JS тоже, наверняка, есть библиотеки для этого, а не только JSON.parse.

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

Можно перейти на toml - передавай узлы один за другим в любом порядке.

Тогда уж на newline-separated JSON.

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

А тут без вариантов. В любом случае, прогрессивность на совести автора протокола, и ему решать когда можно начинать обработку частичных данных, а когда ждать ещё что-то.

Идея, разумеется, не безнадежная, хотя не так элегантна как с jpeg.
C картинками она хороша, потому что картинки сравнительно большие, но однородные.
Простой перестановкой мы получаем прогрессивность фактически забесплатно - просто передаем не первую строку, а каждый 64-й (16-й) пиксель.
Так мы получаем дешевую возможность создать превью.

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

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

Дополню: мне идея из статьи сразу напомнила про ресурсы rest. Т е вместо передачи всего документа, в под объекте post может быть ссылка на его rest-ресурс. Это же уже работает и нет необходимости изобретать. Клиент может кэшировать одинаковые ресурсы, что бы не делать одинаковые запросы. И всë! Чуть доработать, что бы при чтении page.getPost() возвращался промис. На стороне сервера доработать, что бы разбивку объекта на ресурсы делать аннотациями. (Ну например если речь о Java, то это не сложно. Возможно что такое готовое уже есть)

Там вот ниже пишут, что данные могут измениться, пока их получают. В случае с rest, можно легко сделать на сервере снимок, который он нужное время держит в кэше и ссылки ведут на этот снимок. Если приходит запрос именно к этому ресурсу, то именно эти данные отдаются. Либо если оказалось, что они устарели, сервер отвечает что-нибудь типа 404 и клиент инвалидирует всë дерево, сообщая эксепшеном или событием приложению. Опять же неплохо бы этим управлять аннотациями (или даже в апи какие-то флаги предусмотреть, типа "подержи копию 30 секунд для меня " )

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

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

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

"Создай проблему, реши проблему, живи на разницу."
Современный frontend является "прекрасной" реализацией этого выражения.

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

Я понимаю, что это очень интересно всё, конечно, но почему бы не передавать построчно серию JSON-ов, как в ClickHouse сделали формат JSONEachRow? https://clickhouse.com/docs/interfaces/formats/JSONEachRow

Пример:

{"num":42,"str":"hello","arr":[0,1]}
{"num":43,"str":"hello","arr":[0,1,2]}
{"num":44,"str":"hello","arr":[0,1,2,3]}

Например, первой строкой отправили хедер, второй — футер, всеми последующими — отдельные посты (всё, что требует пагинации)

Это же json lines!

Да, он и есть)

Моя профдеформация мне говорит, что для эксплуатации такой реализации можно в ещё не загруженном элементе ссылаться на уже загруженный, т.е. произвести зацикливание ссылок.

Это заявлено в тексте как фича.

Может я просмотрел, но там наоборот: сначала грузится ссылка, а потом её содержимое.

В смысле, заявлена как фича сама возможность описывать циклы.

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

Protobuf же

Все равно же не провалидировать, пока все не скачается?

но мы не можем определить, поступят ли новые comment или этот был последним.

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

Однако если мы передаём JSON прогрессивно, то можем решить вынести его

До первой гонки потоков. Что если пока мы эти данные выкачивались они уже изменились и из трех вынесенных элементов один уже поменялся? А веб это реалтайм и оно из коробки всегда имеет на это шанс.

например, чтобы по умолчанию объекты встраивались (ради компактности)

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

Если вам знакомы системы, решающие эту задачу иначе, то напишите мне о них!

Вот вам способ без $1 и прочих меток. Передаете данные JSON стримом где первым чанком заходят верхнеуровневые данные, дальше чанками заходят вложенные. При этом вы не ставите стабы из $, а просто подразумеваете что второй чанк будет вот с этим, третий с этим и прочее. И начальный скелет

{ header: "$1", post: "$2", footer: "$3"}

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

{ comments: [.....]}

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

Возьмите jsonl и в начале передавайте наиболее важные данные, а далее подсасывайте всё остальное. Можно даже кодиповать идентификатор узла, например:

{ node: header}

{content: header text}

{ block: comments }

{ id:1, text: comment text }

И можно, например, генерировать события, что начался такой то блок, а получатель уже будет отрисовывать части интерфейса. Но если честно, за такое олимпиадное программпование нужно бить по рукам. Единственное где это может пригодиться, это передача данных в файле, чтобы при парсинге не забивать память, храня всё дерево. Ну и про gzip не стоит забывать, который вам сожмет json в 5-10 раз, уменьшив время передвчи по сети.

Но тут конечно от задачи зависит, возможно комменты лучше подсосать отдельным запросом

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

JSON как правильно не особо большой, дабы в сфере веба бороться за десяток, пусть даже сотню мс. Если JSON большой, что заметно пользователю - то это вопрос проектирования уже. А если вопрос в принципиальной эффективности то современный веб в принципе крайне далек от этого и тут вагон и маленькая тележка потенциала для улучшений. На худой конец при наличии чётких контрактов можно сделать предположение, что схема всегда правильна и парсить налету как есть. Что конечно не слишком далеко уже от изобретения своего нового протокола и вопроса, стоит ли оно усилий

Пагинация комментов (или какая там будет длинная однородная структура?..) и отдельные запросы на особо тяжёлые структуры в моменты доматывания до них. И всё, вместо одного гигантского json у нас несколько небольших.

Гораздо проще разбить JSON на части, и загружать по мере просмотра страницы. Может человек вообще не долистает до комментариев, и их можно будет не загружать.

кажется миллениалы изобрели сжатие сортированных текстов методом сокращения общего (одинакового) начала строк.

...а не, не изхобрели. Они еще не знают про json path

ну ладно, когда узнают, тогда и изобретут...

Какое сжатие, брага ради, вы о чем, тут за ноу нау идёт текстовый файл с префиксами длины строки, чтобы посимвольно перевод строки не искать … а вы про сжатие заголовков ….

Да, но... почему не искать? зачем? В чем радость?

Нет, я понимаю, в "старых" языках типа Си и Паскаля упоротые по скорости парсеры будут, один раз загрузив блоб с сети, просо расставлять указатели по памяти, для каждой ноды (в данном случае нода - строка текстовика, но в принципе и любые структуры), сам блоб оставляя без изменений, и ничего кроме указателей не храня в объектах элементов. Тут вероятно да, кадры данных с явно указанной длинной - полезны.

А уж если они однажды type-name-length-value или tag-length-value изобретут, вообще счастье настанет.

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

Спрошу как системный аналитик. Какую проблему на самом деле решает такая архитектура? Плохая связь? Так её ещё больше нагружаете за счет количества догрузок. Ускорение рендеринга страниц? Так отдаёте некачественный контент и заставляете прыгать страницу при подгрузке. Улучшение доставляемости? Сомнительно, выше писали что просто не нужно гигабайтные файлы отправлять. Плюс услоднение разработки, повышенный контрооь консистентности данных, повышенный расход проца и батареи телефона на доп обработки...

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации