명예의 전당에 투표수 순으로 매주 1000개씩 업데이트하는것이 목적인데
// 명예의 전당 매서드 일주일마다 업데이트
@Cron(CronExpression.EVERY_WEEK)
async updateHallOfFame() {
const lastWeekStart = new Date();
lastWeekStart.setDate(lastWeekStart.getDate() - 7 - lastWeekStart.getDay())
lastWeekStart.setHours(0, 0, 0, 0);
const lastWeekEnd = new Date(lastWeekStart);
lastWeekEnd.setDate(lastWeekEnd.getDate() + 6)
lastWeekEnd.setHours(23, 59, 59, 999);
// 지난주에 진행된 투표 데이터를 조회
const lastWeekVotes = await this.votesRepository.find({
where: {
createdAt: Between(lastWeekStart, lastWeekEnd),
},
});
// 투표 기반으로 명예의 전당 집계
const hallOfFameData = this.aggVotesForHallOfFame(lastWeekVotes)
await this.updateHallOfFameDatabase(hallOfFameData);
}
// 날짜 추상화 매서드
private getThisMonthRange(){
const start = new Date();
start.setDate(1); // 이번달 첫쨰날
start.setHours(0, 0, 0, 0) // 자정
const end = new Date(start.getFullYear(), start.getMonth() + 1, 0);
end.setHours(23, 59, 59, 999) // 하루의 마지막 시간
return { start, end }
}
// 투표 데이터 집계 매서드
private async aggVotesForHallOfFame(votes: Votes[]){
const { start, end } = this.getThisMonthRange();
const candidates = await this.votesRepository
.createQueryBuilder("vote")
.select(['vote.id', 'vote.title1', 'vote.title2'])
.addSelect("vote.voteCount1 + vote.voteCount2", "totalVotes")
.where('vote.createdAt BETWEEN :start AND :end', { start: start.toISOString(), end: end.toISOString() })
.having("totalVotes >= :minTotalVotes", { minTotalVotes: 100 }) // 투표 수 100 이상인 것만 조회
.groupBy("vote.id")
.getRawMany();
return candidates
}
// DB에 명예의 전당 데이터를 업데이트(배열형태로 받아서 한번에 저장) ver 1.
private async updateHallOfFameDatabase(hallOfFameData: any){
// 한번에 저장
const newHallOfFameEntries = hallOfFameData.map(data => {
const newHallOfFameEntry = new TrialHallOfFames();
newHallOfFameEntry.id = data.id // vote table의 id임다
newHallOfFameEntry.userId = data.trial.userId // vote에는 userId가 없으므로 일대일관계인 trial에 가서 userId 가져옴
newHallOfFameEntry.title = data.title1 + 'Vs' + data.title2
newHallOfFameEntry.content = data.trial.content // vote에는 content가 없으므로 일대일관계인 trial에 가서 content 가져옴
newHallOfFameEntry.createdAt = new Date();
newHallOfFameEntry.updatedAt = new Date();
return newHallOfFameEntry;
});
// DB에 새로운 명전 저장
await this.trialHallOfFamesRepository.save(newHallOfFameEntries)
}
이 코드는 그저 투표수에 100개 이상인 것만 필러링해서 저장하는것 이외에는 다른 로직은 행하고 있지 않다.
// 투표 데이터 집계 매서드
private async aggVotesForHallOfFame(votes: Votes[]){
const { start, end } = this.getThisMonthRange();
const candidates = await this.votesRepository
.createQueryBuilder("vote")
.select(['vote.id', 'vote.title1', 'vote.title2'])
.addSelect("vote.voteCount1 + vote.voteCount2", "totalVotes")
.where('vote.createdAt BETWEEN :start AND :end', { start: start.toISOString(), end: end.toISOString() })
.having("totalVotes >= :minTotalVotes", { minTotalVotes: 100 }) // 투표 수 100 이상인 것만 조회
.groupBy("vote.id")
.getRawMany();
return candidates
}
따라서 이 투표 데이터 집계 매서드에서 리팩토링을 해보도록 하겠다.
// 투표 데이터 집계 매서드
private async aggVotesForHallOfFame(votes: Votes[]){
const { start, end } = this.getThisMonthRange();
const candidates = await this.votesRepository
.createQueryBuilder("vote")
.select(['vote.id', 'vote.title1', 'vote.title2'])
.addSelect("vote.voteCount1 + vote.voteCount2", "totalVotes")
.where('vote.createdAt BETWEEN :start AND :end', { start: start.toISOString(), end: end.toISOString() })
.having("totalVotes >= :minTotalVotes", { minTotalVotes: 100 }) // 투표 수 100 이상인 것만 조회
.orderBy('totalVotes', 'DESC') // 내림차순
.limit(1000) // 상위 천개만 선택
.groupBy("vote.id")
.getRawMany();
return candidates
}
.orderBy('totalVotes', 'DESC') // 내림차순
.limit(1000) // 상위 천개만 선택
이 두줄을 넣어서 리팩토링 해주었다
orderBy 매서드를 이용해서 'DESC' 옵션으로 내림차순 해주었고 limit(1000)를 통해 상위 천개만 선택하였다.
그런데 만약 그런데 만약 집계하려고했을때 DB에 투표수가 100개가 넘는 데이터가 1000개 이상없으면 문제가 생길까 걱정을 하였는데
limit 옵션은 반드시 1000개 필요 한것을 의미하는 것은 아니라고한다. 즉 데이터의 개수가 800개 이면 800개가 올라가는 것이다. 휴... ^^