각종 후기/우아한테크코스

[우아한 테크코스 3기] LEVEL 3 회고 (176일차)

제이온 (Jayon) 2021. 7. 27.

안녕하세요? 제이온입니다.

 

4일간 있었던 일을 간략하게 기록하려고 합니다.

 

 

Self-hosted runner를 도커 컨테이너로 띄우기 [포기]

며칠동안 붙잡았던 Self-hosted runner를 도커 컨테이너로 띄우는 방법을 결국 포기했습니다.

 

 

    - name: Copy built project
      if: success()
      uses: appleboy/scp-action@master
      env:
        GIT_USERNAME: ${{ secrets.GIT_USERNAME }}
        GIT_PASSWORD: ${{ secrets.GIT_PASSWORD }}
      with:
        host: ${{ secrets.EC2_HOST }}
        username: ${{ secrets.EC2_USERNAME }}
        key: ${{ secrets.EC2_PRIVATE_KEY }}
        envs: GIT_USERNAME, GIT_PASSWORD
        source: '/home/runner/_work/2021-darass/2021-darass'
        target: '/home/ubuntu/test'
        strip_components: 1

 

 

현재 도커 컨테이너 안에서 workflows를 진행 중이므로 위와 같이 scp 라이브러리의 source 위치를 도커 컨테이너 내부 빌드 프로젝트 위치를 적으면 되는 줄 알았습니다. 하지만, 운영 서버로 디렉토리 껍데기만 복사될 뿐 내부 내용물은 하나도 없었습니다. 신기하게도 self-hosted-runner로 수동으로 접속해서 scp 명령어를 입력하여 운영 서버로 빌드 프로젝트를 전송하니까 완벽하게 잘 전송이 되는 것을 확인했습니다.

 

결국은 다른 기능도 구현을 해야하므로 여기까지만 붙잡고 있기로 했습니다. 그래도 도커 volume도 학습하고, pem 키를 이용하여 scp 명령어를 학습하는 좋은 시간이었다고 생각합니다. 

 

 

페이지네이션

어제와 오늘은 페이지네이션을 학습했습니다. 현재 우리의 댓글 모듈 서비스는 개발 초기이므로 댓글의 개수는 당연히 적습니다. 하지만, 서비스 이용자가 조금 많아져서 하나의 포스팅에 달리는 댓글이 막 1000개가 되었다고 가정해 봅시다. 그리고 해당 댓글들을 하나의 페이지에서 모두 보여준다면 굉장한 스크롤 압박이 발생할 것입니다.

 

그래서 댓글 페이지를 1, 2, 3, ... n번으로 나누거나 [더보기] 버튼을 눌러서 일부의 댓글을 보도록 구현해야 합니다. 우리 팀은 댓글이 아무리 많이 달려도 현재로서는 100개 정도라고 판단하여 [더보기] 버튼을 통해서 추가 댓글을 볼 수 있게 하자고 합의를 보았습니다. 어쨌든 더보기를 누를 때마다 하나의 페이지에 해당하는 댓글들을 반환해야하므로 두 방식이 거의 똑같다고 볼 수 있긴 합니다.

 

페이지네이션을 구현하는 가장 쉬운 방법은 아래와 같습니다.

 

 

SELECT * FROM comment ORDER BY id desc LIMIT 4000000, 10000;

 

 

위 코드는 4,000,001번째 댓글부터 10,000개의 댓글을 보여주는 SQL문입니다. LIMIT의 offset은 열린 구간이라는 것에 유의해야 합니다. 하지만, 위 방식은 4,000,000개의 데이터를 모두 탐색해야 하므로 시간 복잡도가 최악일 경우 O(N)이 됩니다.

 

 

SELECT * FROM comment WHERE id > 4000000 LIMIT 10000;

 

 

그래서 id를 PK로 설정하여 인덱스로 구축하고 where절에서 id가 4,000,000번째 데이터보다 큰 id를 찾으라고 작성하면, 거의 O(1)에 가까운 시간으로 4,000,001번째 댓글을 찾을 수 있습니다. 여기서 부등호의 오른쪽 조건이 offset이라고 생각하면 됩니다.

 

 

하지만 우리의 갓JPA는 저런 SQL문을 고려하지 않아도 됩니다. 이미 다 기능으로 잘 구현되어 있기 때문이죠.

 

 

public interface PagingAndSortingRepository<T, ID extends Serializable>
  extends CrudRepository<T, ID> {

  Iterable<T> findAll(Sort sort);

  Page<T> findAll(Pageable pageable);
}

 

 

먼저 페이지네이션 기법을 적용하려는 Repository는 위 클래스를 상속 받아야합니다. PagingAndSortingRepository 인터페이스는 이름 그대로 데이터 정렬과 데이터 페이지네이션 메소드를 제공합니다. 저는 페이지네이션이 목적이므로 후자의 메소드를 선택했습니다.

 

 

PagingAndSortingRepository<User, Long> repository =
Page<User> users = repository.findAll(PageRequest.of(1, 20));

 

 

위와 같이 Pageable의 구현체인 PageRequest 객체를 활용하여 몇 번째 페이지에 해당하는 컬렉션을 반환할지 정해주고, 한 페이지당 몇 개의 컨텐츠를 구성할지 정해줄 수 있습니다. 위 코드는 하나의 페이지에는 20개의 컨텐츠가 들어가도록 설정하고, 그 중에서 2번째 페이지를 반환한다. 여기서 주의할 점은 페이지에 해당하는 값은 열린 구간이라는 것입니다.

 

 

    public static PageRequest of(int page, int size) {
		return of(page, size, Sort.unsorted());
	}

    public static PageRequest of(int page, int size, Sort sort) {
		return new PageRequest(page, size, sort);
	}

 

 

참고로 PageRequest 객체를 생성할 때 Sort 객체를 인자로 추가하여 정렬을 해 줄 수도 있습니다.

 

 

정리

오늘은 목표했던 페이지네이션 기법을 구현할 수 있었습니다. 내일은 해당 코드에 단위 및 통합 테스트를 작성하고, 댓글을 과거순, 최신순, 좋아요순으로 정렬하는 기능을 구현하려고 합니다.

댓글

추천 글