Я заметил, что элементы неправильно вытесняются в хеш-карте eBPF LRU (BPF_MAP_TYPE_LRU_HASH
). В следующем коде я вставляю в хеш-карту LRU размера 8 и печатаю ее содержимое каждую секунду:
package main
import (
"fmt"
"github.com/cilium/ebpf"
"log"
"time"
)
func main() {
spec := ebpf.MapSpec{
Name: "test_map",
Type: ebpf.LRUHash,
KeySize: 4,
ValueSize: 8,
MaxEntries: 8,
}
hashMap, err := ebpf.NewMap(&spec)
if err != nil {
log.Fatalln("Could not create map:", err)
}
var insertKey uint32
for range time.Tick(time.Second) {
err = hashMap.Update(insertKey, uint64(insertKey), ebpf.UpdateAny)
if err != nil {
log.Printf("Update failed. insertKey=%d|value=%d|err=%s", insertKey, insertKey, err)
}
var key uint32
var value uint64
count := 0
elementsStr := ""
iter := hashMap.Iterate()
for iter.Next(&key, &value) {
elementsStr += fmt.Sprintf("(%d, %d) ", key, value)
count++
}
log.Printf("Total elements: %d, elements: %s", count, elementsStr)
insertKey++
}
}
Когда я запускаю вышеуказанную программу, я вижу это:
2023/03/29 17:32:29 Total elements: 1, elements: (0, 0)
2023/03/29 17:32:30 Total elements: 2, elements: (1, 1) (0, 0)
2023/03/29 17:32:31 Total elements: 3, elements: (1, 1) (0, 0) (2, 2)
2023/03/29 17:32:32 Total elements: 3, elements: (3, 3) (0, 0) (2, 2)
...
Поскольку на карте восемь записей, я ожидал, что четвертая строка покажет четыре значения, но она показывает только три, потому что запись (1, 1)
была исключена.
Если я изменяю max_entries
на 1024, я замечаю, что эта проблема возникает после вставки 200-го элемента, но иногда это происходит и после этого. Это непоследовательно.
Эта проблема не ограничивается созданием/вставкой карты из пользовательского пространства, потому что я наблюдаю эту проблему в своей программе XDP, которая создает карту и вставляет в нее; приведенное выше воспроизводит проблему, которую я наблюдаю в своей реальной программе. В моей реальной программе, в которой также было 1024 элемента, я заметил, что эта проблема возникла после вставки 16-го элемента.
Я проверил это на наших производственных серверах с ядром Linux 5.16.7.
Я провожу тестирование на виртуальной машине Linux, обновляю ядро до версии 6.2.8 и вижу, что политика вытеснения отличается. Например, когда max_entries
равно 8, я наблюдаю следующее:
2023/03/29 20:38:02 Total elements: 1, elements: (0, 0)
2023/03/29 20:38:03 Total elements: 2, elements: (0, 0) (1, 1)
2023/03/29 20:38:04 Total elements: 3, elements: (0, 0) (2, 2) (1, 1)
2023/03/29 20:38:05 Total elements: 4, elements: (0, 0) (2, 2) (1, 1) (3, 3)
2023/03/29 20:38:06 Total elements: 5, elements: (4, 4) (0, 0) (2, 2) (1, 1) (3, 3)
2023/03/29 20:38:07 Total elements: 6, elements: (4, 4) (0, 0) (2, 2) (1, 1) (5, 5) (3, 3)
2023/03/29 20:38:08 Total elements: 7, elements: (4, 4) (0, 0) (2, 2) (1, 1) (6, 6) (5, 5) (3, 3)
2023/03/29 20:38:09 Total elements: 8, elements: (7, 7) (4, 4) (0, 0) (2, 2) (1, 1) (6, 6) (5, 5) (3, 3)
2023/03/29 20:38:10 Total elements: 1, elements: (8, 8)
...
Когда max_entries
равно 1024, я замечаю, что после добавления 1025-го элемента общее количество элементов равно 897. Я не могу протестировать ядро 6.2.8 на наших рабочих серверах.
Хеш-карта LRU не гарантирует, что существует точно максимальное количество элементов, и реализация явно направлена на обеспечение хорошей производительности с гораздо более чем 8 элементами. Что я вижу из довольно беглого взгляда на код:
LRU разделен на две части: «активный список» и «неактивный список», с задачей, которая периодически перемещает элементы из одной в другую в зависимости от того, был ли к ним доступ в последнее время. Это не настоящий LRU (элементы не перемещаются в голову каждый раз, когда к ним обращаются).
Когда карта заполнена и что-то нужно удалить, чтобы вставить новый элемент, код удалит до 128 элементов из неактивного списка за один проход; только если неактивный список пуст, он удаляет один элемент из активного списка.
Существует также «локальный свободный список» выделенных элементов для каждого ЦП, ожидающих заполнения данными; когда он пуст, он пытается извлечь из глобального списка свободных, и если он пуст, он переходит к пути выселения. Целевой размер локального бесплатного списка — 4 элемента.
Таким образом, поведение в 6.2.8 кажется простым и последовательным: предположительно, все ваши ключи находятся в «неактивном списке» (что не слишком удивительно для шаблона доступа сканирующего типа, или, возможно, просто ни у одного из них не было шанса получить повышение). пока), и все они выбрасываются. Я не так понимаю версию 5.16, но, вероятно, это связано с локальным свободным списком и всеми обновлениями, работающими на одном и том же процессоре.
По сути, я думаю, что тип данных не предназначен для использования так, как вы его используете, и ошибка заключается в ваших ожиданиях. Если вы не согласны, думаю, вам придется обсудить это с разработчиками ядра.
@user2233706 user2233706 Думаю, 256. Можете ли вы обновить свой вопрос, указав фактическое поведение, которое вы видите с 1024 max_entries? И ваше количество процессоров? Я не уверен, имеет ли это значение, но может.
(Если он по-прежнему удаляет все после 4 или 8 вставок, я бы назвал это ошибкой. Но если он заполняется, скажем, до 800, я бы сказал, что мой ответ по-прежнему актуален: это неточный LRU, и он работает в пакетном режиме для улучшения пропускная способность.)
В моей реальной программе максимальный размер составляет 1024, и я наблюдаю, что эта проблема возникает после добавления 16-й записи. Это на 5.16.7. Возможно, 6.2.8 ведет себя лучше, но я не могу протестировать это ядро на наших реальных серверах.
@user2233706 user2233706 Основываясь на вашем редактировании, я бы сказал, что поведение на 6.2 соответствует ожиданиям (на самом деле у вас осталось ровно 1025 - 128 = 897 элементов). Если этого достаточно для вас, то у вас есть причина для «это приложение требует обновления ядра». Если поведение версии 6.2 для вас неприемлемо, то вам либо нужен редизайн, чтобы не зависеть от карт хеш-функции BPF LRU, либо вам нужно убедить вышестоящий linux принять более медленный «точный LRU» патч... и тогда вам нужно обновление ядра :)
Глядя на журналы, я могу предположить, что изменение поведения между 5.16 и 6.2 не было преднамеренным «исправлением», а скорее побочным эффектом github.com/torvalds/linux/commit/…, который перешел в 6.1.
В моей реальной программе я наблюдаю эту проблему, когда
max_entries
равно 1024. Каково минимальное число дляmax_entries
, чтобы хэш-карта LRU была полезной? В приведенной выше программе я все еще вижу эту проблему, еслиmax_entries
равно 4096, хотя и в меньшей степени.