설명 | |
---|---|
제목 | 위키키 |
개발 목적 | 우리만의 추억을 함께 기록하고, 즐거운 순간을 공유하며 소중한 경험을 함께 만들어보세요! 위키키는 친구, 동료와 함께 다양한 활동을 기록하며 우리만의 특별한 공간을 만들 수 있는 그룹형 커뮤니티 서비스입니다. |
개발 기간 | 2023.09.25 ~ 2023.11.11 |
-
그룹 기반의 다양한 활동 기록 📝
서로의 추억과 경험을 기록하고 페이지 별 목차를 통해 정보에 편리하게 접근하세요.
-
편리한 그룹원 초대 및 편집 권한 📨
그룹을 손쉽게 생성하고 초대링크를 통해 친구나 동료를 초대하세요. 모든 그룹원에게 페이지 편집 권한이 있어 함께 만들어가는 즐거움을 누릴 수 있습니다.
-
비공개 그룹 옵션 🔐
원하는 경우 비공개 그룹으로 설정하여 오직 그룹원끼리만 접근할 수 있도록 하세요. 우리만의 기록 공간을 안전하게 유지할 수 있습니다.
위키키를 통해 함께 즐거운 순간을 기록하고 공유하는 새로운 경험을 만나보세요. 다양한 활동을 즐기며 소중한 추억을 함께 쌓아가는 여정을 시작해보세요!
프론트 서버 : https://kb70bd6b8a3f6a.user-app.krampoline.com/
백엔드 서버 : https://kb70bd6b8a3f6a.user-app.krampoline.com/api/
- 사용자는 그룹을 생성하고 관리할 수 있습니다. 그룹의 이름, 대표 사진, 공개 여부 등을 설정할 수 있습니다.
- 사용자는 그룹에 가입하거나 초대를 받아 그룹에 참여할 수 있습니다. 초대코드를 통해 그룹 가입을 할 수 있습니다.
- 그룹의 멤버 리스트를 확인하고 관리할 수 있습니다. 그룹원 초대, 탈퇴 등의 기능이 제공됩니다.
- 그룹 내에서 페이지를 생성하고 수정할 수 있습니다. 각 페이지는 제목, 내용, 목차 등을 가지고 있습니다.
- 그룹 내에서 변경된 최근 페이지의 히스토리를 확인할 수 있습니다.
- 사용자는 페이지에 댓글을 작성하고 수정, 삭제할 수 있습니다.
- 사용자는 그룹 내에서 자신이 기여한 문서의 목록을 확인할 수 있습니다.
- 사용자는 메인 페이지에서 그룹을 검색할 수 있으며, 그룹 내에서는 페이지를 검색할 수 있습니다.
- 검색 결과를 확인하고 해당 페이지로 이동할 수 있습니다.
- 사용자는 카카오 로그인을 통해 로그인 하거나, 회원가입을 통해 계정을 생성하고 로그인하여 서비스를 이용할 수 있습니다.
문서 목록 |
---|
GitHub (FE) |
API 문서 |
와이어프레임 |
Spring Boot : 3.1.4
- Spring Security
- JPA
- Lombok
- Actuator
- JWT
- Validation
Java : 17
DB
- 테스트/로컬 용 : H2
- 배포용 : MariaDB 최신 버전
redis
- 회원 탈퇴 시, 회원이 작성했던 글, 댓글, 작성내역 등의 정보를 유지합니다
- 해당 정보를 유지하기 위해, 회원 탈퇴 시 회원과 관련된 groupMember에 외래키를 더미 회원을 만들어서 연결해줍니다.
- 탈퇴한 회원은 Database에서 사라지게 되며, 해당 회원이 작성했던 기록들에는 '알수없음'으로 표기됩니다.
신뢰성이 높은 그룹과 폐쇄성이 높은 그룹의 수요가 각각 존재할 것이라 생각해 총 3가지 그룹으로 분류하였습니다
- 인증이 필요하여 신뢰성을 높인 공식 그룹 (ex. 부산대학교 그룹)
- 공개적인 정보 공유 그룹인 비공식 공개 그룹
- 폐쇄되어 정보를 공유하는 비공식 비공개 그룹
- 공식 그룹은 해당하는 인증 방식을 통해 가입 권한을 얻을 수 있습니다
- 비공식 공개 그룹은 해당 그룹에 설정된 비밀번호를 맞추거나 초대 링크를 통해 가입 권한을 얻을 수 있습니다
- 비공식 비공개 그룹은 초대 링크를 통한 접근으로 가입 권한을 얻을 수 있습니다.
- 초대 링크는 일정 기간 동안 해당 링크를 타고 들어오는 사용자들에게 가입 권한을 줍니다
- Redis에 해당 key 값을 저장하고 일정 주기로 만료된 초대 링크를 찾아 Redis에서 삭제합니다
- 가입 권한은 Redis에 해당 회원에게 권한을 얻은 groupId를 저장하여 확인하였습니다.
그룹 탈퇴 시 해당 그룹에 남아있는 사람의 존재 여부를 확인한 후 그룹 탈퇴와 나아가 그룹 삭제까지 수행합니다
- GroupMember가 남아있는 경우 : 그룹 탈퇴
- 해당 그룹과 회원 사이의 그룹 멤버의 ActiveStatus를 False로 변경하는 것으로 최대한 DB 접근 없이 그 GroupMember의 내역들을 유지하였습니다
- 또한 재가입의 경우 해당 GroupMember의 ActiveStatus를 True로 변경하여 기존의 내역들을 열람 가능하도록 하였습니다
- GroupMember가 남아있지 않은 경우 : 그룹 삭제
- 해당 그룹에 남아있는 모든 비활성 GroupMember, Page에 관해 삭제 후 Group 삭제를 진행하였습니다
위키키 서비스는 그룹원들끼리 사용할 수 있는 위키식 게시판을 제공하는 서비스입니다. 따라서 그룹 안에 여러 개의 페이지가 존재하며, 각 페이지 안에는 여러 개의 글들을 트리구조로 작성을 할 수 있습니다.
-
페이지와 관련된 기능으로는 다음과 같습니다.
- 페이지 조회/생성/삭제
- 페이지 좋아요/싫어요
- 페이지 키워드 검색
- 최근 바뀐 페이지 목차 조회
- 페이지 링크 걸기
-
글과 관련된 기능으로는 다음과 같습니다.
- 글 작성/수정/삭제
- 글 히스토리 조회
- 댓글 조회/작성/수정/삭제
- 글 신고
특정 페이지에 들어가면, 해당 페이지에 속한 모든 글들을 제공해줍니다. 글들이 트리구조로 이루어져 있으며, 각 글들에 해당하는 목차도 제공하고 있습니다. 글 생성시에 1.1과 1.2 사이에 글을 추가하게 되면 기존 1.2는 1.3으로 밀리며 새로운 1.2가 생기게 됩니다. 직접 DB에 각 글들에 대해 목차라는 칼럼을 생성하여 넣어주게 되면, 유연하게 목차를 생성하기가 어렵다고 판단하여 글 생성시에 Index를 저장하는 방식이 아닌 조회시에 Index를 계산하여 응답해주는 방식으로 구현하였습니다.
IndexUtils 라는 클래스를 하나 생성하여 @Component를 설정하여 컨테이너에 등록해주었습니다. 해당 클래스에 있는 HashMap<Long, String> createIndex(List posts) 메소드를 사용하여 트리 구조로 구성된 글들을 DFS를 통해 목차를 생성해줍니다.
위키키 서비스에서 같은 그룹 내에 있는 페이지들 사이에 이동할 수 있는 링크를 설정할 수 있도록 설계하였습니다. 사용자에게 링크를 걸려고하는 페이지가 해당 그룹 내에 존재하는지 실시간으로 확인시켜줍니다. 글자의 변화를 실시간으로 감지하여 요청을 날리기 때문에 서버에 순간적으로 많은 요청이 오게 됩니다. 모든 요청마다 RDBMS를 접근을 하게 되면 부하가 너무 심할 것으로 예상하고 이 부분을 Redis를 활용하여 개선하였습니다.
처음에는 redis에 string 자료구조를 사용하여 groupId_pageTitle을 key로 pageId값을 value로 해서 저장하였습니다. 1000개의 그룹이 존재하고, 각 그룹이 500개의 페이지를 차지하고 있을 때, redis의 메모리 사용량을 확인해보니 26.71MB를 차지하고 있었습니다. 해당 서비스에서 DB 부하를 줄이고 빠른 응답을 위해 Redis를 영구적인 저장공간으로 사용했기 때문에 최대한 메모리 사용량을 줄이기 위해 노력했습니다.
Redis는 Key 값에 대해서 Overhead가 발생하기 때문에, Key의 갯수를 줄일 수 있다면 메모리사용량을 줄일 수 있을 것이라고 판단했습니다. Redis의 저장을 Hash 자료구조로 변경하여 groupId를 key로 하고 PageTitle을 HashKey로 pageId를 value으로 수정하였습니다. 1000개의 그룹이 존재하고, 각 그룹이 500개의 페이지를 차지하고 있을 때, redis의 메모리 사용량을 확인해보니 9.86MB 만큼 차지하고 있었습니다.
즉, 약 250% 정도의 저장공간을 감소시킬 수 있었습니다.
Hash 자료구조는 hashKey가 512개를 넘어가면 zip 자료구조에서 hashtable로 변경되기 때문에 하나의 그룹에 페이지가 512개를 넘어가게되면 메모리 사용량이 갑자기 증가하는 문제가 있었지만, 증가해도 String 자료구조로 저장했을 때와 메모리 사용량이 비슷하며, 그러한 그룹들이 많이 생기지 않을 것으로 예상하고 해당 방법을 채택하였습니다.
페이지 키워드 검색시에는 해당 그룹내에서 키워드를 포함하고 있는 모든 페이지들을 페이지네이션으로 제공하였습니다. 또한 페이지 안에서 제일 위에 있는 글의 내용도 함께 보여주도록 구현을 하였습니다.
구현을 하는 과정에서 페이지와 가장 상단에 있는 글들을 보여주기 위해 Fetch join과 Pagination을 동시에 사용하였는데, 밑에 글과 같은 경고 문구가 나타났습니다. fetch join 쿼리 결과를 전부 메모리에 적재한 뒤, Pagination 작업을 어플리케이션 레벨에서 처리한다는 경고였습니다. 추후에 글들이 많이 쌓이게 되었을 때, 성능상으로 치명적이라고 판단을 하고 이 부분을 2개의 쿼리를 통해 해결하였습니다.
HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
먼저 DB로부터 그룹 내에 해당 키워드를 포함하는 페이지 10개를 받아오는 select 쿼리를 날렸습니다. 그리고 나서 해당 페이지ID를 PK로 가지고 있으며, 가장 상단에 있는(Order = 1)인 Post들을 IN연산자를 활용하여 조회하는 쿼리를 날려서 해결하였습니다.
- Logback을 사용하여 로그를 생성하였습니다
- !Prod 환경 파일에서 실행될 경우 Console 창에 debug 레벨까지의 log를 생성하였습니다
- Prod 환경 파일에서 실행될 경우 ./log 디렉토리 내부에 info, warn, error 레벨 로그 파일들을 각각 생성하여 저장하였습니다
- 각 레벨의 로그 파일들은 maxFileSize 100MB, maxHisotry 30일, totalSizeCap은 3GB
- 로그 포맷은 [%d{yyyy-MM-dd HH:mm:ss}:%-4relative] [%thread] %-5level [%C.%M:%L] - %msg%n 로 설정하였습니다
git clone https://github.com/Step3-kakao-tech-campus/Team8_BE.git
// 깃헙 주소 클론
// 그 뒤 로컬 저장소로 이동
./gradlew build
// 프로젝트 빌드
java -jar ./build/libs/wekiki-0.0.1-SNAPSHOT.jar
// 빌드 파일 실행
MIT License
Copyright (c) 2023 [1기] 카카오 테크 캠퍼스 (3단계) 프로젝트 8조
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.