Долгое чтение из буферизированного канала

Есть код, который загружает документы с внешнего сервера попутно делая их обработку, перед тем как записать их в текстовые поля БД 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 шт):

Автор решения: Mike

По итогам расследования было выяснено, что проблема кроется в MySQL, он постоянно занят операциями update, которые длятся очень долго. Оказалось, что процесс работы построен так: Сначала в БД создаются все документы, в них заполняются только метаданные. После обработки программой эти записи изменяются, в них добавляется большой блок данных (текст документа).

Таблица использует движок InnoDB. В нем записи разложены в виде страниц индекса, построенного в порядке первичного ключа. При обновлении записи ее длина сильно увеличивается, резерва места на странице на хватает и запись приходится переносить. А вместе с этим приходиться перестраивать индекс и возможно переносить другие записи. Что в свою очередь требует интенсивного обращения к диску.

Итого: необходимо пересмотреть алгоритм, что бы избежать обновления (с увеличением размера) записей. В идеале вставлять в таблицу полностью готовую запись один раз.

→ Ссылка