반복되는 작업은 한번에 처리해 보자
반응형

안녕하세요 😉

유유자적한 개발자 유로띠 입니다 😀

 

 

회사에서

프로젝트를 통해 배우고 성장하며

이것을 토대로 그로스 해커(Growth Hacker)가 되기 위한 포스팅입니다.

 

오늘의 이야기는

일괄로 생성하는 작업 중

고민했던 내용입니다 😏 

 

외부 연동을 통해 강의 상품을 제공


온라인 강의를 제공하는 서비스 회사에서

이번에 맡은 프로젝트는 외부 업체를 통해 수강생과 수강생이 신청한 강의 정보를 받아

수강생을 등록하고 신청한 강의 상품을 일괄로 구성하는 기능 개발을 진행하였습니다.

 

일단 외부 api를 통해 정보를 가져오고 등록만 하면 되는 작업이기 때문에 별로 어렵지 않다고 생각하였습니다. 👍

 

👀 요구사항을 간단히 살펴보겠습니다.

 

✅ 외부업체의 api를 이용하여 정보를 조회하여 수강생과 강의 정보를 가져옵니다.
 데이터를 알맞게 가공합니다.
 수강생 목록을 확인하여 수강생을 등록합니다.
 수강생이 신청한 강의에 대해서 강의 상품을 일괄로 생성합니다.
 수강생에게 신청한 강의를 등록해 줍니다. (수강 신청 = 수강권 발행이라고 합니다.)

 

일단 api를 통해 정보를 가져와 알맞게 처리하기 위해 다음과 같이 가공하였습니다.

 

🙆‍♂️  수강생 목록

 

 

📒 신청한 강의(A, B, C)에 따른 수강생 목록

 

 

 

 

요구사항을 토대로 슈도 코드를 간단하게 구성하면 다음과 같습니다.

실제는 좀 더 복잡하지만, 오늘 알려드릴 내용에는 이 정도면 충분하기 때문에 이점 양해 부탁드립니다 🙏

for (수강생 정보 in 수강생 목록) {
	수강생 = DB.select(수강생 정보);
    
 	if  수강생 있다면 -> UPDATE(수강생);
	    수강생이 없다면 -> INSERT(수강생);
}

for (key in 수강신청한 강의 목록) {
	....
	강의정보 = {
		제목: title,
		상태: state,
		수강 시작일: 20211001,
		수강 종료일: 20211030,
        };
	강의 아이디 = DB.INSERT(강의정보);
	생성한 강의 목록.push(강의 아이디);
}

for (key in 수강신청한 강의 목록) {
	강의 = 생성한 강의 목록.find(key);
	수강생 목록 =  수강신청한 강의 목록[key];
	수강권 생성 = DB.insert(강의, 수강생 목록);
}

🟢 수강생에 대해서는 수강생을 조회하여 있다면 업데이트를 진행하고, 없다면 신규로 수강생을 등록합니다.

🟢 수강 신청한 강의 목록의 key에서 강의 정보를 가져와 등록할 강의정보를 구성하고 강의 상품을 등록합니다.

🟢 이후 수강생의 수강권 발행을 위해 강의 아이디를 따로 배열(생성한 강의 목록)에 저장합니다.

🟢 그다음 강의와 수강생 정보를 이용하여 수강권을 생성합니다.

 

 

이렇게 기능을 구성하였고 요구한 대로 개발을 완료하였습니다. 

 

🤔  그러나... 신경 쓰이는 부분이 있습니다.

 

강의 상품을 구성하면서 몇 가지의 정보를 제외하고는 비슷하게 강의 상품을 만드는 반복문(for) 안에서 강의를 생성하는 DB query(DB.INSERT(강의정보);)를 하나씩 실행한다는 점입니다.

for (key in 수강신청한 강의 목록) {
	....
	강의정보 = {
		제목: title,
		상태: state,
		수강 시작일: 20211001,
		수강 종료일: 20211030,
        };
	강의 아이디 = DB.INSERT(강의정보);
	생성한 강의 목록.push(강의 아이디);
}

 

 

만약 신청한 강의가 100개가 존재하면 query를 100번 실행하게 될 것이고, 유형을 다양하게 구성한다면 어떻게 될까요?

 

🤔  예를 들어 동일한 강의 상품이지만 수강 기간에 따라 예습 강의, 본 강의, 복습 강의 등 유형을 다양하게 구성한다고 하면❓

100 * 3(예습 강의, 본 강의, 복습강의) = 300개의 강의를 일괄로 생성하며 이런 경우 300번의 생성 쿼리가 발생됩니다. 🥲

 

 

요구사항이 변경된다면 아래의 슈도 코드처럼 구성하겠죠?

for (key in 수강신청한 강의 목록) {
	....
	강의정보 = {
		제목: title,
		종류: 본강의,
		수강 시작일: 20211001,
		수강 종료일: 20211030,
        };
	강의 아이디 = DB.INSERT(강의정보);
	생성한 강의 목록.push(강의 아이디);
}

//예습 용 강의가 필요하다.
for (key in 수강신청한 강의 목록) {
	....
	강의정보 = {
		제목: title,
		종류: 예습강의,
		수강 시작일: 20211001,
		수강 종료일: 20211030,
        };
	강의 아이디 = DB.INSERT(강의정보);
	생성한 강의 목록.push(강의 아이디);
}

//복습 용 강의가 필요하다.
for (key in 수강신청한 강의 목록) {
	....
	강의정보 = {
		제목: title,
		종류: 복습강의,
		수강 시작일: 20211001,
		수강 종료일: 20211030,
        };
	강의 아이디 = DB.INSERT(강의정보);
	생성한 강의 목록.push(강의 아이디);
}

 

대량의 정보를 처리한다고 할 순 없지만, 불필요한 부분이라 생각되어 리팩터링이 필요한 부분이라 생각하였습니다. 👌

 

그럼 어떻게 해야 할까요?...

 

🚀 GROW UP POINT

사실 뭐 대단한 건 아닙니다.. 간단합니다.. 😜

 

한건 씩 하지 말고 배열에 담아서 한 번에 처리하자!

 

 

쿼리의 불필요한 실행을 줄이기 위해 강의 정보를 배열에 담아 한 번의 쿼리 실행으로 수정하였습니다.

for (key in 수강신청한 강의 목록) {
	....
	강의정보 = {
		제목: title,
		상태: state,
		수강 시작일: 20211001,
		수강 종료일: 20211030,
        };
        
    생성해야 할 강의 목록.push(강의정보);
}

DB.INSERT(생성해야 할 강의 목록);

이렇게 변경하면 많은 강의를 신청하여도, 한 번의 생성 쿼리로 일괄로 생성할 수 있습니다. (multiple insert)

또한 요구사항이 변경되어 예습 강의, 본 강의, 복습 강의처럼 여러 유형이 생성되어도 생성 쿼리 3번이면 끝이 납니다. 🙌

 

 

하지만, 이렇게 수정하니 또 하나의 문제가 발생되었습니다. 🥲

 

일괄로 insert는 가능하지만 리턴되어 나오는 결괏값은 일괄로 생성된 배열이 아닌 첫 번째 AUTO_INCREMENT ID만 알 수 있었습니다. 

 

MySQL - LAST_INSERT_ID 문서 중에 해당 정보가 나와있습니다.

Important
If you insert multiple rows using a single INSERT statement, LAST_INSERT_ID() returns the value generated for the first inserted row only. The reason for this is to make it possible to reproduce easily the same INSERT statement against some other server.

 

 

강의 상품을 일괄로 생성하고 해당 강의의 정보를 가진 목록이 필요합니다.

for (key in 수강신청한 강의 목록) {
	....
	강의정보 = {
		제목: title,
		상태: state,
		수강 시작일: 20211001,
		수강 종료일: 20211030,
        };
        
    생성해야 할 강의 목록.push(강의정보);
}

//첫번째 생성된 아이디값 만 알수 있다!
강의 아이디 = DB.INSERT(생성해야 할 강의 목록);
생성한 강의 목록.push(강의 아이디);

하지만 방금 생성한 강의 상품의 목록을 알 수 없기에 이후 수강권을 발행하지 못하는 문제가 발생되었습니다.

 

 

 

🚀 GROW UP POINT

🤔 그럼 어떻게 해야 할까요?

 

여러 가지 방법이 있겠지만, 제가 생각하기에 가장 쉬운 방법으로 진행하였습니다.

 

등록되는 강의 정보에 해당 트랜잭션에서 생성한 식별 값(uuid)을 넣어서 등록하고 조회하자!

 

해당 작업이 발생되는 트랜잭션에서 유일한 식별 값(uuid)을 생성하여 강의 상품 생성 시 추가하도록 합니다.

그리고 일괄적으로 강의 생성 후에 다시 식별 값으로 조회하여 생성된 강의 상품을 가져옵니다.

const uuid = 유일한 식별값;

for (key in 수강신청한 강의 목록) {
	....
	강의정보 = {
		제목: title,
		상태: state,
		수강 시작일: 20211001,
		수강 종료일: 20211030,
		식별키: uuid,
        };
        
    생성해야 할 강의 목록.push(강의정보);
}

DB.INSERT(생성해야 할 강의 목록);

생성한 강의 목록 = find강의By식별값(uuid);

 

😊 최종적인 슈도 코드는 다음과 같습니다.

for (수강생 정보 in 수강생 목록) {
	수강생 = DB.select(수강생 정보);
    
 	if  수강생 있다면 -> UPDATE(수강생);
	    수강생이 없다면 -> INSERT(수강생);
}

const uuid = 유일한 식별값;

for (key in 수강신청한 강의 목록) {
	....
	강의정보 = {
		제목: title,
		상태: state,
		수강 시작일: 20211001,
		수강 종료일: 20211030,
		식별키: uuid,
        };
        
    생성해야 할 강의 목록.push(강의정보);
}

DB.INSERT(생성해야 할 강의 목록);

생성한 강의 목록 = find강의By식별값(uuid);

for (key in 수강신청한 강의 목록) {
	강의 = 생성한 강의 목록.find(key);
	수강생 목록 =  수강신청한 강의 목록[key];
	수강권 생성 = DB.insert(강의, 수강생 목록);
}

 

이번 프로젝트를 통해 개선하거나 변경된 점 👍

불필요하게 반복하게 된 생성 쿼리 문제

🔜 반복되는 정보는 배열로 담아 일괄 쿼리를 이용하여 생성 쿼리를 줄였습니다.

 

일괄 생성 쿼리(multiple insert) 실행 시 리턴 값에 대해서 생성된 강의 정보를 배열로 받을 수 없는 문제

🔜 강의 정보 등록 시 식별 값을 이용하여 등록 후 식별 값으로 조회하는 방법으로 해결하였습니다.

 

 

 

 

👀 마무리


 

일괄적으로 정보를 생성하는 작업은 다양한 업무나 비즈니스 로직에서 사용되기에

간단하지만 이러한 방식으로 개선한다면 조금이지만 반복되는 쿼리의 부담을 줄일 수 있습니다. ✌️

 

 

참고

mysql - last_insert_id

 

MySQL :: MySQL 8.0 Reference Manual :: 12.16 Information Functions

MySQL 8.0 Reference Manual  /  Functions and Operators  /  Information Functions 12.16 Information Functions Table 12.20 Information Functions Name Description BENCHMARK() Repeatedly execute an expression CHARSET() Return the character set of the ar

dev.mysql.com

mariaDB - last_insert_id

 

LAST_INSERT_ID

Last inserted auto_increment value.

mariadb.com

 

반응형