← Назад к вопросам

Какая ошибка в работе запомнилась больше всего?

2.0 Middle🔥 141 комментариев
#Другое

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

🐛 Самая памятная ошибка: Deadlock в системе кеширования с двойной проверкой (Double-Checked Locking)

Больше всего мне запомнилась ошибка deadlock в системе распределённого кеширования, которую мы разрабатывали для высоконагруженного финансового приложения. Ошибка была коварна тем, что проявлялась только под высокой нагрузкой (30k+ RPS) и влезала в код, который "точно должен был работать".

🔍 Контекст и проблема

Система использовала паттерн Double-Checked Locking для ленивой инициализации объектов кеша в памяти. Код выглядел так:

public class CacheService
{
    private static readonly object _lockObject = new object();
    private static Dictionary<string, CacheItem> _cache;

    public CacheItem GetOrAdd(string key, Func<CacheItem> factory)
    {
        if (_cache == null)
        {
            lock (_lockObject)
            {
                if (_cache == null)
                {
                    _cache = LoadCacheFromDatabase(); // Долгая операция
                }
            }
        }
        
        lock (_cache) // 🔴 ПРОБЛЕМА ЗДЕСЬ
        {
            if (_cache.TryGetValue(key, out var item))
                return item;
                
            item = factory();
            _cache[key] = item;
            return item;
        }
    }
}

💥 Что происходило?

  1. Поток A захватывает _lockObject и начинает инициализацию _cache
  2. Поток B видит, что _cache != null (после присваивания ссылки, но до полной инициализации)
  3. Поток B пытается захватить lock (_cache) — но объект находится в нестабильном состоянии
  4. Поток A внутри LoadCacheFromDatabase() делает callback, который тоже пытается получить элемент из кеша
  5. Взаимная блокировка: поток A ждёт поток B, поток B ждёт поток A

🧠 Почему ошибка была критичной?

  • Нерепроизводимость в тестах: в dev-среде нагрузка была недостаточной
  • Каскадный эффект: deadlock в кешировании блокировал весь кластер приложений
  • Потеря данных: сбой происходил при пиковых нагрузках, когда система была наиболее важна

🔧 Решение проблемы

Мы исправили это несколькими слоями:

public class CacheService
{
    private static readonly Lazy<ConcurrentDictionary<string, CacheItem>> _lazyCache 
        = new Lazy<ConcurrentDictionary<string, CacheItem>>(
            () => new ConcurrentDictionary<string, CacheItem>(LoadCacheFromDatabase()),
            LazyThreadSafetyMode.ExecutionAndPublication
        );

    private static ConcurrentDictionary<string, CacheItem> Cache => _lazyCache.Value;

    public CacheItem GetOrAdd(string key, Func<CacheItem> factory)
    {
        // ConcurrentDictionary обеспечивает потокобезопасность
        return Cache.GetOrAdd(key, _ => factory());
    }
}

📚 Выводы и уроки

Эта ошибка научила меня нескольким важным принципам:

  1. Избегайте блокировок на публичных объектах

    • Блокировка на объекте, который доступен извне, опасна
    • Используйте приватные объекты только для синхронизации
  2. Инициализация должна быть атомарной

    • Если объект опубликован, он должен быть полностью инициализирован
    • Паттерн Lazy<T> решает большинство таких проблем
  3. Тестируйте под нагрузкой

    • Unit-тесты недостаточны для многопоточных сценариев
    • Обязательно проводите нагрузочное тестирование
  4. Используйте готовые потокобезопасные коллекции

    • ConcurrentDictionary, ConcurrentQueue и др. избавляют от многих проблем
    • Они оптимизированы и хорошо протестированы
  5. Мониторинг deadlock-ов

    • Мы внедрили Health Checks с таймаутами
    • Добавили логирование всех операций блокировки длительнее >100ms

Эта ошибка стоила нам 4 часов простоя в пиковый час, но стала бесценным уроком. С тех пор я всегда:

  • Анализирую все lock-операции на возможность взаимной блокировки
  • Предпочитаю lock-free алгоритмы где это возможно
  • Использую Monitor.TryEnter с таймаутами в критических секциях
  • Провожу код-ревью многопоточного кода с особым вниманием