멱등성 설명: 신뢰할 수 있는 시스템 설계 안내
Wenhao Wang
Dev Intern · Leapcell

추상적 개념
멱등성은 수학 및 컴퓨터 과학에서 흔히 볼 수 있는 개념으로, 추상 대수학에서 주로 사용됩니다.
프로그래밍에서 멱등 연산은 여러 번 수행해도 한 번 수행한 것과 동일한 효과를 갖는다는 특징이 있습니다. 멱등 함수 또는 메서드는 동일한 매개변수로 반복적으로 실행해도 동일한 결과를 반환하는 함수 또는 메서드입니다. 이러한 함수는 시스템 상태를 변경하지 않으며, 반복적인 실행으로 인해 의도치 않은 변경이 발생하지 않습니다. 예를 들어, getUsername()
및 setTrue()
와 같은 함수는 멱등성을 갖습니다.
간단히 말해, 연산을 아무리 많이 실행해도 그 효과나 반환 결과는 동일하게 유지됩니다.
예시:
- 프론트엔드 폼이 여러 번 제출되더라도 백엔드는 하나의 결과만 생성해야 합니다.
- 결제 요청을 시작할 때, 사용자의 계정은 한 번만 차감되어야 합니다. 네트워크 재시도 또는 시스템 버그로 인해 여러 번 제출되는 경우에도 단 한 번의 차감만 발생해야 합니다.
- 메시지를 보낼 때, 한 번만 보내야 합니다. 동일한 SMS가 여러 번 전송되면 사용자가 불편을 느낄 수 있습니다.
- 비즈니스 주문을 생성할 때, 각 요청은 하나의 주문만 생성하여 중복 레코드를 방지해야 합니다.
멱등성이 필수적인 시나리오는 이 외에도 많이 있습니다.
멱등성 구현을 위한 기술
읽기 (쿼리) 연산
쿼리 연산은 한 번 실행하든 여러 번 실행하든, 기본 데이터가 변경되지 않는 한 동일한 결과를 반환합니다. 이로 인해 SELECT
쿼리는 자연스럽게 멱등성을 갖습니다.
삭제 연산
삭제 연산 또한 멱등성을 갖습니다. 삭제를 한 번 실행하든 여러 번 실행하든, 데이터는 제거됩니다. (단, 반환 값은 다를 수 있습니다. 데이터가 없는 경우 삭제는 0을 반환하고, 여러 레코드가 있는 경우 여러 행이 영향을 받습니다.)
더티 데이터 방지를 위한 고유 인덱싱
예를 들어, 각 사용자가 하나의 금융 계좌만 가져야 하는 금융 시스템에서 단일 사용자에 대해 여러 계좌가 생성되는 것을 어떻게 방지할 수 있을까요?
금융 계좌 테이블의 사용자 ID 필드에 고유 인덱스를 추가하면 계좌 생성 시도 시 하나의 요청만 성공합니다. 이후의 모든 요청은 org.springframework.dao.DuplicateKeyException
과 같은 고유 제약 조건 위반 오류를 발생시킵니다. 시스템은 데이터베이스를 다시 쿼리하여 데이터가 이미 존재하는지 확인하고 적절한 결과를 반환할 수 있습니다.
중복 폼 제출 방지를 위한 토큰 메커니즘
요구 사항: 페이지의 데이터는 한 번만 제출되어야 합니다.
원인: 중복 클릭, 네트워크 재시도 또는 Nginx 재시도에 의해 트리거된 재제출로 인해 동일한 데이터가 여러 번 처리될 수 있습니다.
해결 방법:
- 클러스터 환경: Redis와 결합된 토큰을 사용합니다.
- 단일 JVM 환경: Redis와 결합된 토큰 또는 JVM 메모리에 저장된 토큰을 사용합니다.
프로세스:
- 데이터 제출 전에 서비스에서 유효 기간과 함께 Redis 또는 JVM 메모리에 저장되는 토큰을 요청합니다.
- 제출 시 백엔드는 토큰을 검증하고 삭제하며, 다음 요청을 위해 새 토큰을 생성합니다.
토큰 특성:
- 제출 전에 요청해야 합니다.
- 한 번만 사용할 수 있습니다.
- 속도 제한 메커니즘으로 작용할 수 있습니다.
중요: Redis는 토큰을 검증하기 위해 삭제 연산을 사용해야 합니다. 삭제가 성공하면 토큰이 유효한 것으로 간주됩니다. SELECT + DELETE
를 사용하면 동시성 문제가 발생할 수 있으므로 권장하지 않습니다.
비관적 잠금
데이터를 검색할 때 잠금 획득:
SELECT * FROM table_xxx WHERE id='xxx' FOR UPDATE;
참고: id
필드는 기본 키 또는 고유 인덱스여야 합니다. 그렇지 않으면 쿼리가 전체 테이블을 잠그어 성능 문제가 발생할 수 있습니다.
비관적 잠금은 일반적으로 트랜잭션과 함께 사용되며, 트랜잭션이 지속되는 동안 데이터가 잠겨 있습니다. 잠금이 오래 유지될수록 성능에 미치는 잠재적 영향이 크므로 특정 요구 사항에 따라 사용하십시오.
낙관적 잠금
낙관적 잠금은 업데이트 시점에만 데이터를 잠그므로 다른 시간에는 불필요한 잠금을 피할 수 있습니다. 비관적 잠금에 비해 효율성이 더 높습니다.
낙관적 잠금을 구현하는 방법에는 여러 가지가 있으며, 버전 번호 또는 조건 기반 제약 조건을 사용하는 방법이 있습니다.
-
버전 번호 접근 방식:
UPDATE table_xxx SET name=#name#, version=version+1 WHERE version=#version#
-
조건 기반 접근 방식:
UPDATE table_xxx SET avai_amount=avai_amount-#subAmount# WHERE avai_amount-#subAmount# >= 0
avai_amount - subAmount >= 0
조건은 버전 번호 없이도 안전한 업데이트를 보장합니다.- 이는 재고가 차감되고 필요에 따라 롤백되는 재고 모델에 이상적입니다.
- 더 나은 성능을 제공합니다.
모범 사례:
-
테이블 잠금을 방지하려면
UPDATE
쿼리에서 기본 키 또는 고유 인덱스를 사용하십시오. 위의 쿼리는 다음과 같이 수정해야 합니다.UPDATE table_xxx SET name=#name#, version=version+1 WHERE id=#id# AND version=#version# UPDATE table_xxx SET avai_amount=avai_amount-#subAmount# WHERE id=#id# AND avai_amount-#subAmount# >= 0
분산 잠금
분산 시스템에 데이터를 삽입할 때, 전역적으로 고유한 인덱스를 강제하기 어려울 수 있습니다 (예: 분산 노드 전체에서 보편적인 고유 키가 부족함).
이러한 경우 Redis 또는 Zookeeper를 사용하여 분산 잠금을 구현하여 동시 데이터 삽입 또는 업데이트를 관리할 수 있습니다. 시스템은 잠금을 획득하고 작업을 수행한 다음 잠금을 해제합니다. 이 접근 방식은 동시 쓰기를 방지하는 데 도움이 되며 분산 시스템에서 일반적인 솔루션입니다.
SELECT + INSERT
패턴
낮은 동시성 또는 예약된 작업이 있는 백엔드 시스템의 경우 작업을 진행하기 전에 작업이 이미 실행되었는지 확인하여 멱등성을 보장할 수 있습니다.
- 먼저 데이터베이스에서 중요한 데이터를 쿼리하여 작업이 이미 실행되었는지 확인합니다.
- 실행되지 않은 경우 처리를 진행합니다.
참고: 높은 동시성 시나리오에서는 이 접근 방식을 사용하지 마십시오.
상태 머신 멱등성
주문 처리 또는 작업 실행과 관련된 비즈니스 애플리케이션에서는 상태 전환을 신중하게 관리해야 합니다.
- 비즈니스 레코드에는 일반적으로 상태 필드가 있으며, 전환은 미리 정의된 유한 상태 머신을 기반으로 발생합니다.
- 요청이 이전 상태에서 이미 업데이트된 상태로 전환하려고 시도하면 거부되어야 합니다.
- 이는 상태 전환에서 멱등성을 보장합니다.
핵심 고려 사항: 상태가 시간에 따라 진화하는 주문 기반 워크플로의 경우 강력한 비즈니스 시스템을 설계하려면 상태 머신에 대한 강력한 이해가 필수적입니다.
API 호출에서 멱등성 보장
예를 들어, 결제 API를 제공하는 은행은 가맹점이 요청에 두 개의 필드를 포함하도록 요구합니다.
source
: 요청의 출처입니다.seq
: 고유한 시퀀스 번호입니다.
데이터베이스에서 source + seq
에 고유 인덱스를 적용하면 중복 결제를 방지하여 동시 시나리오에서 하나의 요청만 처리되도록 할 수 있습니다.
핵심 사항:
외부 API에서 멱등성을 지원하려면:
- 두 개의 핵심 필드 필요:
source
및seq
. - 서비스 제공자의 시스템에서 이러한 필드에 대한 고유 제약 조건을 적용합니다.
- 요청을 처리하기 전에 이미 처리되었는지 확인합니다.
- 이미 처리된 경우 기존 결과를 반환합니다.
- 처리되지 않은 경우 실행을 진행합니다.
모범 사례: 새 레코드를 삽입하기 전에 요청이 처리되었는지 항상 쿼리하십시오. 확인 없이 데이터를 직접 삽입하면 작업이 이미 성공했더라도 오류가 발생할 수 있습니다.
결론
멱등성은 좋은 소프트웨어 설계의 기본 원칙입니다. 특히 타사 결제 플랫폼, 은행 및 핀테크와 같이 정확성이 가장 중요한 산업에서 높은 안정성 시스템을 설계할 때 중요한 고려 사항입니다. 중복 차감 또는 다중 지급과 같은 문제는 수정하기가 매우 어렵고 사용자 경험에 큰 영향을 미칠 수 있습니다.
개발자는 멱등 연산을 구현하여 일관성, 정확성 및 원활한 사용자 경험을 보장하는 강력하고 내결함성 시스템을 구축할 수 있습니다.
저희 Leapcell은 백엔드 프로젝트 호스팅을 위한 최고의 선택입니다.
Leapcell은 웹 호스팅, 비동기 작업 및 Redis를 위한 차세대 서버리스 플랫폼입니다.
다국어 지원
- Node.js, Python, Go 또는 Rust로 개발하십시오.
무제한 프로젝트를 무료로 배포
- 사용량에 대해서만 비용을 지불하십시오 - 요청 없음, 요금 없음.
탁월한 비용 효율성
- 유휴 요금 없이 사용한 만큼만 지불하십시오.
- 예: 25달러로 평균 응답 시간 60ms에서 694만 건의 요청을 지원합니다.
간소화된 개발자 경험
- 간편한 설정을 위한 직관적인 UI.
- 완전 자동화된 CI/CD 파이프라인 및 GitOps 통합.
- 실행 가능한 통찰력을 위한 실시간 메트릭 및 로깅.
손쉬운 확장성 및 고성능
- 고도의 동시성을 쉽게 처리할 수 있도록 자동 확장됩니다.
- 운영 오버헤드가 전혀 없습니다. 빌드에만 집중하십시오.
설명서에서 자세히 알아보십시오!
X에서 팔로우하십시오: @LeapcellHQ