본문 바로가기
프로젝트/사이드 프로젝트(2024.09.01-2024.10.04)

[사이드 프로젝트 : DevToolKit] 데이터 비정규화시키기

by dal_been 2024. 10. 4.
728x90

현재 우리의 사이드 프로젝트 메인서비스 메인화면에서 폴더와 여러유형의 컨텐츠들이 함께 보여야한다.

그러나 여기서 막히게되는데..

 

 

데이터 정규화

 

초기 테이블 설계 시, 나는 폴더와 다양한 유형의 컨텐츠(코드형, 게시판형, 파일형)를 정규화된 방식으로 구조화했다. 이를 위해 다음과 같은 세 가지 테이블로 분리하였다.

  1. 폴더 도메인: 폴더의 기본적인 속성인 이름과 부모 폴더 정보만을 저장하는 구조로 설계했다. 폴더는 다른 컨텐츠와 구분되어 관리되는 독립적인 개체로 설정되었다.
  2. 컨텐츠 도메인 1 (코드/게시판형): 이름과 내용만을 포함하는 단순한 구조로, 텍스트 기반의 컨텐츠를 저장하기 위한 테이블로 설계되었다. 코드와 게시판 글 같은 비파일형 컨텐츠는 이곳에 저장된다.
  3. 컨텐츠 도메인 2 (파일형): 파일 컨텐츠에 특화된 테이블로, 이름, 파일 URL, 파일 크기, 파일 확장자, 그리고 해당 파일이 속한 폴더 도메인 정보를 포함했다. 이를 통해 파일형 컨텐츠의 특성을 적절히 관리할 수 있었다.

이렇게 나눈 이유는 데이터 정규화를 통해 중복을 최소화하고, 각 도메인 간 명확한 관계를 설정하여 유지보수성과 확장성을 높이기 위함이었다. 이를 통해 구조적으로 일관성을 유지하면서도 각 컨텐츠의 속성에 맞춘 효율적인 데이터 관리를 할 수 있었다.

 

 

테이블을 정규화한 후, 팀원들로부터 확정되지 않은 요구 사항이 확정되었을때, 예상치 못한 문제에 직면하게 되었다.

 

"폴더와 컨텐츠(코드형, 게시판형)를 한 번에 검색하고, 정렬, 검색조건, 페이징까지 처리할 수 있게 해주세요!" 라는 요청이 들어왔을 때, 기존의 구조는 폴더와 컨텐츠가 서로 독립적으로 설계되어 있어 한 번에 조회하는 기능을 처리하기 어려웠다. 

 

그러나 여기서 끝이 아니었다. 또 다른 요청이 들어왔다.

"폴더형 탭에서는 폴더와 파일형 컨텐츠도 함께 보여주고, 마찬가지로 정렬, 검색조건, 페이징을 처리해주세요!"

이 요구 역시 기존의 정규화된 테이블 구조로는 대응하기 힘든 문제였다.

두 번째 멘붕이 찾아왔고, 프로젝트 마감기한은 고작 2주밖에 남지 않은 상황이었다.

 

데이터 비정규화를 고민하자

 

일단 요구사항 정리

1. 전체보기에서 폴더와 코드 컨텐츠, 게시판 컨텐츠가 한번에 검색 (정렬 + 검색조건+ 페이징)

2. 폴더형탭에서 폴더와 파일컨텐츠 정렬이 함께 되어야함

 

 

첫번째 요구사항 전체보기

처음에는 폴더를 컨텐츠와 비정규화하는 방법을 고려했으나, 폴더를 비정규화하면 테이블에 null 값이 많이 발생할 것이라는 문제가 있었다. 따라서 폴더와 컨텐츠를 통합하는 것은 어려운 선택이었다.

 

다음으로 NoSQL이나 ElasticSearch와 같은 기술 스택을 도입해 검색을 구현하는 방안을 생각해 보았지만, 비용적인 부담과 서버 비용이 이미 발생하는 상황에서 2주 안에 이러한 기술을 도입해 프론트엔드와 연동 테스트까지 마무리하는 것은 현실적으로 불가능했다.

결국, 요구사항 변경을 제안했다. "폴더 대신 파일을 전체보기에서 통합하는 건 어떠세요?"라는 제안에 팀원들이 일단은 동의했지만, 장기적으로는 다시 폴더와 통합하기를 희망한다는 의견도 있었다. 특히, 폴더 안에 너무 많은 파일(icon1.zip, icon2.zip)이 노출되는 것에 대한 우려가 있었다.

 

그래서 파일을 컨텐츠(게시판, 코드)와 비정규화하여 처리했다.

 

 

두번째 요구사항 폴더형탭

폴더형 탭에서는 폴더와 파일이 함께 정렬되는 요구가 있었다. Mac이나 다른 서비스에서도 파일과 폴더가 함께 정렬되는 방식이 있었기 때문에, DB 쿼리에서 UNION을 사용하는 것도 고려했으나, 그렇게 하고 싶지 않았다.

또한, 앞서 언급한 이유로 NoSQL이나 ElasticSearch 같은 기술을 적용하는 것도 어렵다고 판단했다.

결국, 2주 안에 마무리해야 하고 서비스가 실제 운영되는 서비스가 아닌 만큼, 백엔드에서 모든 데이터를 가져와 Comparator를 사용해 정렬하는 방식으로 해결했다.

    fun getSortComparator(sortType: SortType): Comparator<FolderReadResponse> =
        when (sortType) {
            SortType.NEW ->
                Comparator { o1, o2 ->
                    compareValuesBy(o1, o2, { it.lastModifiedDate })
                }
            SortType.NAME ->
                Comparator { o1, o2 ->
                    compareValuesBy(o1, o2, { it.name })
                }
            SortType.DEFAULT ->
                Comparator { o1, o2 ->
                    compareValuesBy(o1, o2, { it.id })
                }
        }

 

아 진짜 맘에 안들었지만,,, 일단 빠르게 해결해야했기때문에..

 

 

더 비정규화 해보자

 

이거에 대해 고민이 되서 팀장님께 살짝 물어보았다. 

"팀장님 폴더와 컨텐츠가 분리된 테이블인데 같이 검색되어야한고 정렬, 페이징이 함꼐 되어야해요 어떻게 하실래요??" 

 

했더니 팀장님 답변

 

"그냥 비정규화 시키면 안되?? 왜 나눈건데??"

 

나: "테이블 정규화를 통해 좀더 명확하게 하고 싶어서요"

 

팀장 : "음.. 지금생각하는 답변은 비정규화인거같아. 어처피 생각해보면 둘다 검색이 되어야하는 컨텐츠? 혹은 데이터잖아.. 그러니까 유형이 같다..? 비정규화 해보는게 어때?"

 

팀장님과 대화를 나눈 후, 나 역시 비정규화가 실용적인 선택일 수 있음을 깨닫게 되었다.

처음엔 폴더와 컨텐츠를 분리하여 테이블을 명확하게 유지하려 했지만, 팀장님께서 "둘 다 결국 검색되어야 할 데이터니까 굳이 분리할 필요가 있을까?"라는 말을 듣고 다시 고민하게 되었다. 폴더 역시 일종의 컨텐츠라고 생각하면, 비정규화를 통해 두 가지 데이터를 하나로 합치는 것이 훨씬 효율적일 수 있었다.

 

그래서 생각해보았다. 만약 폴더까지 컨텐츠 도메인에 추가된다면 어떤 필드가 추가될까? 했을때 

parentContent (type = Folder) 정도였다.

 

비정규화하게 된다면 폴더를 컨텐츠 도메인에 추가하고, 폴더에 필요한 필드는 parentContent (type = Folder) 정도로 추가될 수 있었다. 이렇게 하면 폴더와 컨텐츠를 동시에 검색하고 정렬하는 데 문제가 해결될 뿐 아니라, 폴더와 컨텐츠 생성, 읽기, 수정, 삭제 API도 통합할 수 있었다. 이는 코드적으로도 더 효율적이고 유지보수성도 높아진다.

 

 


 

이번 프로젝트에서 처음 테이블 설계를 할 때는 데이터 정규화를 통해 명확한 구조를 유지하려고 했지만, 실제로 팀원들의 요구사항이 구체화되면서 정규화만으로는 해결할 수 없는 문제들이 발생했다. 폴더와 컨텐츠를 한 번에 검색하고 정렬까지 처리해야 했기 때문에, 결국 비정규화를 고민할 수밖에 없었다.

 

팀장님과의 대화 후, 비정규화가 훨씬 실용적일 수 있다는 결론을 내렸다. 폴더를 별도의 테이블로 유지하기보다는 하나의 컨텐츠로 보고 통합하는 것이 훨씬 효율적이었다. 이렇게 하면 폴더와 컨텐츠를 동시에 검색하고 정렬하는 요구사항을 쉽게 해결할 수 있었고, API도 통합하여 코드가 더 간결해졌다.

 

결국, 이 경험을 통해 비즈니스 로직과 실제 요구사항에 맞춰 유연하게 대응하는 것이 데이터 설계에서 중요하다는 것을 다시 한 번 느꼈다. 비정규화는 단순히 테이블을 합치는 게 아니라, 더 복잡한 요구사항을 해결하고, 개발을 빠르게 진행할 수 있는 강력한 도구라는 걸 깨달았다