'좋아요' 두 번 누르기 원천 차단법
애플리케이션 코드를 믿지 않고, 데이터베이스 자체의 UNIQUE 제약조건을 통해 중복 '좋아요' 등의 데이터 오염을 원천 차단하는 방법을 배웁니다.
회원가입을 할 때 누군가 나와 똑같은 이메일로 가입하는 것을 막아야 합니다. 또한, 커뮤니티에서 하나의 게시글에 한 사람이 '좋아요'를 두 번, 세 번 연달아 눌러서 인기글을 조작하는 것도 막아야 합니다. 이 문제를 AI에게 해결해 달라고 하면 AI는 십중팔구 앱 코드(JavaScript나 Python) 상에서 먼저 검사하는 방식을 제안합니다. 하지만 이 방식에는 치명적인 구멍이 있습니다. 이번 시간에는 앱 코드를 절대 믿지 않고, 데이터베이스 단에서 절대적인 중복 차단 방벽을 세우는 `UNIQUE` 제약조건에 대해 알아봅니다.
앱 코드를 믿으면 안 되는 이유 (동시성 문제)
바이브코딩 AI에게 "한 사람이 같은 글에 좋아요를 한 번만 누를 수 있게 해줘"라고 하면 보통 이런 흐름의 로직(코드)을 짜줍니다.
- 프론트엔드: 버튼을 누르면 서버로 '좋아요' 요청을 보냅니다.
- 백엔드 서버: 데이터베이스에 가서 "이 사람이 이 글에 이미 좋아요를 누른 적이 있나?" 물어봅니다. (`SELECT`)
- 백엔드 서버: "아직 없네!"라는 대답을 들으면, 그제야 데이터베이스에 '좋아요' 기록을 새롭게 하나 추가합니다. (`INSERT`)
- 프론트엔드: '좋아요' 버튼을 회색으로 비활성화하여 더 이상 못 누르게 막습니다.
겉보기에는 아주 논리적이고 완벽해 보입니다. 그런데 만약 악의적인 유저가 해킹 프로그램을 써서 0.001초 간격으로 '좋아요' 요청을 10번 연속으로 우다다닥 보냈다면 어떻게 될까요? (또는 스마트폰이 느려져서 유저가 답답한 마음에 버튼을 마구 연타했을 수도 있습니다.)
찰나의 순간에 뚫리는 방어막 (Race Condition)
오해 또는 실수
0.000초
바로잡기
10개의 요청이 서버에 거의 동시에 도착합니다.
오해 또는 실수
0.001초
바로잡기
서버는 10개의 요청에 대해 각각 DB에 '이미 누른 적 있니?'라고 물어봅니다.
오해 또는 실수
0.002초
바로잡기
DB는 아직 새롭게 저장된 기록이 없으므로, 10개 모두에게 '아니, 누른 적 없어!'라고 대답합니다.
오해 또는 실수
0.003초
바로잡기
서버는 10개 요청 모두 합격 판정을 내리고, DB에 INSERT 명령을 10번 날립니다.
오해 또는 실수
결과
바로잡기
유저 한 명이 게시글 하나에 좋아요 10개를 눌러버린 치명적인 버그 데이터가 쌓입니다.
앱 코드는 여러 요청이 동시에 들어왔을 때 이렇게 속수무책으로 뚫려버립니다. 이를 전문 용어로 동시성 경쟁 상태(Race Condition)라고 부릅니다.
최후의 철벽 수비: `UNIQUE` 제약조건
이런 구멍을 완벽하게 틀어막으려면, 검사 로직을 백엔드 앱이 아니라 데이터베이스의 깊은 곳(DB 스키마 자체)에 박아두어야 합니다. "어떤 일이 있어도 이 칸에는 똑같은 값이 두 번 들어올 수 없다!"라고 데이터베이스 문지기에게 못을 박는 것이 바로 `UNIQUE` 제약조건입니다.
단일 컬럼 UNIQUE 적용 (이메일 중복 방지)
회원가입 시 이메일 중복을 막고 싶다면, `email` 컬럼에 `UNIQUE`를 걸어줍니다.
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(100) UNIQUE, -- 똑같은 이메일은 절대 두 번 들어갈 수 없습니다.
name VARCHAR(50)
);이렇게 해두면, 100명이 동시에 똑같은 이메일로 가입 요청을 우다다닥 보내도, 데이터베이스는 찰나의 순간에 오직 1명만 `INSERT`를 허락하고 나머지 99명에게는 "중복 데이터 에러(Unique Constraint Violation)!"를 뱉어내며 매몰차게 튕겨냅니다.
궁극의 콤보: 복합 UNIQUE (Composite Unique)
그렇다면 서두에서 말한 '좋아요' 문제는 어떻게 해결할까요? '좋아요' 테이블 구조를 상상해 봅시다. 어떤 유저(`user_id`)가 어떤 글(`post_id`)에 좋아요를 눌렀는지 기록합니다. 여기에 `user_id` 컬럼에 혼자 `UNIQUE`를 걸면, 그 유저는 일평생 단 하나의 글에만 좋아요를 누를 수 있게 됩니다. `post_id`에 걸면 그 글에는 세상에서 딱 한 명만 좋아요를 누를 수 있습니다.
우리가 원하는 조건은 "유저 혼자서는 여러 글을 누를 수 있고, 글 하나도 여러 유저에게 받을 수 있지만, [어떤 유저 + 어떤 글] 의 짝꿍 조합은 오직 세상에 하나만 존재해야 한다"는 것입니다. 이때 사용하는 것이 두 개 이상의 컬럼을 묶어서 중복을 검사하는 복합 UNIQUE입니다.
CREATE TABLE likes (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
post_id INT NOT NULL,
-- 이 두 개의 조합이 겹치는 데이터는 절대 삽입 불가!
UNIQUE (user_id, post_id)
);이 제약조건 한 줄만 테이블에 걸어두면, 프론트엔드가 버그를 내든 백엔드 코드가 부실하든 아무 상관이 없습니다. 누가 버튼을 1초에 100번 연타하더라도, 데이터베이스는 첫 번째 요청만 1개의 좋아요로 기록하고 나머지 99개는 데이터베이스 엔진 차원에서 강력하게 튕겨내 버립니다. 이것이 데이터 무결성(Data Integrity)을 완벽하게 지켜내는 진정한 방어 기술입니다.
개념 퀴즈
퀴즈 답을 맞춰야 학습 완료가 됩니다.새로운 비밀번호 없이 아이디(username)만으로 로그인하는 게시판을 바이브코딩으로 만들었습니다. 악의적인 유저가 해킹 프로그램을 사용해 똑같은 아이디 2개로 동시에 가입 요청을 보냈을 때, 백엔드 서버의 코드를 무사히 통과하더라도 최종 저장소에서 이를 절대 허락하지 않고 에러를 뱉어내게 만드는 데이터베이스의 강력한 안전장치는 무엇인가요?
3줄 요약
- 1동시 다발적으로 들어오는 요청을 앱 코드(프론트/백엔드) 로직만으로 검증하면, 찰나의 순간에 중복 데이터가 뚫고 들어오는 심각한 버그가 발생합니다.
- 2특정 데이터가 절대로 중복되지 않도록 데이터베이스 자체 스키마에 박아두는 철벽 방어막이 바로 UNIQUE 제약조건입니다.
- 3'한 사람이 한 글에 좋아요를 한 번만 누를 수 있다'와 같은 규칙은 user_id와 post_id 두 컬럼을 하나로 묶어 복합 UNIQUE를 걸어줌으로써 완벽히 해결됩니다.