логика такая: у нас есть компонент в системе, который выполняет какую-то работу, попутно сохраняя "снимки" данных в процессе. Условно, начал работу — сделал снимок, произошло какое-то событие в системе — сделал снимок и так далее. Идея ясна. Все они имеют почти идентичные значение полей (например, отличается last_event_time).
У клиента есть желание видеть самые актуальные значения в реальном времени. В базе у нас содержатся "дубликаты", из которых нужно выбрать один самый актуальный. При каждом запросе чистить дубликаты (а Clickhouse это позволяет) слишком дорого. Для меня решением стал запрос:
SELECT
argMax(hex(id), last_event_time)
argMax(field_1, last_event_time),
argMax(field_2, last_event_time),
FROM (
SELECT id, field_1, field_2, field_3, last_event_time -- ...
FROM table_1
WHERE (service_timestamp > toDateTime64('1694165200', 6, 'UTC') AND service_timestamp < toDateTime64('1696957199', 6, 'UTC'))
UNION ALL
SELECT id, field_1, field_2, field_3, last_event_time -- ...
FROM table_2
WHERE (service_timestamp > toDateTime64('1694165200', 6, 'UTC') AND service_timestamp < toDateTime64('1696957199', 6, 'UTC'))
)
GROUP BY (id)
ORDER BY (service_timestamp) ASC
LIMIT 0, 100;
argMax() вернет строку m с максимальным значением n.
m | n
1 | 2
1 | 3
2 | 4
4 | 4
SELECT argMax(m, n) .. GROUP BY (m)
===============================
1 | 3
2 | 4
4 | 4
Однако при большом наборе полей памяти используется невероятно много. Вопрос: как обычно решается такая проблема? Именно Best Practice.
Я не администратор, опыта работы с базами данных у меня мало. Буду признателен за помощь!
argMax вполне good practice. Еще вариант, выбрать максимальное значение timestamp и сджойнить с основной таблицей SELECT * FROM (SELECT * FROM table) as full RIGHT SEMI JOIN (SELECT max(service_timestamp) as service_timestamp, id ) as maxTime USING service_timestamp, id
Не тяжеловатый джойн будет ?
С ограничениями по Where нормально. В чистом виде конечно не пойдет
из БД в ПЗУ такое грести накладно.. Проще на коленке написать отдельную приложуху на любом ЯП или использовать Redis и постоянно обновлять по ключу данные.. Тоже пример "на котиках", но логику вы поймёте: есть N светофоров - id 1, 2, 3,... и в каждый момент времени у вас поступает связка: (id, сигнал(зел/жел/кр), timestamp) -> в редиску перезаписывать для каждого id: 1, кр, 1696912696 3, зел, 1696912697 ... Если у вас ваши поля НЕ огромные, вы там не храните видео и прочее и оборудования - не миллиарды миллиардов, тогда вы в каждый момент времени имеете САМЫЕ просто актуальные данные и достаточно получить их из редиски той же по ключу - это считанные миллисекунды, даже учитывая, если у вас редиска будет далеко.. А как вам советуют с джойнами и прочим - это накладно ОЧЕНЬ - "из пушки по воробьям". Посмотрите в сторону in-memory хранения последнего стейта, чтоб не выгребать.. БД всё же нужна для ретроспективы у вас, а не для последнего стейта..
Приложение, которое генерирует снимки данных, как раз работает с Redis. И занимается планированием — там он к месту. А Clickhouse работает в качестве исторической справки. Тут он используется по назначению: клиенты могут захотеть выгрузить и посмотреть на историю за прошлую неделю, месяц или даже год. Тут Redis не поможет, к сожалению)
Для ретро - запрос в клик, последнее в редиске в качестве хранилища ключ-значение. Видимо, там редиска используется как мессадж бас. А раз редиска уже есть, почему бы не выделить там ещë kvp хранилище? Я не говорил, что вам не нужна ретроспектива, но для последних значений лучшей практикой будет подрубить in-memory kvp хранилище.
Слишком большой объём данных. У нас огромный трафик, и Clickhouse себя зарекомендовал великолепно) С этим помощь не нужна, спасибо). Мне нужна только помощь с оптимизацией запроса)
Сделать mv с AggregatingMT движком и argMaxState не вариант? У нас похожий кейс, нужно выдавать статусы на максимальную дату, по сути текущие статусы. Уникальных записей под полмиллиона, размер - единицы гигабайт за год на диске всего
Я в эту сторону не смотрел, спасибо, обязательно посмотрю. Тут как раз выше кидали статью с MV. А насколько ощутима разница между "оптимизированным запросом" и MV было в вашем случае, если не секрет? Я вот добился неплохой производительности просто добавив LIMIT внутрь подзапроса: SELECT argMax(hex(id), last_event_time) argMax(field_1, last_event_time), argMax(field_2, last_event_time), FROM ( SELECT id, field_1, field_2, field_3, last_event_time -- ... FROM table_1 WHERE (service_timestamp > toDateTime64('1694165200', 6, 'UTC') AND service_timestamp < toDateTime64('1696957199', 6, 'UTC')) LIMIT 0, 100; -- добавил тут UNION ALL SELECT id, field_1, field_2, field_3, last_event_time -- ... FROM table_2 WHERE (service_timestamp > toDateTime64('1694165200', 6, 'UTC') AND service_timestamp < toDateTime64('1696957199', 6, 'UTC')) LIMIT 0, 100; -- добавил тут ) GROUP BY (id, ...) ORDER BY (...) ASC LIMIT 0, 100;
в исходной таблице несколько миллиардов записей, допустим для миллиона уникальных клиентов. При аггрегации все это схлапывается до миллиона получается, по ним и идёт поиск по дата+стейт, либо просто стейт (в нашем случае с датой), даже за год всего 365млн строк, боюсь даже посчитать сколько в сырой будет за это время (храним макс. несколько месяцев оригинал). Так вот по MV поиск по конретному клиенту менее одной секунды, по сырью ддо нескольких минут, в зависимости от нагрузки
Обсуждают сегодня