대용량시스템에 대한 이해(2) - 데이터 정규화/SNS모델링

남순식·2023년 9월 25일
0

대용량 시스템

목록 보기
3/7

간단한 토이프로젝트로 SNS모델링을 진행하며 학습한 내용입니다.

1. 데이터 정규화

1-2. 데이터 정규화

관계형 데이터베이스에서 중복을 최소화하기 위해 데이터를 구조화 하는것

보통 3차 정규형까지 사용하며 차수가 높아질수록 만족시켜야할 제약이 늘어남

장점

  • 중복을 제거하고 한 곳에서 관리
    저장공간 최소화

  • 데이터 정합성 유지하기가 유리함

  • 데이터 삽입 시 관계 재구성 필요성 감소

단점

  • 원본데이터를 찾아가기 위해 Join이 필요 -> 쿼리 시간 길어짐

1-3. 데이터 비정규화(반정규화)

하나 이상의 테이블에 데이터를 중복으로 배치하는 최적화 기법

  • 의도적으로 정규화에 반하는 행위

비정규화 대상

  • 자주 사용되는 테이블에 액세스하는 프로세스의 수가 가장 많고,
    항상 일정한 범위만을 조회하는 경우

  • 테이블에 대량 데이터가 있고 자주 처리하는 경우

  • 지나치게 조인을 많이 사용하게 되어 조회하는 것이 기술적으로 어려울 경우

장점

  • 빠른 데이터 조회
    (Join 비용이 줄어들기 때문)

  • 살펴볼 테이블이 줄어들기 때문에 데이터 조회 쿼리가 간단해짐

단점

  • 데이터 갱신이나 삽입 비용이 큼

  • 데이터 간의 일관성이 깨어질 수 있음

  • 데이터를 중복하여 저장하므로 더 많은 저장 공간이 필요

Note :
정규화 데이터베이스 : 중복을 최소화하는 데이터베이스
비정규화 데이터베이스 : 읽기 시간을 최적화하는 데이터베이스

2. 실습

2-1. SNS 모델링

요구사항

  • 회원정보 관리
    • 이메일, 닉네임, 생년월일을 입력받아 저장한다.
    • 닉네임은 10자를 초과할 수 없다.
    • 회원은 닉네임을 변경할 수 있다.
    • 회원의 닉네임 변경이력을 조회 할 수 있어야한다.
  • 팔로우 기능
    • 팔로워목록을 알 수 있다.

2-1-1. 실제 사용 테이블

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)
);

Jdbc로 JPA를 구현했었는데 이는 생략한다.

2-1-2. 히스토리성 데이터는 정규화 대상이 아니다.

@Getter
public class Member {
	final private Long id;
	private String nickname;
	final private String email;
	final private LocalDate birthday;
	final private LocalDateTime createdAt;
	final private static Long NAME_MAX_LENGTH = 10L;
    
	@Builder
	public Member(Long id, String nickname, String email, LocalDate birthday,
		LocalDateTime createdAt) {
		this.id = id;
		this.email = Objects.requireNonNull(email);
		this.birthday = Objects.requireNonNull(birthday);
		validateNickName(nickname);
		this.nickname = Objects.requireNonNull(nickname);
		this.createdAt = createdAt == null ? LocalDateTime.now() : createdAt;
	}

}
@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;;
    }
}

두 Entity에는 같은 nickname 필드가 정의돼있지만 정규화 하지 않는다.

  • 요구사항에 따라 데이터의 최신성을 보장해야하는 경우가 아니면 정규화 대상이 아니다
  • 사용자의 닉네임 변경기록은 최신데이터가 아닌 과거의 내역을 보관하므로 정규화하지 않는다.

2-1-3. 정규화 하는 경우

@Getter
public class Follow {
	private final Long id;
	private final Long fromMemberId;
	private final Long toMemberId;
	private final 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;
	}
}

팔로우같은 경우 정규화 해야한다.

  • 수 백만 팔로워를 가진 인플루언서가 무언가를 수정했을 때 데이터가 정규화 돼있지 않다면?
    모든 팔로우의 데이터를 수정해야한다.

  • 팔로우의 경우 데이터의 최신성을 보장해야 한다.

2-1-4. 정리

중복된 데이터를 무조건 정규화 하는것은 아니다.

  • 중복된 데이터를 무조건 정규화 하는것은 고려해봐야할 사항이다.

  • 정규화도 비용이다 (읽기 - 쓰기 비용간의 트레이드 오프)

정규화의 고려해야할 점

  • 데이터의 최신성을 보장해야 하는가?

  • 히스토리성 데이터는 정규화 대상이 아니다.

  • 데이터 변경 주기와 조회 주기

    • 데이터의 변경과 조회중 변경이 빈번하다면 정규화하여 쓰기의 이점을 가져가야한다.
  • 객체 탐색 깊이가 얼마나 깊은가

    • A -> B -> C -> D의 깊이에서 B -> D를 갖게할 수 있다. (읽기 향상)
    • C -> D 참조가 변경 되면 B도 수정해야 한다. (쓰기 저하)
      트레이드오프이며 밸런스를 유지하는 방향으로 방법을 고민한다
profile
응집력있는 시간을 보내기 위한 블로그

0개의 댓글