반응형

📝INSERT, UPDATE, DELETE, SELECT 부하순서

// 일반적 부하순서
DELETE > UPDATE > INSERT > SELECT
  1. DELETE
    • 데이터를 삭제할 때는 실제 삭제 외에도 인덱스 갱신, 참조 무결성(FK) 체크, 트리거 실행, 로그 기록 등이 발생
    • 대량 DELETE는 물리적으로 데이터를 지우고 공간을 재정리해야 하므로 부하가 큼 
  2. UPDATE
    • 데이터를 수정할 때는 읽기 + 쓰기 작업이 모두 필요
    • 인덱스가 걸린 컬럼을 수정하면 인덱스 갱신 작업이 필요하므로 부하가 커질 수 있음
  3. INSERT
    • 인덱스가 많은 테이블에 INSERT하면 인덱스를 모두 갱신해야 하므로 부하가 커질 수 있지만, 일반적으로 UPDATE보다 비용이 낮음
    • PK 충돌 체크, 제약조건(FK, UNIQUE) 체크 과정은 발생
  4. SELECT
    • 단순 SELECT는 읽기 작업만 수행하므로 상대적으로 부하가 적음
    • 복잡한 JOIN, 대량의 데이터 스캔, 인덱스 미사용, 서브쿼리, 정렬(SORT) 등이 있으면 SELECT가 가장 무거워질 수도 있음

 

📝 트랜잭션이 길어지면 생기는 일 (UnDo, ReDo)

트랜잭션을 길게 유지하면 UnDo나 ReDo에 트랜잭션 롤백하기 위한 데이터들이 계속 쌓이고 유한하기 때문에 성능상 이슈가 생길 수 있으니 트랜잭션의 경우 짧게 유지하고 처리하고 끝내야한다.

 

 

📝 DB 파일 시스템 (Page)

Page는 데이터베이스가 디스크에서 데이터를 읽고 쓰는 기본 단위입니다. (메모리에서 Page단위로 로드)

대부분의 DBMS는 디스크 I/O 성능 최적화를 위해 일정 크기의 블록으로 데이터를 관리합니다. (일반적으로 페이지 크기는 4KB, 8KB, 16KB) 

 

Page 안에는 레코드들이 들어가있습니다. 데이터를 1행 가져오더라도 페이지 단위로 메모리에 올립니다.

찾으려는 데이터가 페이지 하나에 다 있으면 좋은데 그렇지 않으면 이곳 저곳 페이지 단위를 다 올리기 때문에 조회 성능 이슈가 있습니다.

데이터 INSERT할때 순차적으로 들어가며 중간 데이터 삭제 되는 경우 비어져있습니다.

추후 DB 자체에서 VACCUM / OPTMIZE 작업을 하지만 부하가 있기 때문에 한가한 시간에 예약 실행 시키는 게 일반적입니다.

 

 

📝 DB락 (행, 페이지, 테이블, 범위)

DB락의 경우는 동시성 보장을 위해 락을 건다.

 

  • 행 락 (Row Lock)
    • 하나의 행(Row, 레코드)에만 걸리는 잠금
  • 페이지 락 (Page Lock)
    • 하나의 페이지(=디스크 I/O 단위, 보통 8KB)에 걸리는 잠금
  • 테이블 락 (Table Lock)
    • 전체 테이블에 락을 걸어 다른 트랜잭션이 읽기/쓰기 제한을 받게 함
  • 범위락 (Range Lock)
    • 조건에 해당하는 행 + 그 범위 내에 존재할 수 있는 잠재적 행들까지 락을 거는 방식 (현재 있는 행뿐 아니라, 그 범위에 새로 삽입될 행도 잠금 대상이 됨)

 

RDBMS 기본 락 범위
Oracle Row-level Lock
MySQL Row-level Lock
PostgreSQL Row-level Lock
MSSQL Row-level 또는 Page/Table 자동 승격

 

 

📝 DB락 에스컬레이션

DB 에스컬레이션은 락이 많아질 경우 DBMS가 너무 많은 수의 소규모 락을 관리하는 오버헤드를 줄이기 위해 자동으로 더 큰 범위의 락으로 전환하는 과정이 있습니다.

 

 

RDBMS 락 에스컬레이션
Oracle ❌ 지원하지 않음
SQL Server ✅ 지원함 (자동)
PostgreSQL ❌ 지원하지 않음
MySQL (InnoDB) ❌ 지원하지 않음
DB2 ✅ 지원함 (자동)
Sybase ASE 지원함

 

 

 

📝 분산락

분산락은 여러 서버에서 동시에 공유 자원에 접근하지 못하도록 제어하는 메커니즘으로 분산 시스템에서 데이터 정합성과 무결성을 보장하는 데 필수적입니다. 참고로 DB에서의 동시성 처리를 위해 MVCC라는 기능이 있어서 어느정도 동시성 처리를 하지만 추가적으로 분산락까지 적용시켜야 동시성을 완전히 제어할 수 있습니다.

 

예를 들면 쿠폰을 100개만 발급해야하는데 99개일 때 3개의 동시성 요청이 들어간 경우 102개의 쿠폰이 발급 됩니다.

격리수준이 높더라도 동시성 문제 4개에 해당 안 되는 동시성 문제(더티리드, 반복 불가능 읽기, 팬텀리드, 갱신손실)이기 때문에 따로 정합성을 위해 개발이 필요합니다.

 

 

주의 사항

  • 락 테이블을 가지고 직접 락 ID를 발급 받아서 검증을 해서 두번이상 안 들어가게끔 설계합니다. 
  • 이럴 경우 락 테이블은 Write DB 한군데에서만 처리해야합니다. (다른 DB에서 레플리카를 해도 정합성이 안 맞을 수 있음) 아래 방법은 DB 방식이지만 저러한 방식들로 구현되며 Redis를 이용한 락 관리를 많이 사용합니다.
  • 아래 예제에는 락 무한기간 점유에 대해서는 설계가 되어있지 않습니다.
    • 하트비트(락을 보유 중임을 주기적으로 연장하는 갱신으로 계속적으로 UPDATE를 보내 갱신), TTL 설정 필요
  • 아래 예제는 뒤 늦은 업데이트에 대한 처리도 필요합니다.
    • 펜싱토큰으로 처리합니다.
      • A가 락을 잡고 있다가 멈춤 → TTL 만료로 B가 선점 → A가 뒤늦게 깨어나 예전 락이라고 착각하고 쓰기를 시도.
      • TTL 기준으론 A·B 둘 다 ‘내가 주인’이라고 믿을 수 있음
      • 이때 토큰 비교로 B(큰 번호)의 작업만 통과, A(작은 번호)는 거부되어 데이터가 안전합니다
  • 다양한 상황에 따른 추가 작업을 고려해야합니다.

 

 

방법1

BEGIN;
SELECT * FROM lock_table WHERE resource_name='job1' FOR UPDATE;

-- 처리 로직 수행...

COMMIT;

쿠폰이 100개면 100개 행만큼 만든 이후에 DB에서 Row-Level로 행을 잠궈서 발급해 처리합니다.

 

 

특징

  • 매번 한정 개수일 때 미리 만들어야하는 번거로움이 있습니다.
  • 해당 행에 LOCK을 걸기 때문에 UPDATE 동시성에 대해서도 처리가 가능합니다.

 

 

방법2 (추천)

CREATE TABLE payments (
  payment_id       BIGSERIAL PRIMARY KEY,
  order_id         BIGINT NOT NULL,
  idempotency_key  TEXT   NOT NULL UNIQUE,
  status           TEXT   NOT NULL DEFAULT 'PENDING',
  amount           NUMERIC(12,2) NOT NULL,
  receipt_no       TEXT
);

CREATE TABLE key (
  key_id           BIGINT AUTO_INCREMENT PRIMARY KEY,
  idempotency_key  TEXT   NOT NULL UNIQUE
);

DB에서 제약조건을 걸어 검증을 하는 방법입니다.

payments에 데이터를 넣기 위해서는 무조건 idempotency_key를 key 테이블에서 발급 받아서 사용해야합니다.

 

 

진행 프로세스

  1. key 테이블에서 키(idempotency_key) 발급 (INSERT)
  2. 해당 key 테이블의 키를 payments 테이블 idempotency_key에 넣으며 결제 테이블(payments)에 넣습니다.

 

특징

  • 유니크 제약 조건이 있기 때문에 동일한 idempotency_key가 들어갈 수 없습니다.
  • INSERT에만 유효하고 UPDATE 동시성의 경우 다른 방법 처리가 더 필요합니다. (버전 정보 추가 기입 등...)

 

 

방법3

-- 1. 락 획득 (my_lock이라는 이름, 최대 10초까지 기다림)
SELECT GET_LOCK('my_lock', 10) AS lock_acquired;
-- (lock_acquired = 1 이면 락 성공, 0이면 타임아웃, NULL이면 에러)

... (프로세스 처리)

SELECT RELEASE_LOCK('my_lock') AS lock_released;
-- 락 해제

DB자체에서 락테이블 관리하고 DB에서 제공하는 함수들이 있습니다.

 

 

특징

  • 특정 LOCK의 이름을 가지고 처리 후에 반납하기 때문에 이미 점유중인 특정 LOCK의 이름을 또 가지고 오려고하면 락을 획득 못합니다.

 

📝 DB CRUD의 멱등성

동작 멱등성 여부 이유
C(Create) ✅ (조건부) 동일 데이터를 여러 번 INSERT하면 중복 행이 생김. (PK/Unique 제약으로 멱등성 확보 가능)
R(Read) 몇 번 조회하든 DB 상태 안 변함.
U(Update) ✅ (조건부) 같은 값을 여러 번 업데이트해도 결과가 동일. 하지만 UPDATE users SET balance=balance+100처럼 가변 연산이면 ❌
D(Delete) 같은 레코드 삭제를 여러 번 시도해도 결과는 "없음"으로 동일. (단, 오류 처리 방식에 따라 API 결과는 다를 수 있음)

 

 

🔗 참고 및 출처

https://hyunwook.dev/236

 

반응형