시작하기
- Carryduo 게임 데이터를 분석하기 위해서는 몇가지의 사용자 데이터 수집이 필요하다.
게임 내의 사용자 id, 사용자 puuid, matchid 등이 필요하다. - Carryduo는 사용자 티어 중 중간 티어(게임 랭크)인 플레티넘, 다이아몬드 사용자의 데이터를 수집한다.
- Riot에서 제공하는 api로 플레티넘, 다이아몬드 사용자의 summonerId, puuId, matchId를 차례로 수집 한다.
데이터 수집 프로세스
기존 데이터 수집 프로세스
- 데이터베이스 연결
- 테스트용 request 로직실행 - 예외처리를 위해 테스트용으로 riot api에 요청 실행
- 데이터 수집 로직 실행
- 스케줄 패키지인 toad-scheduler를 이용해 2시간 30분 마다 실행 하도록 설정
데이터 수집 프로세스 완료 시간을 고려해 여유있게 시간 설정
//task.js
const matchIdTask = new AsyncTask(
"task",
async () => {
const response = await summonerController.testRiotRequest()
console.log(response)
if (response) {
return await startGetMatchIds()
} else {
logger.info("라이엇 API키 만료")
}
},
(err) => {
logger.error(err, { message: "-from matchIdtask" })
}
)
async function startGetMatchIds() {
try {
const start = performance.now()
// await dataRetirementController.deleteDoneMatchId()
// Outdated matchId 처리
await dataRetirementController.deleteOutdatedData("matchId")
// Wrong matchId 처리
await dataRetirementController.deleteWrongData('matchId')
await dataRetirementController.deleteWrongData('puuId')
await dataRetirementController.deleteWrongData('summonerId')
// // 로우데이터 수집
await sleep(10)
await summonerController.summonerId()
await sleep(10)
await puuidController.puuId()
await sleep(10)
await matchIdController.matchId()
await sleep(10)
const end = performance.now()
const runningTime = end - start
const ConversionRunningTime = String((runningTime / (1000 * 60)) / 60).split('.')[0]
const ConversionRunningMinute = (runningTime / (1000 * 60)) % 60
logger.info(`=== ${ConversionRunningTime} 시간 ${ConversionRunningMinute} 분소요===`)
} catch (err) {
logger.error(err, { message: "-from matchIdTaskMethod(startgetMatchIds)" })
}
}
//index.js
// 매치Id 수집
const matchIdJob = new SimpleIntervalJob({ hours: 2, minutes: 30, runImmediately: true }, matchIdTask) // runImmediately: 즉시실행
// runImmediately: 즉시실행
scheduler.addSimpleIntervalJob(matchIdJob)
문제점
- 데이터 수집과 데이터 분석은 하루에 같이 실행된다.
- 데이터 수집량에 비해 데이터 분석량이 뒤쳐지기 때문에, 매일 분석 되지 않는 matchId가 쌓인다.
- 데이터 분석량에 맞는 matchid 개수를 파악하여 그에 맞게 수집량을 정하여 그에 맞게 프로세스 실행하여야 한다.
해결
- pm2의 cron 기능을 통해 오후 1시 30분에 자동 재시작 하게끔 설정했다.
- while 문을 통해 수집로직을 실행한다.
- 로직 실행 후 수집한 matchid의 개수를 파악하고 30000개 이상 수집되었으면 수집 프로세스를 멈춘다.
child-process의 exec를 이용하여 pm2 프로세스를 멈춘다.
//handler.js
async function handler() {
try {
let matchIdCnt = 0;
let runningCnt = 1;
await db.connect();
await serviceDB.connectService();
console.log("DB 연결 작업 완료");
const response = await summonerController.testRiotRequest();
if (response) {
while (matchIdCnt < 30000) {
const start = performance.now();
await collectData();
console.log("데이터 수집 작업 완료");
sleep(3);
await deleteData();
console.log("불순 데이터 삭제 작업 완료");
const { today_matchid_count } = await getTodayMatchDataCount();
matchIdCnt = today_matchid_count;
const end = performance.now();
const runningTime = end - start;
const ConversionRunningTime = String(
runningTime / (1000 * 60) / 60
).split(".")[0];
const ConversionRunningMinute = (runningTime / (1000 * 60)) % 60;
logger.info(
`===${runningCnt} 번째 작업 ${ConversionRunningTime}시간 ${ConversionRunningMinute}분 소요 수집한 matchId: ${matchIdCnt}===`
);
runningCnt++;
}
const now = new Date();
logger.info(
`데이터 수집 프로세스 종료! 수집한 matchId 개수:${matchIdCnt}, 종료 시간:${now}`
);
exec("pm2 stop handler.js");
} else {
throw new Error("API expiration");
}
} catch (error) {
logger.error(error, { message: "-from handler" });
}
}
handler();
- 분석량에 맞게 수집량을 정하여 잘못된 matchid 제거 로직 수행이 원활해 졌고 무분별하게matchid 데이터가 쌓이는 것을 방지 하였다.
- 또한 수집이 완료되면 프로세스를 멈추므로 ec2 cpu 자원 및 메모리 사용량 및 로그 파일 사용량 감소로 인하여 서버 자원을 효율적으로 사용하였다.
데이터 수집 오프셋 저장
기존 오프셋
- 전역 변수로 설정한 page, standard를 수집 실행 로직, 수집 로직에서 사용한다.
//오프셋 전역변수
let page = 1
let standard = 6
let errStatus = 0
//수집 실행 로직
async function startGetSummonerId() {
let summonerIds = []
logger.info('summonerId 분석 시작')
while (page !== standard) {
console.log("while문 진입", "status: " + page)
await getSummonerId(summonerIds, page)
}
if (standard === 31) {
page = 1
standard = 6
} else {
standard += 5 //6, 11, 16, 21, 26, 31
errStatus = 0
}
logger.info(`summonerId 분석 완료, 다음 분석 첫페이지: ${page}, 종료 페이지: ${standard}`)
return "success"
}
//수집 로직
async function getSummonerId(summonerIds, num) {
const tierList = ["DIAMOND", "PLATINUM"]
for (let tier of tierList) {
...
if (errStatus !== 429) {
...
} else {
errStatus = 0
page -= 1
continue
}
}
}
return page++
}
문제
데이터 수집 프로세스에 따르면 수집된 matchid 개수가 30000개가 넘어가면 프로세스를 종료하게 되어있다.
- 전역변수로 설정한 오프셋 값이 전부 초기화 되는 상황이 발생했다.
- 전역변수로 설정된 오프셋값을 수집 실행, 수집 로직에서 사용하고 있고 수집 실행 로직에서 offset 파일을 비동기적으로 가져온뒤 수집 로직에 인자로 넘겨줬을때 원시 타입인 number로 값을 전달하기 때문에 오프셋 변경이 안되는 상황 발생
ex) page 1을 수집 로직으로 전달 하여 수집 로직에서 page++를 하게 되어도 수집 실행 로직에 page에는 아무런 변화가 없다.
해결
- 오프셋 값 저장
데이터 베이스 테이블과 텍스트 파일 중 리소스가 적고 사용하기 쉬운 텍스트 파일로 저장 하는 것을 선택하였다.
//오프셋 가져오기
async function readOffset() {
const fileName = "summoner-offset.txt"
const { page, standard } = JSON.parse(fs.readFileSync(fileName, { encoding: "utf-8" }))
return { page, standard }
}
//오프셋 저장하기
async function writeOffset(page, standard) {
const fileName = "summoner-offset.txt"
const data = JSON.stringify({ page, standard })
fs.writeFileSync(fileName, data, { encoding: "utf-8" })
}
//수집 실행 로직
async function startGetSummonerId() {
let { page, standard } = await readOffset()
...
}
- 오프셋 전달
offset 저장 파일에서 오프셋을 가져온뒤 해당 값을 객체로 생성하고 객체는 참조값을 전달하기 때문에 수집 로직 인수로 오프셋 객체를 전달하여 수집 로직에서도 오프셋 값을 변경 가능 하도록 설정하였다.
//수집 실행 로직
async function startGetSummonerId() {
let { page, standard } = await readOffset()
const offset = {
page,
standard,
}
while (offset.page !== offset.standard) {
console.log("while문 진입", "status: " + offset.page)
await getSummonerId(summonerIds, offset)
}
}
...
}
//수집 로직
async function getSummonerId(summonerIds, offset) {
...
if (errStatus !== 429) {
...
} else {
errStatus = 0
offset.page-= 1
continue
}
}
return offset.page++
}
'Project > Carryduo' 카테고리의 다른 글
Carryduo 정적 파일 저장 및 응답하기 (0) | 2023.03.22 |
---|---|
RDS start/stop 으로 실행 시간 조작하기 (0) | 2023.03.22 |
Carryduo 테스트코드 (0) | 2023.03.21 |
Carryduo DTO (0) | 2023.03.21 |
Carryduo 소환사 전적 갱신 요청 제한 시간 구현 (0) | 2023.03.21 |