sync.Map: Почему эта «серебряная пуля» иногда пробивает дно производительностиКак только новичок ловит свою первую панику
fatal error: concurrent map writes, он тут же гуглит проблему, находит sync.Map и радостно заменяет все свои словари на него. Казалось бы, идеальное решение из коробки.Но если вы откроете исходники стандартной библиотеки Go, то увидите, что сами разработчики языка используют
sync.Map крайне редко. В 95% случаев обычный map + sync.RWMutex порвет sync.Map по производительности.Давайте разберем, как эта штука устроена под капотом и почему она может тормозить ваш сервис.
Архитектура двух корзин
Внутри
sync.Map лежат не одна, а две мапы:1.
read: Доступна для чтения вообще без блокировок.2.
dirty: Защищена классическим мьютексом.Когда вы делаете
Load(key), Go сначала бежит в read. Если ключ найден - супер, мы прочитали его lock-free.Но если ключа там нет, Go вынужден брать мьютекс и искать его в
dirty мапе. Это называется cache miss. Если промахов становится слишком много, sync.Map принимает тяжелое решение: он берет всю dirty мапу и копирует её в read.Когда
sync.Map - это катастрофа?• Частая запись новых ключей: Каждый новый ключ попадает в
dirty. Это вызывает постоянные промахи при чтении, мьютекс постоянно лочится, а затем происходит дорогое копирование всей мапы. Вы получаете двойной удар по CPU.• Удаление и перезапись: Стандартный
RWMutex справится с этим гораздо эффективнее.Когда
sync.Map - это шедевр? (Два идеальных юзкейса)Официальная документация выделяет ровно два сценария, где этот тип оправдан:
1. Append-only кэши: Когда записи добавляются один раз и живут вечно (например, кэш скомпилированных регулярных выражений или конфигураций).
2. Disjoint keys (Разделенные ключи): Когда у вас есть 1000 горутин, и каждая читает/пишет строго в свой собственный ключ, никогда не пересекаясь с соседями.
🔥 Senior Tip: Sharded Map (Шардирование)
Что делать, если у вас кэш на 5 миллионов записей, куда постоянно идут конкурентные чтение и запись?
RWMutex станет узким местом (будет блокировать всю мапу целиком).В высоконагруженном проде используют Шардирование. Вы создаете слайс из 256 обычных мап, у каждой из которых свой маленький мьютекс. Хэш от ключа определяет, в какую именно мапу пойдет запрос. Это снижает конкуренцию за лок в 256 раз! (Готовые решения:
orcaman/concurrent-map или alphadose/haxmap).#golang #concurrency #architecture #performance #underhood
📲 Мы в MAX
👉 @golang_lib