어떨 때 composite key를 사용해야 할까?트러블 슈팅2023. 3. 19. 02:36
Table of Contents
https://github.com/inje-megabrain/waka-board-be
이번 게시글에서 쓰일 프로젝트다.
프로젝트 요구 사항은 아래와 같다.
- 동아리 WAKATIME 관리 프로젝트이다.
- 테이블에는 코딩 시간이 저장된다. (20분마다 갱신됨)
- 조회하는 케이스는 둘이다.
- 오늘 코딩 시간 조회
- 지난 n일전에 얼마나 코딩했는데 조회 및 평균 계산
로그를 id로 직접 조회할 일은 없다.
DB 설계
테이블 소개
- user: wakatime 이용자의 계정 정보를 저장하는 테이블이다.
- organizations: 회원의 참여 동아리 정보가 있다.
- user_logs: 동아리 회원의 코딩 시간 기록용 테이블이다.
users
@Entity('users')
export class User implements UserProperties {
@PrimaryGeneratedColumn('increment', { type: 'int' })
id: number;
@Column({ unique: true })
apiKey: string;
@Column({ unique: true })
nickname: string;
@Column({ type: 'int', nullable: true })
organizationId: number | null;
@ManyToOne(() => Organization, (organization) => organization.users, {
onDelete: 'SET NULL',
onUpdate: 'CASCADE',
})
@JoinColumn({ name: 'channelId', referencedColumnName: 'id' })
organization: Organization;
@OneToMany(() => UserLog, (userLog) => userLog.user)
userLogs: UserLog[];
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@DeleteDateColumn({ nullable: true })
deletedAt: Date | null;
}
id는 increment 방식을 사용했다.
유저 정보가 업데이트 되는 경우가 거의 없으며, id를 기준으로 조회하는 경우가 많기 때문에 클러스터 인덱스 방식을 사용하는 increment방식을 사용하였다.
apiKey는 `waka_${uuid4}` 방식인데 이부분은 정규화 + 암호화하여 저장하고 있다.
organizations
@Entity('organizations')
export class Organization implements OrganizationProperties {
@PrimaryGeneratedColumn('increment', { type: 'int' })
id: number;
@Column({ enum: OrganizationType })
title: OrganizationType;
@Column({ type: 'varchar', nullable: true })
description: string | null;
@OneToMany(() => User, (user) => user.organization)
users: User[];
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@DeleteDateColumn({ nullable: true })
deletedAt: Date | null;
}
이건 뭐 설명할게 없다. 지금은 동아리 종류를 전부 enum으로 관리하고 있다.
학교에 있는 컴공동아리를 전부 추가해놨는데, 만약 내년에 대학에 1천명이 입학한다면 Redis를 사용하여 관리하여야 한다.
user_logs
@Entity('user_logs')
export class UserLog {
@Column({ type: 'int', primary: true })
userId: number;
@ManyToOne(() => User, (user) => user.userLogs, {
onDelete: 'SET NULL',
onUpdate: 'CASCADE',
})
user: User;
@Column({ type: 'numeric' })
workedTimeSeconds: number;
@CreateDateColumn({ type: 'date', primary: true })
createdAt: Date;
}
꽤 고민하고 설계했다.
- createdAt은 date 타입이다. (기본값은 datetime)
- createdAt이 log의 식별자 역할을 수행하기 때문이다.
- createdAt과 userId를 이용하여 composite key를 사용하였다.
- composite 키는 두 키를 묶어서 PK처럼 활용하는 방식이다.
꽤나 구현 방식이 갈려서 (두 키를 묶어서 Index로 관리하고 고유 id를 만들어주는 방식, 두키를 묶어서 composite key로 관리하는 방식(현재 방식)) 꽤나 고민해보면서 작업해봤다.
지금 사용하는 방식을 선택한 이유는 고유 id로 조회하는 경우가 없기 때문이다.
하지만, user_id와 created_at이 중복일때는?
composite key의 pk 조건 때문에 중복 생성이 되지 않습니다.
'트러블 슈팅' 카테고리의 다른 글
인덱스의 카디널리티, 복합키의 성능 최적화 (0) | 2023.03.30 |
---|---|
팀원이 설정한 알람이 내 폰에서 울려야한다. (0) | 2023.03.28 |
OAuth 로그인을 하는데 새로운 계정이 생성되면 안된다. (0) | 2023.02.23 |
[Node.js] 싱글 스레드 언어가 어떻게 비동기 처리가 가능한가?, Node.js 동작 원리 (0) | 2023.01.26 |
[NestJS] AdminJS를 이용한 크롤러 관리 패널 제작기 (0) | 2023.01.14 |
@임채성 :: 푸르고 개발 블로그
글 내용 중 잘못되거나 이해되지 않는 부분은 댓글을 달아주세요! 감사합니다! 문의: puleugo@gmail.com