글을 작성하게 된 계기
회사에서 대량의 데이터를 쓰기 작업 하면서 멱등성 있게 처리 하는 방법에 대해 고민하게 되었고, 이 과정에서 알게 된 내용을 정리하기 위해 글을 작성하게 되었습니다.
1. 왜 멱등성이 필요할까?
대량 데이터를 다루는 작업에서는 종종 네트워크 오류, 시스템 장애, 휴먼 에러 등의 이유로 일부 요청이 실패하는 상황이 발생합니다. 이런 경우에는 실패한 부분만 선별적으로 다시 처리 할 수도 있지만, 상황에 따라 전체 데이터를 처음부터 다시 처리해야 하는 경우 도 있습니다. 이때 멱등성이 보장되지 않는다면, 이미 성공했던 작업이 중복으로 처리되는 문제가 발생할 수 있습니다.
즉, 멱등성이 대량 쓰기 작업에서 중요한 이유는 단순히 중복 요청을 막는 것이 아닌, 장애나 부분 실패 상황에서 안전하게 재시도하거나 전체를 다시 처리할 수 있는 기반 이 되기 때문입니다. 동일한 요청을 몇 번 수행해도 결과가 변하지 않는다는 보장이 있어야, 시스템은 실패한 작업이나 전체 작업을 안심하고 다시 시도할 수 있고, 이를 통해 데이터의 일관성 과 신뢰성 을 유지할 수 있겠죠.
- 동일한 요청을 몇 번 수행해도 결과가 변하지 않는다는 보장
- 장애나 부분 실패 상황에서 안전하게 재시도하거나 전체를 다시 처리할 수 있는 기반
2. 멱등성을 보장하는 방법
멱등성을 보장하는 방법에는 여러 가지가 있지만 그중 데이터베이스 레벨 에서 사용할 수 있는 대표적인 방법에 대해 살펴보겠습니다.
- UNIQUE 제약 조건
- INSERT IGNORE
- INSERT … ON DUPLICATE KEY UPDATE
- 파일 데이터 멱등성
2-1. UNIQUE 제약 조건
데이터베이스 테이블에 특정 칼럼 또는 칼럼 조합에 대해 UNIQUE 제약 조건을 걸어 중복 데이터를 사전에 차단하는 방식입니다. 이미 존재하는 값이 들어오면 데이터베이스가 에러를 발생시켜 중복을 방지할 수 있습니다. 이는 데이터베이스 차원에서 일관성을 강하게 보장해 주기 때문에 가장 간단하고 확실한 방법으로, 다른 방법들과 함께 사용됩니다. 그러나 인덱스가 필요하고, 데이터 구조 설계에 영향을 주는 단점이 있습니다.
1
2
3
4
5
6
CREATE TABLE product
(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
product_code VARCHAR(50) NOT NULL UNIQUE, # UNIQUE 제약 조건
name VARCHAR(100) NOT NULL
);
2-2. INSERT IGNORE
INSERT IGNORE를 사용하면 UNIQUE 제약 조건이 걸려 있는 컬럼 에 대해 중복된 값이 들어오더라도 에러를 발생시키지 않고 무시하며 넘어갈 수 있습니다. 즉, 중복 데이터가 들어와도 삽입이 되지 않으며, 전체 구문이 실패하지 않고 처리가 계속됩니다. 단, 삽입 성공 여부를 명확하게 알기 어렵고, 무시된 건에 대해 별도 로깅이 필요할 수 있습니다.
1
2
INSERT IGNORE INTO product (product_code, name)
VALUES ('P001', '사과');
2-3. INSERT … ON DUPLICATE KEY UPDATE
INSERT … ON DUPLICATE KEY UPDATE 구문은 삽입하려는 데이터가 이미 존재하는 경우, 자동으로 업데이트로 대체하는 방식입니다. 하나의 쿼리로 INSERT와 UPDATE를 모두 처리할 수 있어 편리하며, 중복 데이터에 대해 유연하게 대응할 수 있습니다.
1
2
3
INSERT INTO product (product_code, name)
VALUES ('P001', '사과')
ON DUPLICATE KEY UPDATE name = '신규 사과';
그러나 잘못 사용하면 의도치 않은 값이 덮어씌워질 수 있기 때문에 주의가 필요합니다. 추가로 변경 이력 관리나 추가적인 로직이 필요한 경우에는 Trigger와 함께 사용하여 자동으로 로그를 남기거나 부가 작업을 처리할 수도 있습니다.
1
2
3
4
5
6
7
8
9
DELIMITER $$
CREATE TRIGGER trg_product_update
AFTER UPDATE ON product
FOR EACH ROW
BEGIN
INSERT INTO product_log (product_code, old_name, new_name)
VALUES (OLD.product_code, OLD.name, NEW.name);
END$$
DELIMITER ;
2-4. 파일 데이터 멱등성
대량 데이터를 이관할 때, MySQL에서 LOAD DATA INFILE 구문을 사용하여 데이터를 빠르게 삽입할 수 있습니다. 그러나 이 경우에도 멱등성을 보장하기 위해 중복 체크가 필요합니다. 이 경우도 마찬가지로 UNIQUE 제약 조건과 INGORE 옵션을 사용하면 중복된 데이터를 무시하고 삽입할 수 있습니다.
1
2
3
4
5
6
LOAD DATA INFILE '/path/to/product.csv'
INTO TABLE product
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n'
(product_code, name)
IGNORE 1 LINES;
3. 정리
대량의 데이터를 쓰는 작업은 단순해 보이지만, 실제로는 장애나 재처리 상황에서 매우 복잡해질 수 있습니다. 일부만 실패하거나 전체를 다시 처리해야 할 때를 대비해, 멱등성을 어떻게 보장할 것인지 미리 고민해 두는 것이 중요합니다. 이 외에도 REPLACE INTO 등 다양한 방법이 있는데, 상황에 맞게 잘 사용해봅시다.
MySQL을 예를 들어 설명했지만, 다른 데이터베이스에서 명칭만 다를 뿐 비슷한 방법이 존재합니다.