The operation was unable to achieve a quorum during its retry window

에러 메시지를 검색해서 들어온 분들에게 도움이 되지 않을까 생각이 들어 최근 짧게 겪은 문제에 대해 정리해본다.

사이드 프로젝트에서 수량이 한정된 이벤트 쿠폰을 발급받는 API가 있었다. 동시성을 제어하기 위해 redlock을 사용했고 Service 테스트 코드로 아래와 같이 Promise.all로 100개의 메서드를 호출했을 때 정상적으로 재고가 감소되는 것을 테스트했다. (아래는 service 함수를 100번 호출해서 재고가 정상적으로 감소했는지 확인하는 테스트 코드)

it('동시에 쿠폰 획득시 재고는 차례대로 감소한다.', async () => {
// given
const stock = 100;
const coupon = await repository.save(CouponFactory.eventCoupon(stock));
const user = await userRepository.save(UserFactory.of());
const concurrencyRequest = new Array(100)
.fill(undefined)
.map(() => service.giveCoupon(coupon.id, user.id));
// when
await Promise.all(concurrencyRequest); // 100개의 호출을 동시적으로 실행
// then
const savedCouponUsers = await couponUserRepository.find();
expect(savedCouponUsers.length).toBe(100);
const saveCoupon = await repository.findOneBy({ id: coupon.id });
expect(saveCoupon.stock).toBe(0);
});

헌데 이와 같은 에러 메시지를 보게 된다. ExecutionError: The operation was unable to achieve a quorum during its retry window.

이 에러로 검색되는 정보는 많지 않은데 github issue를 보면 버전이나 락을 거는 리소스 키, 락 대기시간 등등 많은 의견들이 오간다. 몇몇 수정해봤지만 해결이 되지 않아 버전을 바꿔보기로 결정했다. 내용 중 redlock, ioredis 버전 4에서는 정상 동작하는데 버전 5에서 에러가 발생하는걸로 보아 버그가 있는것 같다는 커멘트 때문이었다.

관련 패키지를(redlock, ioredis, @nestjs-modules/ioredis) 버전 4로 동작하도록 수정하고 테스트를 실행하니 다음과 같은 에러가 발생한다.

Exceeded 10 attempts to lock the resource

이 에러는 또 뭐지? 왜 10번일까? 고민중에 redlock 설정에 락을 얻기 위해 재시도하는 retrycount의 기본값이 10인 것을 생각했다.

redis service 코드에서 redlock을 생성할 때 retryCount를 늘리고 테스트를 해보니 성공하였다.

import { InjectRedis } from '@nestjs-modules/ioredis';
import { Injectable } from '@nestjs/common';
import Redis from 'ioredis';
import Redlock from 'redlock';
@Injectable()
export class RedisService {
private readonly redlock: Redlock;
private readonly lockDuration = 3_000;
constructor(@InjectRedis() private readonly redis: Redis) {
this.redlock = new Redlock([this.redis], { retryCount: 20 }); // 횟수 증가
}
async acquireLock(key: string) {
return this.redlock.acquire([`lock:${key}`], this.lockDuration);
}
}

두번째 에러가 발생한 이유는 100개의 동시 요청 중 하나의 요청이 락을 잠그고 로직을 수행하는 도중, 락에 의해 대기중인 요청들은 락을 획득하기 위해 재시도 횟수가 10번을 초과했기 때문이다.

여기까지 정리해보면 ExecutionError: The operation was unable to achieve a quorum during its retry window. 에러를 만나서 결국 패키지 버전을 낮추고 테스트를 해보니 Exceeded 10 attempts to lock the resource 를 만나 락을 획득하기 위한 retryCount를 수정함으로써 해결할 수 있었다.

그러면 혹시 최신 버전에서 처음 만난 에러도 동일한 문제가 아닐까? 비록 에러 메시지로는 유추하기 힘들지라도? 다시 redlock, ioredis 버전을 최신 버전으로 돌린뒤 retryCount는 기본 값 10에서 20으로 늘린뒤 테스트 해보았다.

정상적으로 테스트가 완료된다. 문제를 해결하며 느낀 한줄평을 적자면….
에러 메시지를 잘 작성하자.

Leave a Reply