1. Тема дня

После crash consistency нужно посчитать ресурс Flash. Ресурс расходуют physical program/erase operations, а не размер переменной сам по себе. Главная мысль: запись 4 байт раз в секунду может превратиться в сотни миллионов logical updates за 10 лет.

2. Зачем это нужно

Опасные кандидаты:

text
mqtt_reconnect_count++
modem_reset_count++
uart_error_count++
phase_transition_count++
watchdog_warning_count++

Если после каждого increment делать nvs_commit(), ресурс может закончиться намного раньше срока службы.

3. Теория

Endurance budget

Оценка:

text
N_updates ≈ E * P * U

где:

text
E = erase cycles на страницу
P = число страниц wear levelling
U = logical updates между erase одной страницы

NVS ESP-IDF

NVS хранит записи append-only. Новое значение дописывается, старое инвалидируется. Страница NVS 4096 байт, entry 32 байта, integer до 8 байт занимает одну entry. Для маленьких values NVS существенно снижает erase frequency, но ресурс всё равно конечен.

10-летний масштаб

text
1 commit/s       -> 315 576 000 commits / 10 years
1 commit/10s     -> 31 557 600
1 commit/min     -> 5 259 600
1 commit/hour    -> 87 660

Durability classes

text
D0 volatile: можно потерять при reset
D1 approximate: можно потерять последние минуты
D2 checkpointed: можно потерять последние N операций
D3 immediately durable: ACK только после commit

Counter batching

Runtime value меняется часто, Flash checkpoint редко:

text
commit если dirty >= 100 или age >= 60s

Coalescing

Несколько быстрых config changes объединить в один snap- shot/commit.

Разные partitions

Разные write rates лучше разводить по разным NVS partitions:

text
nvs_factory read-only
nvs_config rare durable writes
nvs_stats periodic checkpoints

4. Типичные ошибки

  1. «Всего 4 байта» = маленький износ.
  2. Путать program и erase cycles.
  3. nvs_commit() после каждого счётчика.
  4. Высокочастотную статистику держать рядом с критичной config.
  5. Использовать NVS как binary logger.
  6. Усреднённые 100k cycles вместо datasheet конкретной Flash.
  7. Огромный blob snapshot при изменении одного поля.
  8. Минимальная partition без места для GC.
  9. Откладывать commit D3-операции, уже подтверждённой backend.
  10. На STM32 перезаписывать одну Flash page как EEPROM.
  11. Не считать commits в soak/HIL.

5. Практическое задание

Сделай таблицу persistent objects:

text
name
updates/day
allowed data loss
D-class
storage backend

Найди все записи:

text
rg "nvs_set_|nvs_commit|esp_partition_write|wl_write"

Добавь storage diagnostics:

c
typedef struct {
    uint64_t nvs_set_calls;
    uint64_t nvs_commit_calls;
    uint64_t bytes_requested;
    uint64_t checkpoint_skips;
    uint64_t checkpoint_writes;
} storage_write_stats_t;

Переделай один счётчик, например mqtt_reconnect_count: RAM increment, commit если dirty >= 10 или age >= 60s. Проверь допустимую потерю после esp_restart().

6. Что попробовать дальше

  • Добавить nvs_get_stats() в CLI.
  • 1000 обновлений uint32_t vs 1000 обновлений 1024-byte blob — сравнить расход entries.
  • Для STM32 заранее рассчитать required endurance до выбора Flash layout.
Backend command имеет класс D3 immediately durable. Можно ли отложить commit после успешного ACK для уменьшения износа?

Задание

По упрощённой оценке N_updates ≈ E * P * U возьмите E=1000 erase cycles/page, P=4 pages, U=100 logical updates на erase страницы. Вычислите budget и объясните, почему это не измеренная гарантия lifetime конкретной Flash.

Критерии самопроверки: Получите 400000 и назовите assumptions устройства/workload вместо гарантированного lifetime.

Показать ответ автора

Оценка: 1000 * 4 * 100 = 400000 logical updates. Реальный ресурс зависит от datasheet конкретной Flash, layout storage, размеров updates, garbage collection и write amplification. Оценка требует проверки workload и endurance assumptions; это не hardware measurement.