LINUX.ORG.RU

Монга, микросервисы, разбивка данных

 , , ,


0

1

Суп лор, мучаюсь в попытках найти золотую середину в монге и микросервисах. Суть такая - где провести границу между nested записью/одним микросервисом и dbref/несколькими сервисами. Через слеш поскольку вещи в каком-то смысле связанные.

Warning: ниже много букв.

Упрощённый пример: веб-альбом. Владелец-юзер, директории, картинки, отзывы на них, метаданные с камеры на них. Картинка без директории не существует, хоть в корне но всё-таки. Мета и отзывы без картинок тоже в воздухе не висят.

Для простоты допустим что если все запихать в одну запись, то её размер заведомо сильно меньше 16мб (лимит монги), и даже меньше 4мб (лимит grpc). Также допустим что отзывы не связаны с другими юзерами.

С одной стороны все элементы связаны one-to-many в одно дерево, так что спокойно ложатся в одну nested запись и один сервис. Но тогда начинается геморрой с projection и тонной ручек на каждый чих, иначе для жирного юзера фронт будет получать мегабайты меты чтоб всего-то получить список директорий. Можно graphql, но тогда геморрой обратный - либо писать dataloader, либо тонна запросов в базу чтоб выдрать мету для N картинок. Ну и это все может очень вкусно лечь если сервисов таки больше одного, потому что им всем придется четко знать какие им нужны поля.

С другой стороны мешать юзера с картинками мягко говоря странно, это вообще разные сущности, и логичнее выглядит dbref и/или два сервиса. Но тогда логично вытащить все тоже отдельно в 5 плоских коллекции/сервисов, что начинает выглядеть все более странно с каждым шагом. Плюс это создаёт много радости когда пытаешься навесить на это чудо транзакции если сервисы разные. А отсутствие в монге нормального join только добавляет счастья. Да и вообще для монги это будет антипаттерн.

Можно скажем вытащить юзера, директорию, картинку с метой, и отзывы. Но тогда вопрос будет почему мета вместе с картинкой, а отзывы отдельно, так как они тоже вроде как без картинки не могут.

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

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

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

p.s. я не делаю веб-альбом, если что, но выглядит все довольно похоже

★★★★★

Последнее исправление: upcFrost (всего исправлений: 1)

Мимо проходил, по диагонали прочитал, и вообще не претендую на какую-то экспертизу, тем более в монге

все элементы связаны one-to-many в одно дерево, так что спокойно ложатся в одну nested запись

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

Можно скажем вытащить юзера, директорию, картинку с метой, и отзывы. Но тогда вопрос будет почему мета вместе с картинкой, а отзывы отдельно, так как они тоже вроде как без картинки не могут.

Монго же это про денормализацию. Раскидывать данные по разным коллекциям и сервисам это можно и нужно, но допустимо и продублировать часть или все данные, чтобы не делать лишних джоинов. То есть структура документов будет определяться тем, как ты будешь с ними работать. Главное не забыть поддержать консистентность при изменениях. Либо выполнять джоины на стороне приложения

фронт будет получать мегабайты меты чтоб всего-то получить список директорий

Backend for frontend, или graphql должны помочь, если фронт не хочет получать тонны лишних данных и слать n+1 запросов

Ну и разрабы монги уже всё расписали https://www.mongodb.com/blog/post/6-rules-of-thumb-for-mongodb-schema-design-part-3

One: favor embedding unless there is a compelling reason not to

Two: needing to access an object on its own is a compelling reason not to embed it

Four: Don’t be afraid of application-level joins

Five: Consider the write/read ratio when denormalizing

Six: As always with MongoDB, how you model your data depends – entirely – on your particular application’s data access patterns

grazor ★★
()

Прочитал всё. Аж глаз задёргался.
Я в таких случаях использую PostgreSQL и кидаю файлы в двоичные поля.

Если проект станет нагруженным, у меня будет кластер PostgreSQL. Ну нафиг эти монги...

А по типу как описано в юности делал альбом в директории с метаданными в именах файлов.

Shadow ★★★★★
()
Последнее исправление: Shadow (всего исправлений: 1)
Ответ на: комментарий от grazor

Сложить всё в один документ можно, но тогда будет неудобно выполнять какие-то операции атомарно и конкурентно

Монго же это про денормализацию

чувствуешь, да? потому и вопрос. Вроде как денормализация, а вроде как неудобно, да и не очень логично.

Backend for frontend, или graphql должны помочь, если фронт не хочет получать тонны лишних данных и слать n+1 запросов

graphql без n+1 это dataloader, который блин надо писать. Плюс если сервисов больше одного - надо протягивать эту логику на внутреннюю шину, которая уже без GraphQL и прочих выборок. Есть конечно FieldMask на grpc, но это не очень удобно.

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от Shadow

не сыпь соль на рану. я уже готов слезать хоть на sqlite. Но все равно скажем вопрос cross-service транзакций это не решит

Есть конечно смищное решение отдавать в ответ некий черный ящик Operation, который по сути пачка команд к базе, которые последний в цепочке сервис просто выполняет не глядя, но это дичь.

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от Shadow

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

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от upcFrost

Distributed transaction? Saga? Transactionless? Без жести не обойдётся, конечно

Но всё это звучит как будто ты занимаешься оверинжинирингом и натягиванием совы на глобус. Начиная от монги и заканчивая транзакциями

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

Транзакцию с разных клиентов по её id монга не может

У тебя одна база для разных сервисов?

grazor ★★
()
Ответ на: комментарий от upcFrost

Смысл в том, что ты сейчас можешь не до конца понимать, как компоненты будут между собой взаимодействовать и друг от друга зависеть. Из-за чего можешь насоздавать сущностей на каждый чих, а потом пытаться их подружить. Тогда как после создания монолита выделить и вынести отдельные компоненты должно быть проще, особенно если ты при разработке закладывал возможность разбиения. В общем всё то, что Фаулер описывает в одноимённой статье

grazor ★★
()
Ответ на: комментарий от grazor

Статья кстати неплоха, спасибо, сохраню

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

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

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от upcFrost

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

IMO монга больше про schemaless. То есть можно тяп-ляп, пока никто не видит под одеялом. Но сиквели вроде уже научились индексировать поля json.

Ты никак ищешь формулу всеобщего всего для шардинга :) ?

Vit ★★★★★
()
Последнее исправление: Vit (всего исправлений: 1)
Ответ на: комментарий от Vit

Ты никак ищешь формулу всеобщего всего для шардинга

Не, больше для конкретного примера когда сущности чётко связаны one to many все дружно и когда они не вылетают за лимиты.

монга больше про schemaless

Ну хз, скорее она просто это позволяет, но не приветствует

Но сиквели вроде уже научились индексировать поля json.

Да, научились. Но это дичь. Видел в крупном продакшене, жсон в мускуле это больно

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от upcFrost

Фик знает. IMO у тебя нет особо пухлого one-to-many. Может тебе хватит data path хранить в виде строки через запятую или массива (смотря что база позволяет)?

Например, на форуме древовидная структура подразделов. Там хватает - общее количество меньше 100-1000.

Альбомы - они вообще плоские (1 уровень вложенности).

У меня нет кучи проектов на монге, всего один (но толстый). Не нашлось под нее задач, где сиквель бы прососал. Просто прикольная штука.

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

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

Vit ★★★★★
()

при чём тут вообще сервисы?

У тебя есть разные коллекции/таблицы, вот в них и пиши.

Различные (микро)сервисы — это не про то, как у тебя данные лежат, а про то, что в твоей компании отдельные команды разделяются и вместо одной огромной толпы в 100 человек возникает 15 команд по 5-7 человек, которые делают что-то, ограниченное наружу API. Получается вроде сильно медленнее, чем если бы все вместе делали монолит, но на практике хаоса меньше, потому что договариваться проще.

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

max_lapshin ★★★★★
()
Ответ на: комментарий от cobold

Оно про graphql. Там по канону если есть поле A[].B.C, то поля C выбираются отдельно для каждого B. Чтоб так не было надо писать dataloader, который будет делать то что ты написал

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от Vit

Может тебе хватит data path хранить в виде строки через запятую или массива

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

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от upcFrost

У тебя есть какая-то конкретная проблема которую ты хочешь решить?

Например, «если юзер сделает директории 100 уровня вложенности, то надо будет 100 запросов»:

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

Данный конкретный пример имеет one-to-many, но все еще нормально едет на нормализованных данных.

Vit ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.