Подход с мониторингом скриптами текстовых файлов удобен только в простейших случаях, когда, например, проблемы выявляются наличием в журнальном файле строк «ERROR», «FAILURE», «SEVERE» и т.п. Для мониторинга больших файлов удобно использовать систему Zabbix, где Zabbix Agent (active) будет считывать только новые данные и с определённой периодичностью отправлять их на сервер.
До определённого момента средств Zabbix было достаточно, но для мониторинга бизнес-процессов, анализа метрик в рамках SLA, зачастую появляются более сложные задачи, например:
— определение количества операций, прошедших через систему за промежуток времени;
— определение времени обработки различных операций;
— выявление процентного количество ошибок, исключений в разбивке по операциям и бизнес-данным;
— сбор статистики по немонетарным транзакциям, анализ уровня сервиса.
Обычно такие задачи – головная боль администраторов систем, которым приходится искать изощрённые способы сбора, агрегации и представления таких данных, если система изначально не была спроектирована с расчётом их сбора.
Для решения этих задач нам пришлось искать более гибкое и универсальное решение.
Первоначально пришлось научить приложение с помощью java-драйвера писать логи структурированными документами в MongoDB. Модель данных была специально спроектирована для хранения унифицированных документов в JSON-формате.
Преимущества такого метода – отсутствие задержки в получении новых данных (как в случае apache hadoop), наличие репликации и при необходимости сегментирования, удобные средства выборки и анализа данных с помощью API MongoDB, mapReduce и функций на языке JavaScript, но обо всём по порядку.
Пример
Допустим приложение пишет в БД документы вида:
{
_id: ObjectId
id: string
operation: string
time: ISODate
data: {
info: string
result: string
message: string
}
array: [a1, a2, ... ,aN]
}
Имея коллекцию таких документов, с помощью простейшего запроса можем понять, сколько операций test приложение выполнило за последние 5 минут:
db.test.count({ "operation": "test", "time": {$gte: new Date(new Date() - 1000*60*5)} })
Такой запрос легко «завернуть» в shell-скрипт, который будет подключиться к mongo, а его результат выводить в терминал. Этот скрипт впоследствии можно подключить к системе мониторинга, что мы и сделали, получив наглядный график и «навесив» триггеров на неадекватные значения.
Чтобы запрос выполнялся быстро, достаточно построить единственный индекс
db.test.ensureIndex( {time: 1}, {background: true} )
. Тогда MongoDB будет просматривать лишь данные за последние 5 минут, а не все документы коллекции. Можно сразу добавить ещё несколько индексов, но если их будет слишком много, при каждой операции insert
бинарные деревья, соответствующие им, будут дописываться, что создаёт дополнительную нагрузку. И в какой-то момент индексы могут не поместиться в оперативную память, тогда чтение будет происходить с диска, что значительно замедлит доступ к данным.Работа с датами
Поле с временем (в данном случае time) я использовал чаще всего. В начале работы с оболочкой MongoDB найти информацию по составлению простейших запросов не составит труда, а вот с запросами по дате были проблемы. Опишу несколько способов, как выделить документы в указанном временном промежутке.
Если приложение при операции insert в поле time вставляет
new Date()
, дата будет записана в ISODate-формате. Введя в консоль оболочки mongo команду new Date()
в выводе будет текущая дата в формате ISODate("YYYY-MM-DDThh:mm:ss[.sss]Z")
— эту строку можно взять и подставить в запрос вида:db.test.find({ time: {$gte: ISODate("YYYY-MM-DDThh:mm:ss[.sss]"), $lte: ISODate("YYYY-MM-DDThh:mm:ss[.sss]")} })
, где $gte
— больше или равно, $lte
— меньше или равно, [.sss]
— количество милисекунд.Также можно напрямую задать дату через
new Date()
:db.test.find({ time: {$gte: new Date(YYYY, MM, DD, hh, mm, ss, sss) } })
, где отсчёт месяцев (MM) начинается с 0.или
db.test.find({ time: {$gte: new Date( new Date() -1000*300 ) } })
— выводим документы за последние 5 минут. Вместо выражения
"-1000*300"
можно подставить любое время в милисекундах. Также перед запросом можно заранее определить переменные с датами:var today = new Date();
var yesterday = new Date();
yesterday.setDate(today.getDate() - 1);
db.test.find( {"time": {$gte : yesterday, $lt : today} } );
В некоторых случаях удобно использовать POSIX, например:
for (var i = today.getTime(); i < yesterday.getTime(); i=i+300*1000) {var b=0; b = db.test.find({ "time": { $gte: new Date(i), $lte: new Date(i+300*1000) } // с помощью getTime мы получаем POSIX время в миллисекундах
}).count(); // .count() позволяет подсчитать количество документов
time=new Date(i); // задаем дату текущей итерации i
print(b+"; "+time.toTimeString().split(' ')[0]) } // выводим количество операций (b) + время time в формате timeString, с помощью split(' ')[0] из этой строки выводим только время.
После выполнения данного запроса мы получим в консоль общее количество документов, прошедших через систему, в разбивке по 5-минутным интервалам за сутки.
Метод forEach
Поскольку JSON-подобный вывод агрегации часто неудобен, выручает метод
.forEach()
, который применяется к курсору и способен модифицировать документ произвольным способом с помощью javascript.Например, необходимо вывести список
id
за указанный промежуток времени (возьмём последние 5 минут):db.test.aggregate({$match:{time:{$gte:new Date(new Date()-1000*300)}}}).forEach(
function(doc) {
print( doc.id )
}
)
Вместо
aggregate
в данном случае можно использовать find
или distinct
— главное, чтобы на входе forEach()
был массив. Поскольку формат вывода агрегации несколько изменялся от версии к версии, в версии до 2.6, например, надо использовать aggregate({...},...,{...}).result.forEach
, так как вывод имеет формат "result":[{...},...,{...}]
.Ещё один пример: необходимо выяснить, какой финальный статус у каждого
id
и выгрузить в таблицу.db.cbh.aggregate(
{$match: {time: {$gte: new Date(new Date()-1000*300)}}}, // ограничиваем изначальный набор данных
{$group: { _id: "$id", // группируем по id
"count": {$sum:1}, // подсчитываем количество документов с этим id
"result": {$push:"$data.result"} // вставляем результат в массив
} },
{$match: { "count": 4 }}, // отбираем id в которых 4 записи
{$match: { "result": { $ne:[] }}} // исключаем пустой result
).result.forEach(function(doc) {print( doc._id+", "+doc.result )} )
Такой запрос выведет в консоль результат вида «
id, data.result
», который можно импортировать в excel или любую реляционную СУБД.Функции
С помощью функций удобно подсчитывать метрики, сложные запросы мониторинга, формировать отчёты. Приведу пример простой функции подсчёта среднего времени выполнения операции.
function avgDur(operation, period) {
var i=0;
var sum=0;
var avg=0;
db.test.aggregate(
{ $match : { "time" : { $gte : new Date(new Date - period*1000) } } },
{ $group : {_id: "$id",
"operation":{$addToSet : "$operation"},
"time":{$push : "$time"},
"count":{$sum: 1}
} },
{ $match : {"operation": operation,
"count":4
},
{ $project : {_id:0, "time":1}}
).result.forEach(function(op) {
dur=op.time[3]-op.time[0];
sum=sum+dur;
i=i+1;
});
avg=sum/i;
print(avg/1000); // выводим среднее время в секундах
}
Прежде чем сохранять функцию, лучше запустить её в консоли,
avgDur(test,300)
и проверить её работу. Далее сохраняем её:db.system.js.save(
{
_id: "avgDur",
value : function(operation, period) {
...
}
}
)
После чего запускаем
db.loadServerScripts();
и вызываем фукнцию avgDur(test,300)
. Если сохранить невалидную функцию, при
db.loadServerScripts()
можем получить ошибку и не сможем обратиться к другим функциям, поэтому тщательно проверяем перед сохранением.Подводные камни
Первое, с чем я столкнулся на сервере без репликации – трудности с освобождением места на диске. MongoDB пишет документы на диск подряд, и, если удалить часть коллекции вручную командой
db.test.remove({...})
, то место на диске не освободится, т.к. это бы вызвало сильную фрагментацию. Во избежании этого MongoDB оставляет все документы на своих местах, а в пропусках добавляет ссылки. Тогда чтобы «сжать коллекцию», потребуется выполнить db.repairDatabase()
, но команда потребует столько же места на диске, сколько занимает БД, т.к. вначале база копируется на новое место, и только потом удаляются файлы старой базы. Чтобы избежать подобных проблем, я нашёл несколько решений, которые можно комбинировать:
1. Репликация. При наличии дублирующего сервера, всегда можно остановить slave-реплику, выполнить
repairDatabase()
и запустить сервер обратно. Ещё лучше настроить TTL (time-to-live) индекс, где документы будут сами удаляться после определённого времени жизни, например, через 30 дней. Но в этом случае всё равно придётся периодически делать repairDatabase()
. 2. Создавать коллекцию сразу ограниченного объёма Capped collection. Когда коллекция достигнет ограничения, самые старые документы начнут перезаписываться самыми новыми.
3. Посмотреть, сколько места на диске будет занимать коллекция после 30 дней записи логов, после чего выполнить convertToCapped этой коллекции и задать ограничение с запасом.
4. Записывать временные коллекции в отдельную БД, т.к. при выполнении
db.dropDatabase()
место на диске будет гарантированно освобождено. А вот db.collectioName.drop ()
, к сожалению, место на диске не освобождает. Данные просто помечаются как недоступные.Начиная с версии 2.6 стратегия преаллоцирования немного изменилась, по умолчанию для коллекции применяется опция usePowerOf2Sizes. Освобожденное в результате удаления документов дисковое пространство стало использоваться рациональнее, однако, надёжнее изначально определить размер коллекции.
Агрегация больших объёмов данных
Агрегация — не самый удачный инструмент для обработки огромных объемов данных: 100 миллионов документов, таких как в примере выше, агрегировать будет проблематично.
Первое ограничение, с которым придётся столкнуться — размер результирующего документа не должен превышать 16 Мб. Однако начиная с версии 2.6 результат агрегации передаётся в качестве курсора, и с помощью метода
cursor.next()
, применяемого к курсору, можно выделить нужные данные последовательно. Также есть ограничение в 64 Мб на использование при обработке буфера, который может переполниться при таком количестве документов. Начиная с версии 2.6 параметр агрегации
{allowDisckUse:true}
помогает этого избежать.Большие объёмы данных обрабатывать лучше всего mapReduсe, где используется многопоточная обработка, а также распределение нагрузки между серверами в сегментированном кластере.
Но это всё мелочи. Настоящие муки начнутся, если схема данных не подходит для mongodb, когда появятся задачи, требующие связывания данных из нескольких коллекций или рекурсивного подхода в поиске документов и агрегации.
Приведу пример:
Допустим, нужно вычислить среднее время выполнения всех успешных операций. Но если подход логирования транзакций подразумевает запись документов типа «запрос» и «ответ», и таких документов в рамках одной транзакции будет несколько, то в нашей схеме поле «data.result» с результатом выполнения транзакции появится только в последнем документе серии.
Так как же в этом случае вычислить время? Если в агрегации в блоке
$match
искать все документы с "data.result":"SUCCESS"
, то в выборку попадут только последние документы внутри каждого id
. Парадигма mapReduce здесь тоже не поможет, т.к. на стадии map MongoDB проходит по коллекции только 1 раз.Можно пройти по коллекции и собрать массив со всеми нужными
id
и выполнить агрегацию, подставив в $match
этот массив:ids=[];
var monthBegin = new Date(new Date().getFullYear(),new Date().getMonth()-1,1,0,0,0,0)
var monthEnd = new Date(new Date().getFullYear(),new Date().getMonth(),1,0,0,0,0)
// Здесь мы задаём дату напрямую через new Date(), где вместо YYYY подставляем текущий год, используя выражение new Date().getFullYear(), а вместо MM выражение new Date().getMonth() и new Date().getMonth()-1 для текущего и предыдущего месяца.
db.test.find({time:{$gte:monthBegin, $lt:monthEnd}, "operation":"test", "data.result":"SUCCESS"}, {"_id":0, "id":1}
).forEach(function(op)
{
ids.push(op.id) // заполняем массив id для каждого найденного документа
});
db.test.aggregate(
{$match:{"id":{$in:ids}}}, // подставляем массив с id в аггрегацию
{...}
)
Однако стоит понимать, что этот массив будет всё это время находиться в памяти, и, если в нём будет несколько миллионов элементов, есть высокая вероятность получить out of memory. Чтобы избежать проблем c выделением памяти, можно записать «подготовленные данные» в новую коллекцию:
db.getSiblingDB("testDb").test.find({
"time" : { $gte : new Date(monthBegin.getTime()), $lte : new Date(monthEnd.getTime()) },
"operation" : "test",
"data.result" : "SUCCESS"
}).addOption(DBQuery.Option.noTimeout).forEach( // добавляем опцию noTimeout для курсора
function(doc) {
db.test.find({"id" : doc.id}).forEach( // выполняем поиск по id, который получили в запросе выше
function(row) {
db.getSiblingDB("anotherDb").newTest.insert({ // для каждого документа с данным id пишем документ в новую коллекцию.
"id" : doc.id,
"time" : row.time,
});
}
);
}
);
Таким образом, мы записали новую коллекцию с документами типа
{id:sting, time:ISODate(...)}
. Далее выполняем нехитрую агрегацию новой коллекции и получаем искомый результат:i=0;
sum=0;
avg=0;
db.getSiblingDB("anotherDb").newTest.aggregate(
{$group:{_id:"$id",
"time":{$push:"$time"},
}}
).result.forEach(function(op)
{
dur=op.time[op.time.length-1]-op.time[0]
sum=sum+dur;
i=i+1;
});
print(sum/i) //выводим искомое время.
Таким образом, описанный выше подход позволил упростить поиск нужных записей, настроить весьма точный мониторинг на всех уровнях, а также сформировать отчёты и статистику только на основе журнальных данных из MongoDB. Управления схемой данных из приложения и удобство в развёртывании позволят сэкономить время на разработку.
Однако схема данных MongoDB подойдёт не для каждого проекта. Если заведомо известно, что рекурсивный подход в поиске данных или джойны не потребуется, скорее всего, серьёзных проблем не будет!
Комментарии (47)
rudenkovk
08.12.2015 18:06Прекрасно применимая вещь, в проектах начального уровня. Как только коллекция уходит за терабайт, да появляется потребность соблюдать 152ФЗ, простите за язву, но жизнь на монге становиться прекрасной. :)
Проблемы:
1. Как и говорил автор: место, свободное место и уборка базы станет головной болью.
2. Репликация, шардинг, кластеризация… можно будет узнать очень много нового по этим темам. И все равно не заработает.
3. Распределенное хранилище с единой точкой входа… о сколько мученически ночей на настройку:)
… и кстати, монга как saas, даже за много денег, не решает большинства проблем.sl4mmer
08.12.2015 18:12+2>Репликация,
По моему репликация на монге как раз прекрасно. Есть правда свои ньюансы, но они изложены и в офф документации и в материалах курсов сертификацииrudenkovk
08.12.2015 19:21+1репликация прекрасна в HDFS или ES. А у монги она просто достаточна :) Это сугубо мое ИМХО.
kireevco
08.12.2015 19:33+1 ИМХО от меня. Истинное удовольствие. Туда же идет Cassandra, Couchbase(именно по репликации).
Mongo именно достаточно, но приятнее, в качестве админа, работать с вышеупомянутыми).
В моем случае речь идет о кластерах с > 2 шард/реплик.
Khodus
08.12.2015 18:23+1В данном кейсе речь о «выхлопе» журнальных данных в mongo вместо текстовых файлов. В таких случаях, как правило, хранить данные больше чем за пол года смысла нет, поэтому коллекции можно ограничить и до терабайтов дело не дойдет.
AlexWinner
08.12.2015 18:31У меня терабайт логов набегал примерно за неделю, Mongo при вставке нескольких тысяч записей в секунду становилась неадекватной, удаление старых логов с помощью какой-то встроенной штуки, которая сама может удалять старое, работало как-то через раз. Аггрегация тормозила жутко, когда надо было посчитать уникальные айпи для запроса с параметром, например, x=12. В общем, ушли на ElasticSearch, в котором тоже всякие свои грабли, но в итоге их победили.
kireevco
08.12.2015 18:47+2Это объяснимо, MongoDB блокировало на уровне DB. Теперь, с приходом 3.0 блок происходит на уровне коллекций. Не ясно какой версией пользовался автор.
По моему репликация на монге как раз прекрасно. Есть правда свои ньюансы, но они изложены и в офф документации и в материалах курсов сертификации
Монга имеет чуть более чем сложную систему шардинга и репликации. Топология MongoDB сама по себе предполагает множество шестеренок в механизме, что в конечном счете может усложнить жизнь админу/DevOps. Ничего страшного там нет, но есть системы которые шардинг и репликацию делают гораздо более прозрачно и естественно, например Cassandra или ElasticSearch.
И еще, Автор, почему бы не воспользоваться готовым решением ELK? IMHO, оно будет и быстрее и проще и красивее.Khodus
08.12.2015 19:09Ставка была сделана на средства MongoDB API, JavaScript, MapReduce, позволяющие делать агрегации любой сложности и получать данные о состоянии системы без задержки. Высокопроизводительные noSQL базы как правило обладают плоской схемой и малыми возможностями для агрегации, а другие еще и задержкой в получении данных.
kireevco
08.12.2015 19:21+1Если говорить о логах (данные жестко привязаны к дате/времени) — Вы будете удивлены как быстро работает ELK, кроме того еще и в реальном времени. Все дело в разделении индексов, когда при поиске по одному дню поиск просто не «лезет» в предыдущие (количество данных может быть любым, главное чтобы влезало на диск). Плюс полноконтекстовой поиск в ES на высоте, в то время как в MongoDB это все же довесок к основному движку. Еще одно достоинство ELK: вашим программистам не нужно писать логи в таблицу, они просто продолжают писать в лог файл.
Горизонтальная масштабируемость ES на высоте, просто добавляете еще одну железку. С Mongo такое не прокатит, будете как минимум заморачиваться с шардингом и количеством реплик / арбитров.
Я не знаю чья это кибана, но она работает 104.131.39.41:5601/
К слову о подсчете элементов
Khodus
08.12.2015 19:38Для проектов с простой схемой данных, уверен, MongoDB не самое производительное и не самое простое решение. В данном случае схема довольно сложная, это не учитывая, что в статье она сильно упрощена (для наглядности). В проектах с односложными записями попробую указанные выше решения, спасибо за наводку!
rudenkovk
08.12.2015 19:40+1Как раз с увеличением сложности (объема) все минусы монги полезут вверх, Она как раз применима на малых проектах, или на малых данных.
kireevco
08.12.2015 19:43Logstash может индексировать JSON как есть, а elasticsearch обладает неимоверными возможностями поиска.
Когда нужно делать реально необходимый map-reduce (например для сложных отчетов) существет интеграция.
Тем не менее я не исключаю возможности необходимости map-reduce в mongo, но вот realtime поиск и map-reduce у меня как-то не вяжется в голове.
AlexWinner
08.12.2015 21:01В ES тоже надо заморачиваться с количеством шардов. Правда, возможность делать выборки по wildmask вместо имени индекса это компенсируют. Можно делать индексы за сутки, за час и т.п.
swood
11.12.2015 03:43Эластик хорош всем, кроме того, что не может переварить 8к индексов по 4 гигабайта, состоящие из 4 шард с одной репликой.
kireevco
11.12.2015 03:56Да, проблема эластика в том что на каждый индекс выделяется определенное количество ресурсов.
Даже если индекс пустой, все равно, по умолчанию, происходит резервирование ресурсов.
Не знаю какое архитектурное решение за 8K индексами, но в реальности это решится только горизонтальным наращиванием IO и памяти.
Но я полностью чувствую Вашу боль :)swood
11.12.2015 04:06Да как раз для логов как-то и попытался его использовать. Просто много серверов и логов с них.
На самом деле, если не дышать, то он еще работал. Но вот вставка в моменты переиндексации умирает. Даже если делать ее с большим таймаутом. Окончательно его добила смерть одного из серверов. После этого он так и не смог восстановиться. Хотя серверов было около 50.
Рад, что мою боль разделили ))kireevco
11.12.2015 06:17У меня был кластер logstash/ES из 3х серверов, 96G памяти на каждом. Данных около 4TB всего.
Работало хорошо, индексировало данные с ~200 серверов. Приходилось вручную отсекать левые даты, тогда многие индексы просто не создавались. Шаблон создания индексов: один на каждый день.
Eternalko
08.12.2015 19:33+1Высокопроизводительные noSQL базы как правило обладают плоской схемой и малыми возможностями для агрегации, а другие еще и задержкой в получении данных.
Иначе говоря, вы недостаточно изучили фундамент.
Если совсем коротко, раз уж про ELK который вы очевидно даже не тестировали, то ELK быстрее Mongo в разы. И чем сложнее «запросы» тем отрыв больше.
Но игра «у кого быстрее» это не самое главное. У ELK отличный stack и поддержка для таких time-series use cases на порядок серьезней, практичней и прагматичней.Khodus
08.12.2015 19:45В данном проекте скорости монги хватает, задержек в получении или обработке данных нет. Подход вполне работоспособен и имеет право на жизнь.
kireevco
08.12.2015 19:50+1Если я правильно понял, Вы интегратор / консалтинг компания. На мой взгляд нужно знать работающие из коробки решения. ELK как раз один из них, когда заказчик, как бы это покультурней казать, «писает кипятком от восторга».
Khodus
09.12.2015 12:33Решение хранить логи в mongo было принято архитектором приложения, причины на то были. Мне же, как админу, скорее пришлось с этим как-то жить. Собственно трудности и пути решения описал, это скорее гайд по граблям для тех, кто только начинает знакомство с mongo.
kireevco
08.12.2015 19:39+1По моему опыту как только разработчики «почуют» возможность логить все что не попадя Вы будете писать в свою DB всяких хлам, от которого размеры вырастут неимоверно сильно. Субъективно — от этого высока вероятность отказа MongoDB, а от этого блокирующей абстрактной операции `logger.writeMongoLog(«Error 503 — unknown error»)`, что может положить все приложение. Это просто довод против записи напрямую в DB.
kireevco
08.12.2015 18:49Расскажите/ткните пожалуйста про 1ТБ + 152ФЗ? Или это две различные проблемы? (тогда все понятно).
rudenkovk
08.12.2015 19:24У меня все просто, я имею около 1Тб логов в неделю, и большую их часть надо хранить по 153ФЗ. Дополнительно еще по ним хочется строить ту или иную аналитику.
Так что скорее это одна проблема. :)Eternalko
08.12.2015 19:35Хранить логи по пол года это как не ходить пол года в туалет :)
Но закон есть закон, да )kireevco
08.12.2015 19:45Представил — испугался.
rudenkovk
08.12.2015 19:52ага, и порядка 100k/min метрик, частично надо хранить до конца жизни :) Так что ES наша палочка-выручалочка.
Eternalko
08.12.2015 21:19+1Спокойно, у нас 100к событий в секунду ) Сейчас масштабируем до 1М/с.
Правда столько держать «в себе» нет надобности. Контакт ваш записал, можем обмениваться опытом.rudenkovk
09.12.2015 13:53+1Взаимно :) Пока особо обмениваться нечего, мое решение в бете, а вот перед пилотом, можно с удовольствием. :)
Как вариант, тема часто трогается на русском канале в hangops (в slack)
x88
09.12.2015 09:48Зачем костыли с forEach, если для агрегации есть mapКRduce, который работает и с репликациями, и шардами?
upd: заметил, про mapReduce написали, но поверхностноKhodus
09.12.2015 10:05Для мониторинга системы выполняется около 200 метрик каждую минуту, все делаются путем запросов (чаще агрегаций) из mongo. Данные при этом агрегируются за маленькие промежутки времени, и mapReduce для этого использовать крайне неудобно. Против mapReduce ничего не имею, использую для построения отчетов, когда данные собираются минимум за месяц.
В статье описывал проблемы, с которыми столкнулся, а с mapReduce таковых не было, да и опыта работы не столь много, чтобы поделиться особыми скилами. А вот быстро достать и агрегировать данные за «вчера» приходилось часто, и чтобы привести вывод в удобный вид .forEach() очень выручал, возможно этот опыт кому-то пригодится.
mak_sim
09.12.2015 19:52А не смотрели например на Fluentd? Просто кмк не было необходимости писать драйвер и слать приложением данные в монгу напрямую. Под это дело существует fluent. Получить любые логи, преобразовать в JSON и отправить хоть в монгу хоть куда.
Khodus
09.12.2015 20:47В данном случае я занимался поддержкой уже имеющегося решения, со всеми своими плюсами и минусами, которые изложил. В сторону Fluentd обязательно посмотрю.
AxMuha
10.12.2015 01:34Недавно услышал о такой штуке, как HP Vertica, до 1Тб бесплатно, очень хвалили знатоки… так. к слову.
smarthaos
На специализированные базы данных (https://en.wikipedia.org/wiki/Time_series_database) не смотрели?
> Модель данных была специально спроектирована для хранения унифицированных документов в JSON-формате.
Но это же много ресурсов на формирование строк, их передачу и хранение.