Параллельный запрос к нескольким репликам БД

Отправьте запросы всем репликам и возвращайте первый ответ без ошибки. Остальные отмените общим context; ErrNotFound не должен завершать поиск раньше успеха.

Короткий ответ

Что ответить на собеседовании

Отправьте запросы всем репликам и возвращайте первый ответ без ошибки. Остальные отмените общим context; ErrNotFound не должен завершать поиск раньше успеха.

Подробный разбор

Ответ с пояснениями

Условие

Имеются три адреса реплик базы данных. Необходимо написать функцию, которая одновременно (в параллельных горутинах) запрашивает значение по заданному ключу у каждой из реплик. Как только одна из реплик возвращает корректный результат (значение, отличное от ErrNotFound и без ошибок), функция должна сразу же вернуть полученное значение и отменить ожидание остальных запросов.

127.0.0.1  
127.0.0.2  
127.0.0.3

Для работы предоставлен следующий метод (его реализация уже есть):

var ErrNotFound = errors.New("not found")

func Get(ctx context.Context, address, key string) (string, error) {
    return "", nil // already implemented
}

Ответ

Отправьте запросы всем репликам и возвращайте первый ответ без ошибки. Остальные отмените общим context; ErrNotFound не должен завершать поиск раньше успеха.

Вариант для Go 1.20+ с существующими Get и ErrNotFound:

func GetFirst(ctx context.Context, addresses []string, key string) (string, error) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    type reply struct { value string; err error }
    results := make(chan reply, len(addresses))
    for _, address := range addresses {
        go func(address string) {
            value, err := Get(ctx, address, key)
            results <- reply{value, err}
        }(address)
    }
    failures := make([]error, 0, len(addresses))
    onlyMissing := true
    for range addresses {
        select {
        case <-ctx.Done():
            return "", ctx.Err()
        case r := <-results:
            if r.err == nil { return r.value, nil }
            failures = append(failures, r.err)
            if !errors.Is(r.err, ErrNotFound) { onlyMissing = false }
        }
    }
    if onlyMissing { return "", ErrNotFound }
    return "", errors.Join(failures...)
}

Нужны context и errors. Буфер позволяет завершить отправку после раннего возврата; отмена реально прервёт Get только при её поддержке. Быстрейшая реплика может быть отставшей: эта стратегия не обеспечивает чтение самой свежей версии. Пустой адресный список здесь возвращает ErrNotFound.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее