[협업 툴 개발 프로젝트] 프로젝트 개요
✅ 협업 툴 개발 프로젝트 주제 프로젝트 명 : SCV (Sparta Coding Virtual workspace) 소개 한 줄 정리 : 프로젝트 협업 관리 도구 여러 사용자들이 프로젝트를 관리하고, 팀원들과 협력하여 작업을 효율적
zapzook.tistory.com
프로젝트 진행 중 발생한 트러블 슈팅과 해결 과정에서 알게 된 새로운 알고리즘,
의사 결정 과정을 정리하고자 한다.
✅ 서론
이번에 진행한 팀 프로젝트의 주제는 Trello와 같은 협업 툴 개발 프로젝트였다.
사용해본 적이 있는 사람은 알겠지만, Trello의 도메인은 크게 3가지로 나뉠 수 있다.
보드, 컬럼, 카드이다. 보드는 이 중 가장 큰 개념이며, 하나의 팀이 진행하는 프로젝트라고 볼 수 있다.
컬럼은 카드가 놓이는 공간을 뜻하며, 예를 들자면 Todo(해야 할일), In-progress(진행중), Done(끝난 일)이
있을 수 있겠다. 카드는 하나의 작업을 의미한다.
나는 이중에서 컬럼 도메인을 맡아서 기능 구현을 하였다.
컬럼은 팀 멤버에 의해 자유롭게 생성될 수 있으며, 컬럼의 순서 역시 자유롭게 이동이 가능하다.
컬럼의 기본적인 CRUD 기능은 쉽게 구현하였지만, 컬럼의 순서 이동 로직을 구현하는 것은
생각보다 골치가 아팠다. 기능 자체를 구현하는 것은 쉬웠지만, 한 가지 큰 문제점이
발목을 잡았기 때문이다.
본 포스팅에선 이러한 데이터의 순서 이동 전략을 다룰 예정이다.
✅ 순서 이동 전략
public class BoardColumn {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String columnName;
@Column(nullable = false)
private Long position;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "board_id", nullable = false)
private Board board;
public void updatePosition(Long position) {
this.position = position;
}
}
다음과 같이 5개의 컬럼이 존재한다고 가정하자
각 컬럼은 1,2,3,4,5의 순서(position)를 가진다.
여기서 E컬럼의 순서를 5에서 1로 바꾸는 시나리오를 생각해보자.
📌 순서 바꾸기
이건 내가 가장 처음에 생각했던 순서 이동 로직이다.
E 컬럼의 순서를 1로 바꾸고, 원래 1의 순서를 가졌던 A 컬럼의 순서를 5로 교체하는 방식이다.
이 방식을 사용하면 구현 자체도 굉장히 간단하며, 컬럼의 순서 이동으로 인해 발생하는
업데이트 쿼리도 2개뿐이다. (E컬럼 순서 업데이트, A컬럼 순서 업데이트)
public void updateColumnPosition(Long boardColumnId, PositionUpdateDto requestDto) {
BoardColumn E = boardColumnRepository.findById(boardColumnId).orElseThrow();
BoardColumn A = boardColumnRepository.findByPosition(requestDto.getPosition());
A.updatePosition(E.getPosition());
E.updatePosition(requestDto.getPosition());
}
하지만 드래그 앤 드롭 시스템을 제공하는 일반적인 웹서비스에서 이런 순서 이동 방식은
거의 사용되지 않으며 뭔가 어색한 느낌이 강하다.
📌 순서 이동 및 정렬
첫 번째 방식은 뭔가 잘못 되었다고 느껴, 수정한 방식이다.
E 컬럼의 순서를 1로 변경할 때, 원래 앞에 있던 컬럼들의 순서를 한 칸씩 뒤로 미는 방식이다.
일반적인 드래그 앤 드롭 서비스는 이러한 순서 이동 방식을 제공한다.
하지만 이 방식으로 기능을 구현하고 나니, 비용적인 측면에서 문제점이 느껴졌다.
E 컬럼의 순서를 변경했을 뿐인데, 원래 그 앞 순서에 있던 A,B,C,D 컬럼의 순서까지
모두 업데이트를 해줘야 하기 때문이다.
public void updateColumnPosition(Long boardColumnId, PositionUpdateDto requestDto) {
BoardColumn E = boardColumnRepository.findById(boardColumnId).orElseThrow();
Long ePosition = E.getposition();
Long aPosition = requestDto.getPosition();
updatePosition(boardColumn.getBoard().getId(), ePosition, aPosition);
boardColumn.updatePosition(requestDto.getPosition());
}
private void updatePosition(Long boardId, Long ePosition, Long aPosition) {
if (aPosition > ePosition) { // 순서를 뒤로 이동하는 경우
List<BoardColumn> affectedColumns =
boardColumnRepository.findByBoardIdAndPositionBetween(boardId, ePosition + 1L, aPosition);
affectedColumns.forEach(c -> c.setPosition(c.getPosition() - 1));
} else if (aPosition < ePosition) { // 순서를 앞으로 이동하는 경우
List<BoardColumn> affectedColumns =
boardColumnRepository.findByBoardIdAndPositionBetween(boardId, aPosition, ePosition - 1L);
affectedColumns.forEach(c -> c.setPosition(c.getPosition() + 1));
}
}
구현된 코드만 봐도 굉장히 비효율적인 듯한 느낌이 물씬 풍긴다.
물론, 우리의 시나리오대로 컬럼의 갯수가 5개뿐이고,
컬럼의 순서 이동이 드물다면 큰 성능 이슈는 없을지도 모른다.
하지만 극단적인 예시로 컬럼의 갯수가 1000개라면?
컬럼 순서 이동 요청이 빈번하다면?
서버에는 분명 큰 부하가 생길 것이다..
실제로 더미 컬럼을 1000개를 생성한 뒤 Postman을 통해 실행 속도를 테스트 해봤다.
콘솔에는 수백개의 업데이트 쿼리가 찍혔고, 실행 속도는 793ms(..)가 나왔다.
따라서 위와 같은 로직을 프로젝트에 적용해서는 안되겠다고 판단했고,
개선된 순서 이동 로직을 찾아야 했다.
내가 바라는 개선된 순서 이동 로직의 요점은 하나였다.
컬럼의 순서를 이동하면 그에 맞게 순서가 정렬되지만, 업데이트 쿼리는 1개여야 한다.
마치 어려운 알고리즘 문제를 푸는 기분이였다.
컬럼의 순서를 이동하는데 다른 컬럼의 순서 변동 없이 순서를 정렬한다니,
처음엔 불가능하다고 생각했다.
하지만 결국 방법은 있었다.
📌 순서 이동 : Gap Strategy
문제를 해결하려 최대한 머리를 굴려보고 관련된 여러 레퍼런스를 알아보던 결과,
정답에 가까운 신박한 방식을 찾아냈다.
애초에 순서(position)를 연속되는 정수의 형태로 배정하지 않고,
각 순서 값에 차이(Gap)를 두는 방식이다.
이 방식을 사용한다면 E 컬럼의 순서를 1(A 컬럼의 앞)로 옮긴다 해도, E 컬럼의 순서값만 바꾸고
다른 컬럼의 순서값은 변경할 필요가 없어진다.
가령 위 그림과 같은 상태에서 A컬럼과 B컬럼 사이(3번째 순서)에 새로운 F 컬럼이 추가된다 해도,
F 컬럼의 순서값만 잘 조절해주면 다른 컬럼들의 순서값은 변경이 필요 없다.
로직의 특성상 두 순서값의 사이값을 계속 계산해줘야 하는데, 그 과정에서 나누기 2 연산이 계속 발생하기에
각 컬럼 순서 값의 차이는 2의 10제곱인 1024로 설정하였다. 이러면 나누기 2 연산이 계속되도
순서값에 소수점이 붙어서 가시성이 떨어질 일이 없다.
여담이지만 이러한 전략을 뭐라고 부르는지는 나도 잘 모르겠다.
관련 레퍼런스에도 전략의 이름에 대한 설명은 없었고, 챗 GPT 피셜 Gap Strategy라고 한다.
코드 상에서의 구현은 본 포스팅에선 생략하도록 하겠다.
궁금하다면 프로젝트의 Github를 참고하길 바란다.
GitHub - KIM-TABLE-NEXT/SCV: NBC Spring - SCV
NBC Spring - SCV. Contribute to KIM-TABLE-NEXT/SCV development by creating an account on GitHub.
github.com
✅ 실행 속도 비교
📌 기존 로직
기존의 로직은 컬럼의 갯수가 10개일 때도 실행속도가 216ms로 상당히 느렸고,
컬럼의 갯수가 증가함에 따라 실행속도도 비례해서 증가하였다.
📌 개선된 로직
로직을 개선하고 나니 실행속도가 큰 차이로 빨라졌고,
컬럼의 갯수에 상관없이 업데이트 쿼리는 1개만 발생하기에 컬럼 갯수가 1000개여도
실행속도의 차이가 없는 모습이다.
'TIL' 카테고리의 다른 글
[Spring][Redis] RedisTemplate을 활용한 캐싱 처리 (0) | 2024.03.29 |
---|---|
[Spring][Redis] Redis 캐싱 기능을 활용한 조회 성능 개선 (CacheManager) (0) | 2024.03.28 |
JWT의 무상태성 (+쿼리 최적화) (0) | 2024.03.08 |
[Spring] CRUD 기능 구현 중 알게 된 Dto를 사용해야 하는 이유 (0) | 2024.02.26 |
프로젝트 개발 프로세스와 협업 (1) | 2024.02.08 |