MegaByte & OneByOne
Привет, я тут думал над тем, как выбирать между реляционной базой данных и графовой, когда работаешь с сильно связанными данными в большом приложении. Что думаешь?
Если ты гонишься за миллиардами связей, то граф сияет своей архитектурой, ориентированной на ребра – обходы очень быстрые, и ты избегаешь усталости от соединений. Реляционные таблицы все еще выигрывают, когда тебе нужны строгие ACID-транзакции, зрелые инструменты и сложные аналитические запросы по широким схемам. В большом приложении комбинировать их может быть полезно: сохраняй основную OLTP в реляционной базе данных и перенеси часть, где много связей, в граф для быстрого поиска. Основной компромисс – это производительность против привычности и поддержки инструментами.
Похоже на типичный случай: выбираешь подходящий инструмент для задачи – оставляешь основную деловую логику в чём-то надёжном, а графовая база данных обрабатывает сложные связи. Главное – обеспечить чистоту передачи данных между ними и не потерять требования к консистентности. Что-то конкретное сейчас мучает?
Я, если честно, сейчас разрабатываю систему рекомендаций для музыкального стриминга. У каждого пользователя есть небольшая сеть лайков, подписок и шарингов плейлистов, и все это хранится в реляционной базе данных – для биллинга и профилей. Проблема в том, что алгоритм рекомендаций хочет анализировать дву- или трехшаговые связи в реальном времени – типа "друзья друзей, которые слушали одни и те же треки". Графовая база данных сделала бы это за миллисекунды, но чтобы синхронизировать каждое изменение профиля обратно в реляционную базу, нужно немного «склеивающего» кода. Я экспериментирую с event-driven пайплайном, который пишет поток событий "добавлена связь" в Kafka, потом подает их в граф, и, наконец, использует материализованные представления, чтобы реляционная база данных была достаточно актуальной для биллинга. Сложность в том, чтобы поддерживать целостность этого потока без замедления регистрации новых пользователей. Это компромисс между eventual consistency и задержкой, но для большинства пользователей небольшая задержка в кэше рекомендаций вполне приемлема.
Звучит как типичная проблема с двумя системами – нужно, чтобы биллинговая система работала нормально, но при этом рекомендательная работала достаточно быстро. Если узкое место в пайплайне событий, можно вынести запись в граф в отдельный микросервис, который будет делать пакетные обновления, а потом отправлять флаг “готово” на реляционную сторону, когда граф синхронизирован. Так поток регистрации останется быстрым, а рекомендации смогут позволить себе небольшую задержку. Насколько в среднем растёт граф?
Сейчас граф показывает примерно 8 миллионов пользователей и 120 миллионов связей, и каждую неделю, с появлением новых регистраций и распространением плейлистов, он вырастает процентов на 4-5. Пакетный сервис собирает данные, обновляет граф целиком и отмечает изменения в разделе взаимоотношений, если они происходят в течение 10-секундного интервала – это позволяет почти в реальном времени отслеживать биллинг, а для кэша рекомендаций небольшая задержка вполне допустима.
Наверное, вот это окно – оптимальный вариант. Достаточно быстрое, чтобы биллинг оставался в порядке, и достаточно медленное, чтобы пакетные обновления графов обрабатывались без проблем. Только не забудь добавить лёгкую метку "последнее обновление" для каждого пользователя в реляционную БД, чтобы кэш мог обновляться по требованию, а не ждать следующей партии. Так система останется лёгкой, при этом рекомендательный движок сможет выполнять обходы на два шага почти в реальном времени.