항해14기 본과정/항해14기 개발일지

[항해 14기] 개발일지28 (Transaction, Returning)

스쿼트잘함 2023. 5. 4. 20:56

Node.js 심화

 

1.Transaction

1) 개요

- 작업의 완전성을 보장해주기 위해 사용되는 개념. 데이터베이스에서 특정한 작업을 전부 처리하거나, 전부 실패하게 만들어 데이터의 일관성을 보장해주는 기능
- 트랜잭션(Transaction)을 사용하는 대표적인 이유는 작업의 단위를 하나의 쿼리에 종속하는 것이 아닌, 여러개의 작업(쿼리)을 묶어 하나의 작업 단위로 그룹화하여 처리

2) 특징(ACID)

- A : 원자성(Atomicity)
트랜잭션 내에서 실행되는 명령들을 하나의 묶음으로 처리하여, 내부에서 실행된 명령들이 전부 성공 또는 전부 실패 해야한다는 특징
- C : 일관성(Consistency)

트랜잭션 내부에서 처리되는 데이터의 일관성을 유지해야하는 특징. ROLLBACK을 통하여 작업이 실패하여도 실패한 상태의 데이터를 방치하지 않고 유지시켜줌
- I : 격리성(Isolation)

트랜잭션을 수행하는 중간 상태를 보거나 변경할 수 없도록 구성하는 특징. Lock을 걸어 격리성을 구현, 동시성(여러명의 클라이언트가 하나의 데이터를 동시에 사용 및 공유하는 것) 문제를 방지

- D : 지속성(Durability)
트랜잭션을 성공적으로 수행하면 수정된 데이터를 시스템에 영구적으로 적용하는 특징. 트랜잭션의 중간 결과가 아닌 완성된 결과만 저장하여 데이터베이스에 이상이 생기더라도 자동 복구 할 수 있는 특성
ex) : 어플리케이션이 트랜잭션을 완료한 이후, DB에 COMMIT을 요청하였지만 변경사항이 반영되기 전에 DB가 비정상 종료될 경우, DB가 재시작 될 때 트랜잭션의 변경 사항을 다시 반영하게 됨

 

3) Lock

- 동시성을 제어하기 위해 사용하는 기능. 해당하는 데이터를 점유하여 다른 트랜잭션의 접근을 막아 동시성과 일관성을 유지하기 위해 사용

- 하나의 데이터를 여러 사용자들이 동시에 변경하려고 할 때 락이 존재하지 않다면, 한번에 여러번의 수정이 발생하게되고, 최종 수정된 결과값을 인지할 수 없게 되는 상황으로 인해 데이터베이스의 일관성이 깨지게 된다


4) Lock의 종류
- 공유 락(Shared Locks) /읽기 락(READ Locks)

다른 트랜잭션에서 데이터 읽기허용, 수정 금지
- 배타 락(Exclusive Locks) / 쓰기 락(WRITE Locks)
다른 트랜잭션이 데이터를 읽거나, 수정하는 것을 금지

5) Locking Level

- 글로벌 락(Global Locks) / 데이터베이스 락(Database Locks)
데이터베이스의 모든 테이블에 락을 걸어, 현재 트랜잭션을 제외한 나머지 트랜잭션들이 모든 테이블을 사용할 수 없도록 만든다. 가장 높은 수준의 락
- 테이블 락(Table Locks)
다른 사용자가 작업중인 테이블을 동시에 수정하지 못하도록 한다
- 네임드 락(Named Locks)
테이블이나 테이블의 행과같은 DB 오브젝트가 아닌, 특정한 문자열을 점유함
- 메타데이터 락(Metadata Locks)
다른 사용자가 작업중인 테이블의 동일한 행 및 동일한 데이터베이스의 객체를 동시에 수정하지 못하도록 한다

 

6) 교착상태(Dead Lock)

- 서로 다른 트랜잭션이 동시에 진행중인데 서로에 대해 락이 걸려있을 때 발 서로의 락이 풀리기를 기다리는 교착 상태가 발생하며, 이로 인해 영구히 lock이 진행되는 상황

 

7) Isolation Level

- 트랜잭션의 격리 수준 (Isolation Level)은 여러 트랜잭션이 동시에 처리될 때 다른 트랜잭션에서 변경 및 조회하는 데이터를 읽을 수 있도록 허용/거부 설정을 위해 이용
- 격리 수준의 대표적인 4가지

- READ UNCOMMITTED
커밋 되지 않은 읽기(Uncommitted Read)를 허용하는 격리 수준
가장 낮은 수준의 격리수준이며, 락을 걸지 않아 동시성이 높지만 일관성이 쉽게 깨질 수 있다
- READ COMMITTED
커밋 된 읽기(Committed Read)만을 허용하고, SELECT 문을 실행할 때 공유락을 겁니다
다른 트랜잭션이 데이터를 수정하고 있는 중에는 데이터를 읽을 수 없어 커밋되지 않은 읽기현상이 발생하지 않는다
- REPEATABLE READ
읽기를 마치더라도 공유락을 풀지 않으며, 트랜잭션이 완전히 종료될 때 까지 락을 유지
공유락이 걸린 상태에서 데이터를 수정하는 것은 불가능하지만, 데이터를 삽입하는 것이 가능해짐

하지만 팬텀 읽기(트랜잭션 중 다른 트랜잭션에 의해 삭제된 데이터를 읽는 것)가 발생할 수 있는 문제점이 존재
- SERIALIZABLE
데이터를 읽는 동안 다른 트랜잭션이 해당 데이터를 읽거나 삽입할 수 없고, 새로운 데이터를 추가하는 것 또한 불가능
가장 높은 수준의 격리 수준, 동시성이 떨어지는 문제점이 존재

 

8) 예시 코드

// routes/users.route.js

const { Users, UserInfos, UserHistories, sequelize } = require("../models");
const { Transaction } = require("sequelize");

// 회원가입
router.post("/users", async (req, res) => {
  const { email, password, name, age, gender, profileImage } = req.body;
  const isExistUser = await Users.findOne({ where: { email } });

  if (isExistUser) {
    return res.status(409).json({ message: "이미 존재하는 이메일입니다." });
  }

  // MySQL과 연결된 sequelize connection에서 transaction을 생성합니다.
  const t = await sequelize.transaction({
    isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED, // 트랜잭션 격리 수준을 설정합니다.
  });
  try {
    // Users 테이블에 사용자를 추가합니다.
    const user = await Users.create(
      { email, password },
      { transaction: t } // transaction을 사용합니다.
    );

    // UserInfos 테이블에 사용자 정보를 추가합니다.
    const userInfo = await UserInfos.create({
        UserId: user.userId, // 생성한 유저의 userId를 바탕으로 사용자 정보를 생성합니다.
        name,
        age,
        gender: gender.toUpperCase(), // 성별을 대문자로 변환합니다.
        profileImage
      },
      { transaction: t } // transaction을 사용합니다.
    );

    // 트랜잭션을 사용한 모든 로직을 Commit, DB에 반영합니다.
    await t.commit();
  } catch (transactionError) {
    // 에러가 발생하였다면, 트랜잭션을 사용한 모든 쿼리를 Rollback, DB에 반영하지 않습니다.
    console.error(transactionError);
    await t.rollback();
    return res.status(400).json({ errorMessage: `유저 생성에 실패하였습니다.` })
  }

  return res.status(201).json({ message: "회원가입이 완료되었습니다." });
});

 

 

 

2. Returning

- DB의 update 메소드에서 업데이트값 반환

- MySQL을 사용하여 findOne을 써줬는데 PostgreSQL등 해당 기능을 지원하는 경우 returning 이라는 것을 이용하여 업데이트 된 값을 바로 반환 가능

- 업데이트된 데이터에 대한 return이 필요할 때 db의 연산 시간을 줄일 수 있음