Освобождение места MongoDB для NightScout
Как освободить место в MongoDB Atlas M0 после удаления старых данных через Nightscout.
Введение #
Если вы используете Nightscout с MongoDB Atlas Free Tier (M0), то рано или поздно возникнет проблема места. На Free Tier доступно 512 MB.
По мере наполнения база приближается к лимиту. Вы удаляете старые записи через административный интерфейс Nightscout, однако размер базы в Atlas почти не уменьшается или даже увеличивается. Рано или поздно Atlas заблокирует запись в базу. NS перестает работать, отправка данных в AAPS становится в очередь.
you are over your space quota
Writes are blocked on your clusterПричина может быть не в самих данных, а в индексах MongoDB.
После удаления большого количества документов WiredTiger в M0 сохранит значительный объём пространства внутри существующих индексов. Индексы постепенно станут огромными, значительно больше, чем заново построенные индексы для того же набора документов.
В реальном случае с Nightscout было:
dataSize: 242 MiB
indexSize: 311 MiBТо есть индексы занимали больше места, чем сами данные!
После последовательного удаления и создания тех же индексов заново:
dataSize: 244 MiB
indexSize: 108 MiBКоличество индексов осталось тем же, данные не удалялись, но было освобождено около 200 MiB.
Как диагностировать и исправить проблему - ниже.
1. Подключение к MongoDB Atlas через mongosh #
В MongoDB Atlas открываем:
Database → Connect → MongoDB ShellAtlas даст ссылку на скачивание mongosh, а потом покажет команду примерно такого вида:
mongosh "mongodb+srv://cluster0.example.mongodb.net/" --apiVersion 1 --username USERNAMEПосле подключения посмотри список баз:
show dbsНапример:
heroku_gl409cbt 432.95 MiBПереключись на базу Nightscout:
use heroku_gl409cbtРазумеется, вместо heroku_gl409cbt нужно использовать имя своей базы.
Проверь коллекции:
show collectionsОбычно Nightscout содержит:
activity
auth_roles
auth_subjects
devicestatus
entries
food
profile
settings
treatments2. Проверка общего размера базы #
Выполняем:
db.stats(1024 * 1024)Например:
{
dataSize: Long('242'),
storageSize: Long('121'),
indexes: 56,
indexSize: Long('311'),
...
}Для Atlas M0 особенно интересны:
dataSize
indexSize
indexes
objectsПри диагностике проблемы с квотой удобно ориентироваться на:
dataSize + indexSizeНапример:
242 + 311 = 553 MiBЭто уже больше лимита M0 в 512 MB.
storageSize здесь не следует путать с dataSize: WiredTiger сжимает данные, поэтому физическое хранилище коллекций может быть значительно меньше логического размера.
3. Найти коллекцию, занимающую место #
У Nightscout основной объём обычно приходится на:
entries
devicestatus
treatmentsМожно получить размеры всех коллекций:
db.getCollectionNames().forEach(c => {
const s = db.runCommand({
collStats: c,
scale: 1024 * 1024
});
print(
c,
"| count:", s.count,
"| data:", s.size, "MiB",
"| storage:", s.storageSize, "MiB",
"| indexes:", s.totalIndexSize, "MiB"
);
});Пример проблемной базы:
devicestatus | count: 74900 | data: 117 MiB | storage: 31 MiB | indexes: 12 MiB
treatments | count: 19119 | data: 4 MiB | storage: 1 MiB | indexes: 3 MiB
entries | count: 489188 | data: 120 MiB | storage: 88 MiB | indexes: 295 MiBОчевидно, что проблема здесь в индексах entries.
4. Проверить размеры индексов entries #
Выполняем:
db.entries.stats()В конце вывода будет:
indexSizes: {
_id_: 48050176,
type_1: 18386944,
identifier_1: 18042880,
'type_1_date_-1_dateString_1': 43540480,
mbg_1: 18022400,
sysTime_1: 38334464,
srvModified_1: 18030592,
date_1: 26439680,
isValid_1: 18051072,
dateString_1: 38477824,
sgv_1: 24432640
}Размеры здесь указаны в байтах.
Например:
dateString_1 ≈ 37 MiB
sgv_1 ≈ 23 MiB
date_1 ≈ 25 MiB5. Обязательно сохранить определения индексов #
Перед любыми изменениями выполняем:
db.entries.getIndexes()И сохрани вывод в файл
Например:
[
{ v: 2, key: { _id: 1 }, name: '_id_' },
{ v: 2, key: { type: 1 }, name: 'type_1' },
{ v: 2, key: { identifier: 1 }, name: 'identifier_1' },
{ v: 2, key: { type: 1, date: -1, dateString: 1 }, name: 'type_1_date_-1_dateString_1' },
{ v: 2, key: { mbg: 1 }, name: 'mbg_1' },
{ v: 2, key: { sysTime: 1 }, name: 'sysTime_1' },
{ v: 2, key: { srvModified: 1 }, name: 'srvModified_1' },
{ v: 2, key: { date: 1 }, name: 'date_1' },
{ v: 2, key: { isValid: 1 }, name: 'isValid_1' },
{ v: 2, key: { dateString: 1 }, name: 'dateString_1' },
{ v: 2, key: { sgv: 1 }, name: 'sgv_1' }
]Не следует копировать определения индексов из этой инструкции вслепую. Используй именно определения, которые показывает:
db.entries.getIndexes()для твоей базы и версии Nightscout.
6. Никогда не удалять _id_
#
Индекс:
_id_не трогаем. Это системный обязательный индекс MongoDB. Перестраиваются только вторичные индексы.
7. Перестраиваем индексы по одному #
Идея проста:
старый раздутый индекс -> dropIndex() -> createIndex() -> новый компактный индекс
Например для sgv_1:
db.entries.dropIndex("sgv_1")затем:
db.entries.createIndex({ sgv: 1 })После этого проверить:
db.entries.stats().indexSizesи:
db.stats(1024 * 1024)В реальном случае:
sgv_1
до: ~23 MiB
после: ~3 MiB8. Примеры перестройки других индексов #
Ещё раз: определения должны соответствовать результату getIndexes().
type #
db.entries.dropIndex("type_1")db.entries.createIndex({ type: 1 })В рассматриваемом случае:
~18 MiB → ~2 MiBdate #
db.entries.dropIndex("date_1")db.entries.createIndex({ date: 1 })Результат:
~25 MiB → ~6 MiBdateString #
db.entries.dropIndex("dateString_1")db.entries.createIndex({ dateString: 1 })Результат:
~37 MiB → ~11 MiBidentifier #
db.entries.dropIndex("identifier_1")db.entries.createIndex({ identifier: 1 })Результат:
~17 MiB → ~2 MiBsrvModified #
db.entries.dropIndex("srvModified_1")db.entries.createIndex({ srvModified: 1 })Результат:
~17 MiB → ~2 MiBСоставной индекс #
Если getIndexes() показывает:
{
key: {
type: 1,
date: -1,
dateString: 1
},
name: "type_1_date_-1_dateString_1"
}его можно перестроить:
db.entries.dropIndex("type_1_date_-1_dateString_1")затем:
db.entries.createIndex({
type: 1,
date: -1,
dateString: 1
})9. Что делать, если Atlas уже заблокировал запись #
Важная особенность M0.мЕсли Atlas уже сообщает:
you are over your space quota
Writes are blocked on your clusterто может возникнуть такая ситуация:
db.entries.dropIndex("sgv_1")успешно выполняется, но:
db.entries.createIndex({ sgv: 1 })возвращает:
MongoServerError[AtlasError]:
you are over your space quota...
Writes are blocked on your cluster.То есть индекс уже удалён, но Atlas не позволяет создать его заново. В таком случае не нужно продолжать многократно выполнять createIndex(). Нужно сначала освободить достаточно места. Для этого можно временно удалить несколько вторичных индексов, определения которых предварительно сохранены через:
db.entries.getIndexes()Например:
db.entries.dropIndex("type_1")
db.entries.dropIndex("mbg_1")
db.entries.dropIndex("isValid_1")
db.entries.dropIndex("sysTime_1")Удаление обычного неуникального индекса не удаляет документы Nightscout.
Оно может временно ухудшить производительность некоторых запросов, но сами данные остаются в базе.
После этого проверить:
db.stats(1024 * 1024)Когда база окажется достаточно ниже лимита и Atlas снимет write-block, индексы можно начать восстанавливать.
10. Проверить, что данные снова поступают в Nightscout #
Самый простой признак — посмотреть:
db.stats(1024 * 1024)и обратить внимание на:
objectsЕсли через некоторое время число увеличивается:
583246
583286
583547
...значит в Nightscout снова поступают данные. Например их снова пишет AAPS.
Можно также выполняемть тестовую запись:
db.getCollection("__quota_test").insertOne({ test: 1 })Если получено:
acknowledged: trueзапись разрешена.
Тестовую коллекцию после этого удалить:
db.getCollection("__quota_test").drop()11. Восстановить все штатные индексы #
После появления достаточного запаса необходимо восстановить все индексы, которые были временно удалены.
Например:
db.entries.createIndex({ mbg: 1 })
db.entries.createIndex({ sysTime: 1 })
db.entries.createIndex({ isValid: 1 })Но ориентироваться надо на сохранённый результат:
db.entries.getIndexes()В итоге количество индексов должно совпасть с первоначальным.
Например:
до: indexes: 56
после: indexes: 5612. Итоговая проверка #
Проверить общий размер:
db.stats(1024 * 1024)И размеры индексов entries:
db.entries.stats().indexSizesВ рассматриваемом случае до обслуживания было:
dataSize: 242 MiB
indexSize: 311 MiB
-------------------
~553 MiBПосле перестройки индексов:
dataSize: 244 MiB
indexSize: 108 MiB
-------------------
~352 MiBКоличество индексов:
56 → 56То есть индексы не были просто удалены ради экономии места — они были восстановлены, но уже в компактном состоянии.
Только для коллекции entries:
индексы до: ~295 MiB
индексы после: ~92 MiBЭкономия составила примерно:
~203 MiBпри практически неизменном объёме самих данных.
13. Как понять, что проблема повторилась #
Периодически можно выполнять:
db.stats(1024 * 1024)Если при относительно стабильном объёме данных:
dataSizeначинает сильно расти:
indexSizeстоит посмотреть:
db.entries.stats().indexSizesи сравнить размеры отдельных индексов с предыдущими значениями.
Например после обслуживания нормальные размеры выглядели так:
{
_id_: 32833536,
sgv_1: 3452928,
type_1: 2338816,
date_1: 6647808,
dateString_1: 11264000,
identifier_1: 2232320,
'type_1_date_-1_dateString_1': 20021248,
srvModified_1: 2215936,
mbg_1: 2215936,
sysTime_1: 11128832,
isValid_1: 2215936
}Этот вывод удобно сохранить как контрольную точку.
Вывод #
Если после удаления старых данных Nightscout размер MongoDB Atlas M0 почти не уменьшается, не нужно продолжать удалять историю.
Сначала стоит проверить:
db.stats(1024 * 1024)Если большую часть базы занимают индексы (indexSize) и entries.stats() показывает очень крупные старые индексы, то причина — раздутые индексы.
В таком случае последовательная перестройка индексов: dropIndex() → createIndex(). Перестройка может освободить сотни мегабайт, сохранив:
- все данные Nightscout;
- все необходимые индексы;
- ту же структуру базы;
- ту же функциональность Nightscout.
Основной подход: сначала сохранить getIndexes(), не трогать _id_ и перестраивать индексы по одному.
Вопросы? #
Возникли вопросы, комментарии или есть полезная информация по теме заметки?
Можно написать в телеграм или оставить вопрос в форме ниже.