Долгое чтение из буферизированного канала
Есть код, который загружает документы с внешнего сервера попутно делая их обработку, перед тем как записать их в текстовые поля БД MariaDB.
Код архитектурно разделен на две части: синхронную и асинхронную. Синхронная делает запросы к сайту и получает документ. Получив ответ она передает считывание тела документа горутине (на каждый док своя) и делает новый запрос. Асинхронная часть читает тело и передает его на обработку другим субгорутинам. Вишенка на торте - запись всех результатов в БД.
Запись делается отдельной горутиной (так сделано после того, как я стал получать от БД 1205-ую ошибку из-за очереди запросов, превысивших таймаут "Lock wait timeout exceeded"), которая представляет из себя цикл по каналу, куда предыдущие горутины пишут обработанный документ.
Проблема: пока цикл запроса документов работает (он, естественно, немного опережает обработку документов в горутинах) все работает нормально. Как только он закончился и как только все горутины (кроме финальной - канал апдейта) - отработают, горутина апдейта (канал уже закрыт) начинает вычитывать из канала оставшиеся там документы - все резко падает по скорости. После запроса 5000 документов в канале апдейта (его размер равен общему числу запрошенных документов) в конце остается порядка 300-400 невычитанных документов, которые начинают вдруг крайне медленно записываться. И в чем тормоз - БД, диск или канал - я не могу понять. Но время записи этого остатка документов порядка 4 часов. Тогда как на успевшие запроситься\обработаться\записаться 4500 документов ушло всего 3 часа.
// запуск главной горутины в main функции
chanDoc := make(chan Doc, len(docs))
go app.Download(docs, done, chanDoc) // здесь цепочка функций: RequestDocument (синхронная)-> Load (горутина)-> PullDaemons(некоторое число субгорутин)
//все горутины ожидаются в конце цикла запроса доков в функции Download
// там же и закрывается канал апдейта chanDoc
app.ChanUpdating(chanDoc) // здесь цикл чтения из канала - апдейт текстовых полей в БД (запись текство документов)
----------------------------------
// запись (функция Load) в канал апдейта документа после того как отработаны все предыдущие действия по его обработке
chanDoc <- *doc
doc.Reset()
poolDoc.Put(doc)
----------------------------------------------------------
// канал апдейта
func (app *App) ChanUpdating(chanDoc chan Doc) {
for doc := range chanDoc {
app.UpdateFields(doc)
}
}
func (app *App) UpdateFields(doc Doc) {
errUpdate := app.mysql.Updates(
app.mysql.DSN.TableName,
"itemid = ?",
[]interface{}{doc.ItemId},
map[string]interface{}{
"uploaded": doc.Uploaded,
"orig": doc.Orig,
"ru": doc.Tr["ru"],
"uk": doc.Tr["uk"],
},
)
if errUpdate != nil {
//atomic.AddInt32(&app.sum.ErrorsUpdating, 1)
app.sum.ErrorsUpdating++
log.Errorf("[UPDATE LOAD] [%03s] %s %s",
//red(atomic.LoadInt32(&app.sum.ErrorsUpdating)),
red(app.sum.ErrorsUpdating),
errUpdate.Error(),
doc.BaseName)
// удаляем файл
errPath := os.Remove(doc.FilePath)
if errPath != nil {
log.Errorf("[REMOVE FILE] %s %s",
errPath.Error(),
doc.BaseName)
}
} else {
//atomic.AddInt32(&app.sum.Updated, 1)
app.sum.Updated++
log.Infof("[UPDATE LOAD] [%05s] %s ",
//green(atomic.LoadInt32(&app.sum.Updated)),
green(app.sum.Updated),
doc.ShortName)
}
}
Ответы (1 шт):
По итогам расследования было выяснено, что проблема кроется в MySQL, он постоянно занят операциями update, которые длятся очень долго. Оказалось, что процесс работы построен так: Сначала в БД создаются все документы, в них заполняются только метаданные. После обработки программой эти записи изменяются, в них добавляется большой блок данных (текст документа).
Таблица использует движок InnoDB. В нем записи разложены в виде страниц индекса, построенного в порядке первичного ключа. При обновлении записи ее длина сильно увеличивается, резерва места на странице на хватает и запись приходится переносить. А вместе с этим приходиться перестраивать индекс и возможно переносить другие записи. Что в свою очередь требует интенсивного обращения к диску.
Итого: необходимо пересмотреть алгоритм, что бы избежать обновления (с увеличением размера) записей. В идеале вставлять в таблицу полностью готовую запись один раз.