2019.11.27 NHN FORWARD

‘깃’깔나는 Git 워크플로 알아보기

NHN Edu 서버개발팀 신승엽님

주요 Git 워크플로우 살펴보기

Git flow

  • 많은 도구에서 공식으로 지원하는 브랜치 모델
  • 메인 브랜치
    • 항상 존재한다.
    • master 브랜치
    • develop 브랜치
      • 다음 버전 개발을 위한 코드가 들어있다.
  • 서포팅 브랜치
    • 병렬 업무 가능
    • 필요할 때 생성, 역할을 완료하면 삭제
    • feature 브랜치
      • 하나의 기능을 개발하기 위한 브랜치
      • develop에서 생성하고 완료되면 develop에 merge한다. 이때, 항상 merge커밋을 찍는다.
    • release 브랜치
      • 소프트웨어 배포를 위한 준비를 하기 위한 브랜치
      • 소소한 버그나 메타 데이터를 변경한다.
      • 태그를 이용해 버전을 표현한다.
      • master, release에 모두 반영한다.
      • 다음 버전 준비를 계속할 수 있다.
    • hotfix 브랜치
      • 기본적으로 release와 비슷하지만, 배포 버전의 문제를 고치는 것이기 때문에 master에서 나온다.
      • 이 커밋 역시 master, release에 merge한다.

GitHub flow

  • 사람들이 워크 플로우를 이해하기 쉽게 되어 실수가 없어지고 더 이상 헤매지 않게 되었다.
  • master 브랜치
    • 항상 stable한 상태(반드시 언제 배포해도 문제가 없어야한다. 해당 커밋기준으로 만들어도 문제가 없어야한다.)
  • topic 브랜치
    • feature와 동일한 기능
    • 새로운 기능을 개발할 때 사용하며, master 브랜치에서 시작한다.
    • 기능을 설명하는 명확한 이름을 가진다.
    • 업무가 완료되지 않아도 꾸준히 커밋을 찍는다. 이는 커뮤니케이션을 하기 쉽다.
    • 배포
      • 모든 토픽 브랜치의 CI 빌드가 통과해야한다.
      • 배포락이 가능하다.
      • master 브랜치 최신 커밋이 topic에 반영되어 있는지 확인한다.
    • 배포에 이상이 없다면, master에 topic를 merge한다. 이때, 배포 락이 해제된다.

GitLab flow

  • Git flow는 너무 복잡하다.
    • release, hotfix 브랜치가 너무 복잡하다. 너무 지나친 규칙을 가지고 있다.
  • GitHub flow는 너무 간단하다.
    • 지속적인 배포의 모범 사례
    • 릴리즈 소프트웨어, EC와의 통합은 어떤 식으로 해야하는지 설명되어있지 않는다.
  • 지속적인 배포가 어려울 때,
    • 실서비스에 나가있는 커밋들을 위한 production 브랜치가 존재한다.
    • master 브랜치를 production에 merge함으로써 새로운 버전을 배포할 수 있다.
    • 거의 정확한 배포시각이 merge커밋에 나타난다.
    • 배포 스크립트에 tag를 생성하도록 한다면, tag가 생성된 시간 = 배포가 나간 시간
  • 환경별 배포가 필요할 때,
    • 각 환경에서 테스트를 통과한 경우만 다른 환경으로 배포가 가능하다.
    • hotfix의 경우, feature 브랜치로 만들고 Pull request를 통해 master로 merge한다. 만약 마스터가 자동 테스트를 통과했다면, feature 브랜치를 각각의 브랜치에 머지해야한다.
  • 릴리즈 소프트웨어일 때,
    • 버전 별 브랜치를 master에서 생성한다.
    • 버그 픽스는 master에 머지하고 각 버전 브랜치에 cherry-pick한다.
      • 버전 브랜치에 merge를 먼저 한다면, master로 다시 cherry-pick하는 것을 잊어버릴 수 있기 때문에 다음 버전에서 버그가 다시 출현할 수 있다.
      • 이것은 linux 커널을 먼저 hotfix하고 각 배포판에 적용하는 upstream-forced와 비슷하다.
    • 릴리즈 브랜치에는 bug-fix를 할 때마다 태그로 patch버전을 표시한다.
    • latest 브랜치로 최신 버전을 체크할 수 있다.
  • 개발이 완료되지 않았더라도 중간 결과를 팀 내 공유하라
    • 몇시간동안 한가지 기능을 개발 중이라면, Pull Request를 만들어서 팀 내에 공유하고 피드백을 받는다.
  • 항상 이슈 트래커 시스템에 이슈를 생성하라
    • 변경사항에 대해서 팀 내에 공유가 가능한다.
    • feature 브랜치 내에서 이것저것을 하지않게 되어 변경사항을 작게 유지하는데 도움이 된다.
  • Commit을 자주하고 push도 자주하라
    • commit을 작게 나누면, 나중에 코드를 봤을 때 컨텍스트 이해에 도움이 된다.
    • 완료되지 않았어도 merge/pull request를 통해 논의와 피드백을 받으면 코드향상에 도움이 된다.
  • Merge전에 테스트하라
    • 검증되지 않은 코드가 master로 들어가는 것을 예방할 수 있다.

우리는 이렇게 해요!

브랜치 전략

  • git flow를 기반으로 하되, develop의 역할을 하는 브랜치를 여러개 두는 방식으로 진행하고 있다.
  • develop-YYMMDD 또는 develop-tomato 와 같이 이름을 지정해 개발 일정 브랜치를 생성한다.
    • develop에서 생성한다.
    • 개발 일정 브랜치에서 각 feature 브랜치를 생성한다.
      • PR를 통해 리뷰하고 머지한다.
      • rebase를 이용해 history가 정돈될 수 있도록 한다.
    • 개발이 완료되면, 개발 서버에 배포하고 QA를 한다. QA변경사항도 feature를 따서 개발 일정 브랜치에 넣는다.
      • 일정마다 별도의 브랜치가 존재하기 때문에 git flow에서의 release 브랜치의 존재 의미가 없어졌다.
    • QA가 완료되면, 개발 일정 브랜치를 master와 develop으로 각각 머지한다. 그리고 master에 tag를 생성한다. 실서비스에는 master 브랜치를 배포한다.
    • 나머지 개발 일정 브랜치를 develop으로 rebase해준다. 이때, -p옵션을 이용해 머지 커밋을 유지하며 rebase한다.
      • rebase를 하면, 연속된 충돌 현상을 겪을 때도 있다.
        • 동일한 이름으로 현재 feature 브랜치를 다른 이름으로 변경한 후, 동일한 이름으로 rebase된 개발 일정 브랜치에서 새로운 feature브랜치를 생성한 후 cherry-pick을 통해 진행한다.
  • hotfix는 git flow의 hotfix 절차를 그대로 따른다.
    • hotfix 브랜치를 생성한 후 master와 develop에 각각 머지한다.
    • hotfix도 PR를 생성한다. 이때는 hotfix -> master로 머지되는 PR를 생성한다. 다만, PR를 통해 머지하지 않고 코드리뷰와 테스트만 진행한 후, git flow doc을 이용해 머지한다.

개발 플로우

  1. Dooray! Project 업무 생성

    • jira와 같이 업무 구현 내용, 완료조건을 적는다.
  2. 개발이 완료되면, Pull Request를 생성한다.

    • 코드리뷰에서 승인을 받았는지 확인한다.
    • 최신 develop 커밋이 모두 들어가 있는지 체크한다.
    • 특정 테스트를 통과했는지 체크한다.
  3. Jenkins를 이용한 자동 테스트

    • Github Pull Request Builder 플러그인이 있으면, 자동으로 PR을 날렸을 때, jenkins가 동작한다.
  4. SonarQube를 이용한 정적 분석

    • 실수나 취약점이 발생할 만한 코드를 알려준다.
  5. Pull Request merge

    • 기계적인 테스트, 코드 리뷰는 자동으로 할 수 있다.
      • 사람은 좀 더 비즈니스 로직 등에 집중하여 리뷰할 수 있도록 한다.


HTTP API 설계, 후회, 고민

설계와 후회

GET /../files/{file-id}의 의미?

  • 파일 다운로드?
  • 파일 메타데이터(이름, 크기, )?

Content Negotiation Header를 사용하는 것이 표준을 따르는 방식

  • Accept: application/json
  • Accept: image/png

업무 목록에서 파일 첨부 여부를 알고 싶습니다.

  • GET /../projects/{p-id}/tasks 에서는 파일 정보를 가지고 있지 않다.
  • 그래서 for문을 돌면서 GET /../projects/{p-id}/task
  • 해결 방법
    • hasFiles: true필드를 추가
    • fileIds: [“1”]
    • fileCount: 1

프로젝트에서 태그를 달고 싶습니다.

  • 파일처럼 생각할 수 있다.

업무를 다른 프로젝트로 옮기고 싶습니다.

  • 이전 URL: /orgs/{o-id}/projects/{old-project-id}/tasks/{old-number}
  • 이후 URL: /orgs/{o-id}/projects/{new-project-id}/tasks/{new-number}

프로젝트 옮긴 후 URL이 변경됨 별도의 관리가 없으면, 기존 URL로 접근하는 경우 갑자기 업무가 사라진 것으로 보이게 됨

내부적으로 변경 정보를 유지한다. 기존 URL로 요청이 온 경우, 새로운 URL로 redirect하도록 내부적으로 수정한다.

메일 읽음 표시를 해주세요.

  • GET은 safe한 메소드이기 때문에 값이 변경되면 안된다.

이전

GET /mails/{mail-id}?preview=false

이후

GET /mails/{mail-id}
PUT /mails/{mail-id}

PUT vs PATCH

  1. PUT
    • 전체 Update가 가능하다.
    • null이 의미를 가지는 필드 업데이트 등에 어려움이 있다.
  2. PATCH
    • Partial Update가 가능하다.

고민

HTTP Cache 적용 쉬울까?

  • 파일이 변경이 되었을 때, task에 변경을 끼치게 된다.
    • ETag 헤더 응답을 고려한다면, 하위 resource의 변경이 상위 리소스 ETag에 영향을 미치는 경우에 대한 고려 필요
    • 통일성 없이 수작업으로 챙겨야 할 가능성 - 영향도가 변경되는 변화가 있을 수도 있음

다른 값으로부터 계산된 속성을 표현해야 하는 경우발

  • 글의 권한 정보를 응답에 넣어주면, 클라이언트는 그려주기 편하다.
  • permission만 제외하고 캐시를 한다?

공용 정보와 개인별 정보를 같이 응답하는 경우

  • 개인적인 정보(좋아요)를 제외한 공용 정보만 캐시한다?

미리 잘 정해두면 좋은 것

  • 리소스에 어울리는 단어들 - 비슷한 뜻을 가지는 것
  • flag성 이름 - 규칙을 정하자
    • _flag, _able
    • is, has, can_
  • 날짜, 날짜 시간 필드명
    • _At, _On, DateTime
    • Date
  • 날짜 시간값 포맷
    • ISO8601, UNIX Epoch Time
  • query parameter도 정확한 이름을 가지는 것이 좋다.


쿠폰듀스X101: 가장 좋은 쿠폰을 픽 하려다 만난 CPU full load 개선기

커머스P개발팀 황도영님

1. 무엇이 문제일까?

Heap Memory 그래프를 통해 트래픽이 많은 건지 다른 문제가 있는지 확인하기 어렵다. CPU Thread Dump했을 때, 무언가가 반복 호출되었다. 최대 쿠폰 적용 금액 계산 API가 문제였다.

2. 왜 느릴까?

  1. 물건을 장바구니에 담는다.
  2. 배송지를 입력한다.
  3. 할인/포인트를 선택한다.
  4. 결제한다.

NCP는 다양한 쿠폰 기능을 제공한다. 그래서 매핑하는데 어려움이 있었다.

상품-쿠폰 매칭이슈

  • Q. 가장 좋은 쿠폰을 찾는 것은 어떨끼?
    • A: 언제나 절대적으로 가장 좋은 쿠폰이란 것은 없다.
  • Q. 가장 할인액이 큰 순서대로 매칭하면 어떨끼?
    • A: 최대 할인 쿠폰을 다른 상품에게 양보해야 최적일 수도 있다.
  • 탐욕법 적용이 어렵다.

다양한 쿠폰들

  • 상품 쿠폰은 플러스 쿠폰은 서로에게 영향을 주지 않는다. = 서로에게 독립적인 관계
    • 플러스 쿠폰은 정가에 의존한다.
  • 장바구니 쿠폰은 상품 쿠폰, 플러스 쿠폰에게 영향을 준다. = 의존적인 관계
    • 장바구니 쿠폰의 할인 기준 제한 금액이 존재하면서, 제한 금액보다 적은 경우에는 쿠폰 사용이 취소된다.

BigO((M * N)^p)*C 상품 개수가 점차 늘어남에 따라 연산 횟수가 폭발적으로 증가한다.

함께 구매 가능한 최대 상품수 = 100개 회원당 받을 수 있는 최대 쿠폰 수 = 무한대
=> 해당 알고리즘은 상품수 100개조차 감당할 수 없다.

3. 해결해보자!

아이디어1. 쿠폰들 의존관계 끊기

  • 장바구니 쿠폰은 API에서 잠시 제외하고 추후 해결한다.
  • 이제 상품 쿠폰과 플러스 쿠폰은 서로 독립적으로 되는가?
    • 사실 “두 쿠폰의 할인가 합 > 상품가격” 인 경우, 두 쿠폰은 독립적이지 않다.
    • 정책 변경
      • 마이너스 판매가는 무시하고 결제 시 체크한다.

BigO(M*N)^p -> BigO M^p + N -> BigO M^p

아이디어2 M^p 알고리즘 개선

  • Assignment problem
  • 헝가리안 알고리즘
    1. input을 X와 Y의 크기가 같은 K * K의 정방 행렬로 만들어준다.
      • 부족한 경우, Mock 상품 0으로 채운다.
    2. 여기서는 최소가 아닌 최대를 원하기 때문에, 가장 큰 수에 각 숫자를 뺀다.
      • 각 행에 같은 수로 빼면, 값은 달라지지만, 대소 관계는 달라지지 않는다.
        1. 행렬의 각 행과 열에서 최소값으로 각각 빼준다.
        2. 최소 라인으로 0인 성분을 셀을 지운다. 이 라인이 K개라면 답이 존재한다.
        3. 남은 수 중 0이 아닌 가장 작은 수로 각각 뺀다. 음수 발생 시, 0 이상의 수가 되도록 다시 더한다.
        4. 2로 돌아간다. 서로 겹치지 않은 0의 위치가 해가 된다.

4. 개선 후 결과

BigO((M * N)^p)*C -> BigO K^3 + K^3

1시간 정도 걸리던 작업이 0.8초와 같이 줄었다.