Перейти к основному содержанию

go-cache-lru: три версии за день и сутки под лимитом

29 октября 2026 · 7 минут чтения

golang · cache · opensource

Позавчера я писал про go-cache-lru, реализацию GOCACHEPROG, которая держит кеш сборки Go в заданном размере. Тот пост заканчивался списком того, что не проверено: вытеснение в реальной работе ни разу не срабатывало, а Windows не поддерживалась. 28 октября вышли версии 0.2.0, 0.3.0 и 0.4.01. Ниже — что в них изменилось и как кеш прожил следующие сутки.

0.2.0: вытеснение без пауз и тихий демон

Утром 28 октября кеш дошёл до 25 ГиБ из 30, не вытеснив ни одной записи2. Первый прогон вытеснения должен был удалить около 5 ГиБ, и тут всплыла проблема: файлы объектов удалялись под мьютексом кеша. Пока шла пачка из 256 удалений, ждали все get и put всех команд go. Теперь под блокировкой объект только помечается, а файл удаляется после её снятия. Бенчмарк на 2000 объектах по 256 КиБ показал, как изменилась задержка get во время вытеснения:

ДоПосле
Максимальная задержка get30–38 мс0.12–0.16 мс
get дольше 1 мс40
get, обслуженных за время вытесненияоколо 650около 30 000

Само вытеснение быстрее не стало, но сборки его больше не ждут.

Запись объектов стала дешевле. Тело put приходит в base64 и декодируется кусками не больше 768 байт, а io.Copy превращал каждый кусок в отдельный системный вызов write. На холодной сборке, которая положила в кеш 1.8 ГиБ, демон потратил на эти вызовы 12.7 с из 16 с процессорного времени3. Буфер перед временным файлом поднял скорость записи в бенчмарке со 156–175 до 486–500 МБ/с. Второе узкое место — сокет между командой go и демоном: macOS даёт unix-сокетам буферы по 8 КиБ, и данные шли мелкими порциями. Обе стороны теперь просят 1 МиБ. На холодном go test -race большого сервиса процессорное время демона упало с 8.5 до 7.8 с, а промежуточного процесса — с 3.4 до 2.1 с.

Демон перестал просыпаться без дела. Без единой команды go он всё равно просыпался примерно раз в секунду: индекс сбрасывался по таймеру каждые 100 мс, метка go clean -cache проверялась каждые 2 с, а stats.json перезаписывался каждые 30 с. Теперь каждый таймер работает только при наличии работы. По top -stats idlew за три минуты простоя после сборки было 129 пробуждений, стало 0.

Ещё два изменения про удобство. Демон замечает, что его бинарник заменили, и завершается, так что после обновления больше не нужен go-cache-lru stop. stats -v показывает, чем занят кеш: объекты по размеру и по времени последнего обращения. Раньше для этого приходилось лезть в index.db через sqlite3, и так выяснилось, что 72 % кеша в 25 ГиБ занимали 407 архивов пакетов примерно по 90 МиБ4.

0.3.0: история по часам

Накопительные счётчики плохо показывают, как кеш работает сейчас: в процент попаданий с первого запуска входит прогрев. На одной машине stats давал 96.7 % за 22 часа, хотя за последние три с половиной часа было 98.7 %5. Увидеть второе число можно было, только вычитая два снимка руками.

Теперь демон хранит изменения счётчиков по часам за 7 дней в той же базе SQLite, вместе с пиковым размером кеша за час. stats показывает текущий час, сутки и неделю, stats -v добавляет почасовую таблицу. По этой истории stats даёт совет про --max-size. Пока часов с командами go меньше 24, он ждёт данных. Если вытеснений не было, а пик не дошёл до 60 % лимита, совет — пик плюс 25 %. Если вытеснения были, он сравнивает долю промахов в час после вытеснения с обычной. Когда после трёх и более вытеснений промахов в полтора раза больше, он предлагает лимит на 50 % выше. Пороги — первая прикидка, их надо проверить на настоящей истории.

Обновление стало мягче. В 0.2.0 демон, заметив новый бинарник, сразу переставал принимать подключения, и новые команды go ждали, пока закончатся старые. Теперь он продолжает обслуживать и выходит в первый момент, когда к нему не подключена ни одна команда go.

Управляющим командам больше не нужен --dir. stats, trim, clean и stop берут флаги из GOCACHEPROG в окружении или в go env, поэтому статистика теперь — просто go-cache-lru stats.

0.4.0: Windows

Блокировка демона и запуск в фоне работали через flock и setsid, которых в Windows нет. Теперь там используются LockFileEx и отдельная группа процессов, а различия собраны в файлах *_windows.go. По дороге нашлись две ошибки: путь с буквой диска ломал URI базы SQLite, а два одновременных put одного объекта переименовывали второй файл поверх первого, что Windows запрещает, пока файл кто-то читает. Содержимое у них одинаковое, поэтому теперь остаётся файл, который уже лежит на диске. Тесты гоняются в CI и на Windows, бинарники выходят zip-архивами.

Сутки на 0.4.0

Демон v0.4.0 запустился 28 октября в 17:22, через несколько секунд после тега, и проработал без перезапуска до вечера 29-го. Лимит прежний: --max-size=30GiB, --target-size=25GiB. Вот stats -v на 29 октября, 17:10, путь к домашнему каталогу заменён на ~:

$ go-cache-lru stats -v
daemon          pid 48388, version v0.4.0, up 23h47m56s, 0 active sessions
directory       ~/.cache/go-lru
size            25.8 GiB of 30.0 GiB (target 25.0 GiB), 86.0%
entries         76155 actions, 14056 objects, 0 pinned
counters since  2026-10-27 16:38:51
hits            3542620 (97.4%), 775.3 GiB served
misses          95312
puts            174754, 80.3 GiB written
evictions       66727 entries, 50.6 GiB
cleans          0 explicit, 1 by go clean -cache
sessions        2337
this hour       no go commands
last 24 hours   hit rate 98.0%, 1639228 gets, 28.7 GiB written, 6 evictions
last 7 days     hit rate 97.9%, 2169649 gets, 38.5 GiB written, 7 evictions
advice          not enough history yet: 12 of 24 active hours

objects by size
  < 64 KiB               5675    43.2 MiB    0.2%
  64 KiB - 1 MiB         5823     2.4 GiB    9.3%
  1 MiB - 10 MiB         2096     4.8 GiB   18.7%
  10 MiB - 100 MiB        460    18.3 GiB   70.9%
  >= 100 MiB                2   253.8 MiB    1.0%

objects by last access
  < 1h                   1127   375.2 MiB    1.4%
  1h - 6h               12929    25.4 GiB   98.6%
  6h - 24h                  0         0 B    0.0%
  >= 24h                    0         0 B    0.0%

hourly, last 24 hours
  hour       gets  hit rate     written evictions   peak size
  18:00       710     99.7%     2.0 MiB         0    29.4 GiB
  09:00      9140     98.3%    94.2 MiB         0    29.5 GiB
  10:00     43177     99.6%    60.8 MiB         0    29.5 GiB
  11:00    303009     97.7%     5.0 GiB         1    30.0 GiB
  12:00    437705     97.8%     8.2 GiB         2    30.0 GiB
  13:00    152856     97.1%     3.5 GiB         1    30.0 GiB
  14:00    332627     98.3%     6.9 GiB         1    30.0 GiB
  15:00    352247     98.3%     5.0 GiB         1    30.0 GiB
  16:00      7757     99.6%     5.3 MiB         0    25.8 GiB

Главное здесь — столбец peak size. С 11 до 16 часов в кеш записали 28.6 ГиБ, а пик за каждый час остался на 30.0 ГиБ. Шесть раз кеш упирался в лимит, и шесть раз демон сжимал его до цели, убирая по 5–5.5 ГиБ за прогон. За всё время счётчиков вытеснено 66 727 записей, 50.6 ГиБ. До go-cache-lru кеш на этой машине дорастал до 66–85 ГБ и забивал диск.

Точнее, в байтах пик выходит за лимит на 0.4–31 МиБ. Вытеснение стартует в фоне после записи, которая перешагнула лимит, и пока оно идёт, успевают лечь ещё несколько объектов. В пересчёте на 30 ГиБ это меньше 0.1 %.

Доля попаданий от вытеснения не просела: в часы с вытеснением она 97.1–98.3 %, за сутки — 98 %. Кеш отдал 775.3 ГиБ при 80.3 ГиБ записанного.

Две строки стоит прочитать отдельно. В objects by last access всё, что осталось, использовалось за последние шесть часов, более старое вытеснено. Сборка ветки, к которой я не прикасался со вчера, начнёт с промахов. Стоит ли это лишних гигабайт на диске, должен ответить совет: на момент снимка у него 12 часов с командами go из 24 нужных. А objects by size подтверждает картину из 0.2.0: 460 объектов от 10 до 100 МиБ занимают 71 % места, и сколько всего поместится под лимит, решают они.

Что не проверено

Совет по размеру ещё ни разу не выдал рекомендацию, и пороги в нём пока не сверены с настоящей историей. Запуск с холодным page cache по-прежнему не замерен.


  1. Релизы go-cache-lru, бинарники для macOS, Linux и Windows. ↩︎

  2. Issue #3, удаление файлов вне блокировки кеша. ↩︎

  3. Issue #12, профиль и замеры записи объектов. ↩︎

  4. Issue #5, stats -v и флаг --version. ↩︎

  5. Issue #14, почасовая статистика и правила совета. ↩︎