Комментарии 28
А использовать O_DIRECT (+ FILE_FLAG_NO_BUFFERING для Windows) со всей остальной обвязкой - не подходит? Оно как раз для такого сценария, когда надо тупо много писать на диск....
на самом деле, O_DIRECT в Linux имеет свои недостатки, которые как раз использование page cache и решает, как ни странно. Так что управление через posix_fadvise — годное и более универсальное. O_DIRECT желательно оставить для совсем уже граничных случаев.
О, спасибо, за вопрос, если честно то изначально так и планировалось(даже в роадмапе до сих пор стоит), но есть нюансы. Эти флаги требуют жесткого выравнивания в памяти по границе стараницы(4 кб). А вот аллокатор го и сборщик, не может гарантировать что []byte слайс будет выровнен как надо. Что бы это завести пришлось бы мучиться с unsafe, аллоцировать куски руками высчитывать оффсеты и писать отдельный буфферизатор который добивал бы неровные кадры нулями до размера сектора. FADV_DONTNEED же дает тот же профит, но позволяет использовать просто os.File. Ось в данном случае просто сама разруливает невыровненые чанки, а флагом просто заставляем выкинуть кэш в конце.
Он требует ручного выравнивания буферов по размеру сектора диска, кмк проще юзать mmap для записи больших кусков и снимать грязные страницы мадвайзом
Mmap был бы неплох, если бы не один факт: мы заранее не знаем размер файла, т.к пишем лайв поток. При использовании mmap пришлось бы постоянно юзать ftruncate и ремаппить память пропорционально росту файла. В го это был бы дикий оверхед на page faults.
mmap - очень крутая штука для бд(когда используется random acces), у нас же идет строгая последовательная запись(append-only). Стнадрный write в Линуксе работает почти идеально, поэтому связка os.File и FADV_DONTNEED, тупо проще безопаснее и не жрет процессорное время.
под Linux можно ещё в сторону sync_file_range посмотреть. Это не замена sync(), но именно потому и стоит посмотреть.
Хм, спасибо за намек, возьму себе на заметку. Если окажется что это менее ресурсоемко, и не будет других подводных комней - то передем на нее.
там нужно будет не переезжать, а скорее доезжать — вот краткий hint от ChatGPT по смежным функциям:
sync_file_range -> довести dirty pages до writeback/clean
fdatasync -> durability данных + необходимых для них metadata
fsync -> durability данных + metadata файла
FADV_DONTNEED -> после этого попросить ядро выкинуть clean cache
свободно 100 метров ОЗУ.
И available при этом ещё 30 gb?
Если вы постоянно пишете с sync, далеко масштабировать свое решение у вас не получится - IO дисков кончится раньше.
Для управления page cache есть vm.dirty, как минимум.
Вангую: при аппаратной проблеме с хранилищем эта неубиваемая программа "замрёт как мертвец" в вечных access timeout's, но будет изображать что жива.
Самый простой краш тест, который давно использую: сервер, прога, скрипт, неважно какая сущность - пишут в папку, которая лежит на USB-HDD, а его вытащили из розетки. Вот здесь порой начинается такая магия...
Спасибо за этот "наброс". Да вы правы, но правы лишь в том как поведет себя ОСь. Действительно при физическом отвале носителя или шары системный вызов Write() или Sync() зависит в ожидании I/O таймаута от драйвера. И да горутина пишущая файл, как вы выразились "замрет как мертвец". Но, это пройзодет только с ней, сервер не пострадает и не зависнет . Подсистема записи (Recorder.go) жестко развязана с ядром получения потоков (Ring.go). Сам рекордер является лишь одним из подписчиков ядра(как и муксер или webrtc-клиент). Это равносильно тому что 1000 зрителей смотрят стрим на твиче, у одного вырубили свет, он перестал смотреть стрим. Все. Больше ничего не произойдет, сам стрим на твиче продолжится как не бывало. Более технично в проекте:
Рассыдка кадров из ядра всем поодписчикам реализована через неблокирующие каналы Go(select +default)
// Внутри ядра рассылки (RingBuffer):
select {
case sub.C <- frame:
// Кадр ушел подписчику
default:
// Подписчик тормозит или завис на I/O.
// Дропаем кадр, инкрементим счетчик потерь.
atomic.AddUint64(&sub.Drops, 1)
}Что произойдет если как вы выразились физически удалить устройство:
Горутина рекордера намертво виснет в системном вызове. Она перестает вычитывать кадры из своего(!) канала.
За милисекнуды канал рекордера забивается полностью(в моей реализации, наполнений = 100 кадров)
Ядро (ringBuffer) при попытке отправить следущий кадр, попадает в ветку default, и просто начнет дропать кадры только для рекордера, не блокируя свою работу вообще, даже на милисекунду.
Итог - запись архива ломается, в логах ошибки дропа, но все остальные подписчики (HLS/WebRTC зрители, ИИ-модули, и все остально что еще не прикручено) продолжать получать видеопоток в лайв режиме
Так что сервер, не будет изображать что "он жив", он действительно будет полностью работоспособен, просто изолировав аппартаный отказ хранилища, только в рамках одной горутины.
Надеюсь, я полностью объяснил то как это работает.
Дефолтная ветка в селекте гошных каналов как раз отлично отрабатывает такие аппаратные таймауты без влияния на соседние горутины стриминга
Ну про vm.dirty замечание оличное, но есть два нюанаса:
Sync не делается на каждый кадр, сброс идет только после закрытия целого чанка fMP4, а это раз в несколько секунд. Диск от такого редкого синка даже не почешется, и в I/O не упрется
А вот касательно vm.dirty, это глобальная настройка всего железного сервера, и ставить ее больше зло чем добро, тк в таком случае поведение кеша изменится для всех и вся(может отвалится та же встроенная БД которой такой кеш жизненно нужен). Флаг же позволяет действовать более тонко, мы трогаем только кэш для наших видеофайлов, а у остальных процессов все по стандарту.
Если что то записывать на диск в несколько потоков без танцев с бубном то страшный линукс сожрет всю память и позовет оом киллера?
Миллион раз писал тонны данных на диск, и банальной командой copy и торрентами и как угодно еще и ни разу с этим не было ни малейших проблем (если не считать 12309 бгг).
банальной командой copy
Если речь про cp — там крошечные буферы (если вообще есть), алгоритм без изысков.
Приветствую! Баг 12309 вы вспомнили очень даже кстати, суть проблемы там похожа. Но вы путаете простую десктопную нагрузку(скопировать файлы, торренты), с хайлоадом который пашет 24/7. При copy ядро линукса действительно само выбросит чистык страницы кеша, когда память кончается. Но разница в том, что 100 камер генерят огромный(по сути бесконечный) пооток грязных страниц, я граязные страницы ядро не может отбросить, пока не сбросит на физический блин диска. Если же диски не поспевают, эти грязные страницы копятся до конечной. И когда соседному процессу срочно нужна память - линукс фризит все I/O, либо вытесняет в своп полезную(!) память соседей по процессам, а вот потом он "позовет ООМ киллера".
Флаг FADV_DONTNEED в этой реализацци, это не костыль от падения, в скорее просто гарнатия того что архив не сожрет 90% озу, и системе останется ресурсы для бизнес-логики.
Вызов Sync на каждый чанк блокирует горутину, лучше сбрасывать буферы асинхронно через отдельный воркер, тогда ядро стриминга вообще не заметит задержек диска
Как я уже отвечал на другой комментарий, если бы Sync дергался в гланом цикле стриминга - то да вы бы были правы. Но именно поэтому сам рекордер, это отдельная асинхронная горутина. Ядро раздает всем всем воркерам кадры через неблокирующие каналы. Если диск тупит и Sync повиснет, ядро этого просто не замечает. Канал рекордера забивается = дропаем кадры только для него. Все остальные подписчики работают без задержек.

Пишем терабайты видео на диск в Go и не даем ОС сожрать всю память