참고
정규화 / 비정규화
NOTE
정규화 데이터베이스 ⇒ 중복을 최소화하는 데이터베이스
비정규화 데이터베이스 ⇒ 읽기 시간을 최적화하는 데이터베이스
정규화는 중복을 줄이고, 비정규화는 join을 줄여줌..
정규화
NOTE
관계형 데이터베이스에서 중복을 최소화하기 위해 데이터를 구조화 하는 작업을 말한다!
보통 3 정규형까지 쓴다.
•
차수가 높아질수록 만족시켜야할 제약 조건이 늘어난다.
•
장점
◦
데이터베이스 변경 시 이상 현상 제거
◦
저장 공간의 최소화 기능
◦
효과적인 검색 알고리즘 생성 가능
◦
데이터 삽입 시 관계 재구성 필요성 감소
◦
데이터 구조의 안전성 및 무결성 유지
•
단점
◦
테이블간 JOIN 연산 증가 → 쿼리 시간이 길어짐
비정규화
NOTE
하나 이상의 테이블에 데이터를 중복해 배치하는 최적화 기법!
•
의도적으로 정규화를 위배하는 행위이다.
•
비정규화 대상
◦
자주 사용되는 테이블에 엑세스하는 프로세스의 수가 가장 많고, 항상 일정한 범위만을 조회
◦
테이블에 대량 데이터가 있고, 자주 처리하는 경우
◦
지나치게 조인이 많이해서 조회하는 것이 기술적으로 어려운 경우
•
장점
◦
빠른 데이터 조회 → 조인 비용이 줄어들기 때문
◦
살펴볼 테이블이 줄어들기 때문에 쿼리가 단순해짐
•
단점
◦
데이터 갱신이나 삽입 비용이 높음
◦
데이터의 일관성이 깨질 수 있음
◦
중복 저장하기에 더 많은 저장공간이 필요해짐
실습 (SNS 모델링)
NOTE
실제 SNS에서 사용되는 DB형식을 가지고 정규화/비정규화를 작성한다!
사용 API 목록
# 사용자 테이블
create table Member
(
id int auto_increment,
email varchar(20) not null,
nickname varchar(20) not null,
birthday date not null,
createdAt datetime not null,
constraint member_id_uindex
primary key (id)
);
# 사용자 이름변경 테이블
create table MemberNicknameHistory
(
id int auto_increment,
memberId int not null,
nickname varchar(20) not null,
createdAt datetime not null,
constraint memberNicknameHistory_id_uindex
primary key (id)
);
# 팔로우 테이블
create table Follow
(
id int auto_increment,
fromMemberId int not null,
toMemberId int not null,
createdAt datetime not null,
constraint Follow_id_uindex
primary key (id)
);
SQL
복사
실제 사용한 테이블 내용들
•
JDBC를 주로 사용하는데 이에 대한 설명은 생략한다.
EasyRandom
record
Usecase 사용
NOTE
Domain - Domain간 통신을 위해 Usecase작성에서 흥미로운 주제가 나와서 기록한다!
@RequiredArgsConstructor
@Service
public class CreateFollowMemberUsecase {
final private MemberReadService memberReadService;
final private FollowWriteService followWriteService;
public void execute(Long fromMemberId, Long toMemberId){
/*
1. 입력받은 memberId로 회원조회
2. FollowWriteService.create()
*/
var fromMember = memberReadService.getMember(fromMemberId);
var toMember = memberReadService.getMember(toMemberId);
followWriteService.create(fromMember, toMember);
}
}
Java
복사
ex) Member id값 2개로 Follow 생성
@RequiredArgsConstructor
@Service
public class GetFollowingMembersUsecase {
final private MemberReadService memberReadService;
final private FollowReadService followReadService;
public List<MemberDto> execute(Long memberId) {
var followings = followReadService.getFollowings(memberId);
var followingMemberIds = followings.stream().map(Follow::getToMemberId).toList();
return memberReadService.getMembers(followingMemberIds);
}
}
Java
복사
ex) Member에서 Member의 Follow 목록 MemberList 가져오기
\
실습 과정
NOTE
히스토리성 데이터는 정규화 대상이 아니다.
@Getter
public class MemberNicknameHistory {
final private Long id;
final private Long memberId;
final private String nickname;
final private LocalDateTime createdAt;
@Builder
public MemberNicknameHistory(Long id, Long memberId, String nickname, LocalDateTime createdAt) {
this.id = id;
this.memberId = memberId;
this.nickname = nickname;
this.createdAt = createdAt == null ? LocalDateTime.now() : createdAt;;
}
}
Java
복사
Member의 nickname이 중복으로 포함된다.
•
요구사항에 따라 데이터의 최신성을 보장하지 않아도되는 경우 정규화 대상이 아니다.
•
사용자의 닉네임 변경기록은 최신데이터가 아닌 과거의 내역을 보관하므로 정규화를 하지 않는다.
정규화 하는 경우
@Getter
public class Follow {
final private Long id;
final private Long fromMemberId;
final private Long toMemberId;
final private LocalDateTime createdAt;
@Builder
public Follow(Long id, Long fromMemberId, Long toMemberId, LocalDateTime createdAt) {
this.id = id;
this.fromMemberId = Objects.requireNonNull(fromMemberId);
this.toMemberId = Objects.requireNonNull(toMemberId);
this.createdAt = createdAt == null ? LocalDateTime.now() : createdAt;
}
}
Java
복사
중복되는 데이터가 없도록 설계
•
팔로우의 경우 데이터의 최신성을 보장해야 한다.
•
만약 수백만명의 팔로우가 있는 인플루언서가 무언가를 수정하는데 정규화가 되어있지 않으면 팔로우된 수백만명의 데이터를 모두 수정해야한다!
실습내용 정리
NOTE
중복된 데이터면 반드시 정규화를 해야하나?
•
중복 데이터면 무조건 정규화를 하는건 고려해봐야할 일이다.
•
정규화도 비용이다. (읽기 비용 - 쓰기 비용의 트레이드 오프)
정규화시 고려해야할 점
•
데이터의 최신성을 얼마나 보장해야하는가?
•
히스토리성 데이터는 정규화 대상이 아니다.
•
데이터의 변경 주기와 조회 주기
◦
자주 변경되는 데이터일수록 쓰기에 이점을 가져가도록 한다.
•
객체(테이블) 탐색 깊이
A→B→C→D의 깊이가 B→D를 가지게할수 있음 (읽기 향상)
하지만 C의 D참조가 변경되면 B도 수정해야함 (쓰기 저하)
정규화를 한다 → 읽기시 데이터를 어떻게 가져올것인가?
•
조인을 하는건 언제나 최후의 보루이다.
◦
조회시 성능이 좋은 DB, 캐싱, 최적화 기법등을 먼저 고민해보자
◦
조인을 하는건 무엇보다 결합도가 너무 높아진다.
•
읽기 쿼리를 한번 더 실행하는건 생각보다 큰 비용이 아닐 수 있다.