본문으로 건너뛰기

2023.07.26

· 약 14분

0. 일상

  • 당근에 온지 1주년이 되었다. 시간 엄청 빠르다. 1주년 회고도 써야겠다.
  • "아주 작은 습관의 힘" 책을 읽고 있는데 그렇게 많이 도움이 되지는 않는다. 하지만 끝까지 읽어보려고 노력중
  • 오늘 오후에 문화의 날 활동으로 레드와인 테이스팅을 한다. 7시에 와인수업도 있어서 오늘은 와인데이

1. NextJs Dockerfile

인터넷에 올라오는 것들은 node_modules 사용하는게 대부분이라 yarn berry 사용하는 버전으로 만든거 기록용으로 저장. 누군가는 쓰겠지...

nextConfig의 output에 "standalone" 설정하는거 잊지말자

standalone에 대한 설명은 여기서: https://nextjs.org/docs/pages/api-reference/next-config-js/output#automatically-copying-traced-files

참고: https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile

FROM node:18-alpine AS base

# Install dependencies only when needed
FROM base AS deps

RUN apk add --no-cache libc6-compat
WORKDIR /app

# Install dependencies based on the preferred package manager
COPY package.json ./
COPY yarn.lock ./
COPY .pnp.cjs ./
COPY .pnp.loader.mjs ./
COPY .yarnrc.yml ./
COPY .yarn ./.yarn
RUN yarn install --immutable

# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/.yarn ./.yarn
COPY --from=deps /app/.pnp.cjs ./pnp.cjs
COPY . .

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1

RUN yarn build

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.pnp.cjs ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

RUN rm -rf ./.yarn/cache
COPY --from=builder --chown=nextjs:nodejs /app/.yarn ./.yarn/

USER nextjs

EXPOSE 3000

ENV PORT 3000

# CMD ["node", "server.js"]
CMD ["node", "-r", "./.pnp.cjs", "server.js"]

2. Jest Coverage Comment

ci 단계에서 unit test coverage 측정할 때 codecov actions를 사용하는 경우가 많은데 그렇게 되면 내부 코드가 codecov 로 전송된다는 매우 큰 리스크가 있다. 이에 괜찮은 새로운 actions를 찾아 공유한다.

바로 요것이다. https://github.com/marketplace/actions/jest-coverage-comment

comment도 이쁘게 잘 달아준다.

step 1) 필요 패키지를 설치한다.

yarn add -D jest-junit

step 2) ci용 jest.config 파일을 만든다.

ci 스크립트에서 jest cli 를 활용하는 방법도 있지만 이게 훨씬 깔끔하다.

jest.ci-config.ts
import type { Config } from 'jest';

import defaultConfig from './jest.config';

const config: Config = {
...defaultConfig,
coverageReporters: ['json-summary', 'text'],
reporters: [
'default',
[
'jest-junit',
{
outputDirectory: './coverage',
},
],
],
};

export default config;
coverage.yml
name: Unit Test Coverage

on: [pull_request]

env:
CACHED_DEPENDENCY_PATHS: ${{ github.workspace }}/.yarn/unplugged
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEFAULT_NODE_VERSION: "16.16.0"

jobs:
coverage:
runs-on: [self-hosted]
steps:
- uses: actions/checkout@v3

- name: Set up Node
uses: actions/setup-node@v2
with:
node-version: ${{ env.DEFAULT_NODE_VERSION }}

- name: Install dependencies
run: yarn install --immutable

- name: Check Unit Test Coverage
run: |
mkdir -p coverage
yarn test:unit --coverage --config=jest.ci-config.ts | tee ./coverage/coverage.txt && exit ${PIPESTATUS[0]}

- name: Jest Coverage Comment
id: coverageComment
uses: MishaKav/jest-coverage-comment@main
with:
coverage-summary-path: ./coverage/coverage-summary.json
title: Test Coverage
summary-title: Coverage Report
badge-title: Coverage
hide-comment: false
create-new-comment: false
hide-summary: false
junitxml-title: Test Suites Summary
junitxml-path: ./coverage/junit.xml
coverage-title: Coverage
coverage-path: ./coverage/coverage.txt

만약 가져다 사용하는 사람이 있다면 runs-on은 아마 바꿔야할 듯하다. 나는 github enterprise로 self-host를 띄우기에 저렇게 설정하였다.

Check Unit Test Coverage step을 보면 mkdir -p coverage 는 tee command가 폴더가 없으면 파일을 만들지않기에 미리 만들어주는 것이다. tee ./coverage/coverage.txt 는 coverage script 돌고 나오는 마지막 summary table을 coverage.txt 파일에 입력시키는 명령어다.

3. 아주 작은 습관의 힘

목표 따윈 쓰레기통에 던져버리기

흔히 원하는 것을 얻으려면 구체적으로 실행 가능한 목표를 세워야 한다고 말한다.

나 역시 습관에 대해 오랫동안 이런 관점에서 접근했다. 습관 하나하나는 곧 도달해야 할 구체적인 목표였다. 하지만 그런 목표들 중에서 성공한 것은 극히 일부였고 대부분 실패했다. 나는 내가 얻어낸 결과들이 처음에 세웠던 목표와는 거의 관계가 없고, 사실 모든 것은 시스템에 달려 있다는 것을 깨달았다.

시스템과 목표의 차이는 무엇일까? 목표는 우리가 얻어내고자 하는 결과이며, 시스템은 그 결과로 이끄는 과정이다.

  • 감독의 목표는 챔피언십을 획득하는 것이다. 그렇다면 시스템은 선수들을 선발하는 방식, 코치들을 다루는 방식, 실행하는 방식이다.
  • 기업가의 목표는 수백만 달러짜리 사업을 세우는 것이다. 그렇다면 시스템은 제품이나 서비스에 대한 아이디어를 테스트하는 법, 직원을 고용하는 법이다.
  • 악기 연주자의 목표는 새로운 곡을 연주하는 것이다. 그렇다면 시스템은 '몇 번 연습할 것인가, 어떻게 틀을 깨고 다른 방식으로 연주할 것인가, 배운 내용을 어떻게 나만의 것으로 소화할 것인가'가 된다.

이제 흥미로운 질문을 해보자. 목표를 완전히 무시하고 오직 시스템에만 집중한다면 그래도 성공할까? 나는 '그렇다'고 생각한다. 어떤 스포츠든 목표는 최고의 점수를 달성하는 것이다. 하지만 그렇다고 해서 경기 내내 점수판만 응시하는 건 말도 안되는 짓이다. 실제로 승리할 유일한 방법은 매일 더 나아지는 것뿐이다.

더 나은 결과를 내고 싶다면 목표를 세우는 일은 잊어라. 대신 시스템에 집중하라. 목표가 무용지물이라는 말은 아니다. 목표는 방향을 설정하는 데 필요하며 시스템은 과정을 제대로 해나가는 데 필요하다. 그러나 목표를 생각하느라 너무 많은 시간을 들이고 시스템을 고안하는 데는 시간을 투자하지 않을 때 문제가 발생한다.

문제 1. 성공한 사람도, 성공하지 못한 사람도 목표는 같다.

올림픽에 출전한 선수 모두가 금메달을 원한다. 입사 지원자 모두가 구직을 바란다. 성공한 사람도, 성공하지 못한 사람도 목표는 같다. 목표는 승자와 패자를 가르는 차이가 될 수 없다.

목표는 늘 거기에 있다. 결과에 차이가 생긴건 지속적으로 작은 개선들을 만들어내는 시스템을 시행한 것, 그뿐이였다.

문제 2. 목표 달성은 일시적 변화일 뿐이다.

지저분한 방이 있다고 생각해보라. 방을 치우기로 목표를 세운다. 그리고 당장 청소하는 데 필요한 에너지를 끌어올려 방을 치웠다. 하지만 대충대충 청소하거나 뭐든 잘 버리지 못하는 사람이라면 방은 또 지저분해질 것이다. 계속 같은 결과가 나타난다는 것은 이런 결과의 배경이 된 시스템을 바꾸지 못했기 때문이다. 원인을 다루지 않고 증상만을 치유한 것이다.

목표를 달성하는 것은 우리 인생의 '한순간'을 변화시킬 뿐이다. 이는 '개선'과는 다르다. 우리는 결과를 바꿔야 한다고 생각하지만 사실 그 결과는 문제가 아니다. 진짜로 해야 할 일은 결과를 유발하는 시스템을 바꾸는 것이다. 결과 수준에서 문제를 해결하려고 하면 이는 임시방편일 뿐이다. 영원히 개선하고자 한다면 결과가 아니라 시스템 단계에서 문제를 해결해야 한다.

문제 3. 목표는 행복을 제한한다.

목표 뒤에는 이런 가정이 내포되어 있다. '목표에 도달하면 행복해질 거야' 목표를 우선으로 생각하는 태도의 문제는 다음 표지판에 도달할 때까지 행복을 계속 미룬다는 것이다. 나는 수없이 이런 함정에 빠져 내가 뭘 하는지 잊곤 했다. 수년 동안 나에게 행복이란 미래에 있는 것이었다.

목표는 '이것 아니면 저것'이라는 양자택일적 갈등을 만들어낸다. 목표를 달성하면 성공하는 것이고, 달성하지 못하면 실패하는 것이라고 말이다. 실제 삶의 행로는 우리가 마음속으로 정해놓은 여정과 정확히 일치하지 않는다. 성공으로 가는 길은 수없이 많다. 굳이 하나의 시나리오에만 자신의 길을 맞출 이유는 없다.

시스템 우선주의는 그 해독제를 제공한다. 결과가 아니라 과정을 좋아하게 되면 '이제 행복해져도 돼'라고 말할 시기를 기다리지 않아도 된다. 시스템이 작동하고 있다면 어느 때건 만족할 수 있기 떄문이다. 시스템은 우리가 처음 상상했던 한 가지 결과가 아니라 다양한 형태로 성공할 수 있게 해준다.

문제 4. 목표와 장기적 발전은 다르다.

마지막으로 목표 중심적 사고방식은 '요요 현상'을 불러올 수 있다. 달기기 선수들은 경기가 있으면 몇 달 동안 열심히 운동한 끝에 결승선을 통과한다. 그리고 당분간은 훈련을 멈춘다. 이미 끝난 경기는 더 이상 동기를 자극하지 않는 것이다.

특정한 목표를 이루기 위해 노력했다면, 그것을 달성한 뒤에 무엇이 남아 우리를 앞으로 나아가게 할까? 이 때문에 많은 사람이 목표를 달성하고 나면 과거의 습관으로 쉽게 돌아가곤 한다.

목표 설정의 목적은 게임에서 이기는 것이다. 반면 시스템 구축의 목적은 게임을 계속 해나가는 것이다. 장기적으로 발전하기 위해서는 목표 설정보다는 시스템을 구축해야 한다. 성취하는 것이 아니라 계속해서 개선하고 발전해나가는 순환 고리를 만드는 것이다. 즉, '과정'에 전념하는 것이 '발전'을 결정한다.

4. 와인 앱

크롤링한 데이터를 전부 firestore에 업로드 하였다. firestore 사용량 제한이 있어서 한 번에 모두 업로드는 못하고 3~4일에 거쳐서 모두 업로드 했다. 총 28,523 개로 vivino, wine21 collection을 따로 해서 저장했다. 대략 55000 write를 했다.

참고로 firestore 의 경우 쓰기 2만/day, 읽기 5만/day, 삭제 2/day 까지 무료다. blaze 플랜으로 업그레이드하면 무료 사용량 초과한 만큼 비용이 발생한다.

이제 검색기능을 만들 차례다!