mysql 새 DB관련 API추가 후 pool 장애

임혁진·2024년 1월 17일
0

로빌

목록 보기
9/15

로아에서 원정대 내 다른 캐릭터를 저장하는 원정대DB테이블을 추가하고 나서 테스트할 때는 문제가 없었는데 배포 후에 사용자들의 요청이 많아지니 DB가 응답하지 않는 문제가 생겼다.

기존에 있던 DB는 쿼리를 작성해서 했었고 이번에 새로 넣은 원정대 db는 orm을 적용해보고자 sequelize를 통해서 만들었는데 여기서 문제가 생겼다.

문제 확인

일단 원정대db를 사용하는 api를 중단하고 (모듈이 독립적이라 원정대api요청만 빈응답을 넘겨주는 것으로 대체하고 서비스는 계속 작동하도록 했다.) 문제 분석에 들어갔다.

가장 먼저 log를 들어가서 살펴봤을 때 알 수 있었던 것은 변경하지 않은 class_data table에 읽기 작업이 실패한다는 것이었다. 분명 이번 변경사항에 class_data table과 관련있는 변경사항은 없었는데 문제가 생긴것이다. 서버는 멀쩡하게 동작하는데 db와 연결되는 동작들만 문제가 있는 것으로 보아 RDS를 들어가 상태를 살펴보았다.


DB쪽을 살펴보니 배포 이후에 database connection이 급격하게 올라간 후 새로운api요청을 막은 이후에 다시 안정화가 된 모습을 보였다. 정확하진 않지만 새로 추가된 api가 db의 커넥션에 문제를 일으켜 생긴 문제로 추측했다.

코드를 살펴보니 db커넥션 config가 두개가 생성된 것을 확인할 수 있었다.

const sequelize = new Sequelize(
  config.database,
  config.username,
  config.password,
  {
    host: config.host,
    dialect: config.dialect,
    logging: config.logging,
    pool: {
      max: 25, 
      min: 0,
      acquire: 10000,
      idle: 10000,
    },
  }
);

위 코드가 두개의 테이블을 지정하는데 두 번 사용되었다. 한개의 sequelize인스턴스가 하나의 db를 연결하기 위한 pool을 담당하는데, 두개의 인스턴스가 생겨버리면 각각 연결을 관리하게 된다. 문제는 config내의 max pool의 개수가 최대연결(100) /서버대수(4) =25로 설정되어있기 때문에 한 서버에서 두개의 설정을 사용해버리면 한번에 50개의 연결까지 가능해버려서 최대연결 숫자를 초과해버린다.

시도1

두개의 sequelize객체가 사용되던 것을 하나로 수정한다.

적용 후에도 다시 connection이 상승하는 현상이 발생했다.

혹시나 서버 증설orDB스케일업을 할 경우에 maxpool을 변화시켜야 할텐데 이를 위의 코드에서 매직넘버로 저장하지 않고 적어도 환경변수로 만들어서 새로 증설하거나 할 때 확인하고 수정할 수 있도록 해야겠다. => 적용완료

해결

  async getSiblingsAndUpdatedAtByName(name) {
    const transaction = await siblingsSequelize.transaction();
    try {
      const existingSibling = await Siblings.findOne({
        where: { name: name },
        transaction,
      });
      if (!existingSibling) {
        return { siblings: [], updatedAt: null };
      }
      const targetGroupId = existingSibling.group_id;
      const updatedAt = await this.#getUpdatedAtByGroupId(
        targetGroupId,
        transaction
      );
      const siblingGroupFromDB = await this.#getSiblingsByGroupId(
        targetGroupId,
        transaction
      );
      await transaction.commit(); // 이부분의 await이 빠져있었다.

      return {
        siblings: siblingGroupFromDB,
        updatedAt: updatedAt,
      };
    } catch (err) {
      await transaction.rollback();
      throw err;
    }
  }
 const transaction = await siblingsSequelize.transaction();
    try {
      const existingSibling = await Siblings.findOne({
        where: { name: name },
        transaction,
      });
      if (!existingSibling) {
        await transaction.rollback(); // rollback이 빠져있었다.
        return { siblings: [], updatedAt: null };
      }
     //...

transaction을 commit과 rollback하는 과정에서 누락되는 부분이있었다. rollback을 누락시켜서 pool이 누수되는 것인데 아무리 gpt한테 물어봐도 이걸 찾지 못했다. 아무도 이런 실수는 안하는 걸까? 대부분의 단순 쿼리는 sequelize에서 pool을 관리를 해주기 때문에 만약 다음에도 문제가 생긴다면 transaction이 잘 되고있는지 누락되는 부분은 없는지 잘 점검 해봐야겠다.

사고를 겪고난 후 풀의 안정화

어쨌든 이 부분을 해결하면서 sequelize에서 pool이 어떻게 관리되는지 알 수 있었고 만약 pool자원이 초과될 경우의 시나리오도 생각해볼 수 있어서 더 큰 문제들을 미리 해결할 수 있었던 것 같다.

궁금증

  • 만약 sequelize에서 감당 불가능할 정도의 pool생성이 들어와서 연결이 밀릴 경우에 먼저 알 수 있는 방법이 있을까?
profile
TIL과 알고리즘

0개의 댓글