본문으로 건너뛰기

IT 스타트업에서 경험한 첫 인턴십 (+래블업)

· 약 24분
김유정

지원과정

2021 오픈소스 컨트리뷰션 아카데미를 통해 래블업 인턴십에 지원할 수 있는 기회를 얻게 되었다.

Backend.AI 는 규모가 크고 어려워서 두 달이라는 시간이 짧게 느껴졌고 더 알아보고 싶다는 생각이 들었다. 부족한 점이 많아서 "내가 할 수 있는 일이 있을까?"라는 걱정이 들었지만, 실무를 경험해볼 좋은 기회라 생각하여 지원하게 되었다. 프로젝트에 참여하는 동안 멘토님들에게 많이 배웠고, 다 좋은 분들이었기 때문에 같이 일해보고 싶기도 했다.

서류 합격 후 처음으로 기술면접을 보게 됐다. 질문이 예상가지 않아 이것저것 봤는데, 오히려 역효과였던 것 같다.. 웹 프론트엔드 개발을 주로 해와서 웹과 프론트엔드 관련 지식을 물어보셨는데, 많이 대답하지는 못했다. 그냥 최선을 다해 답변했다. 다행히 합격했다는 연락이 왔다! 그리고 4일 후에 바로 인턴을 시작하게 되었다. 합격 통보나 면접같은 과정이 빠르게 진행됐다.

Orientation

래블업에서는 Backend.AI라는 솔루션을 개발&관리하고 있기 때문에, orientation에서는 Backend.AI를 설치하고 이용하는 과제들을 수행했다. 대부분 Backend.AI를 이용해서 머신러닝 모델을 개발하는 과제였다. 머신러닝을 공부해본 적이 없어서 막막했지만, 어떻게든 해냈다. 인턴십을 하면서 "계속 찾아서 하다보면 되는구나"라는 걸 가장 많이 느꼈다. 모르면 질문하고, 에러나면 고치고 하다 보면 어느샌가 완료한 나를 발견할 수 있었다.

Orientation에서 진행했던 task들은 아래와 같이 간단하게 정리해본다.

  • Task 1. Change MNIST code to be Fashion MNIST
  • Task 2. Increase the accuracy without changing the model
  • Task 3. Install Backend.AI
  • Task 4. Run code on Backend.AI CLI
  • Task 5. Run code on Backend.AI Web UI
  • Task 6. Increase the accuracy without changing the data
  • Task 7. Increase the accuracy within time limit

개인적으로 2번 task가 가장 어려웠다. 데이터 증강 기법을 이용하여 정확도를 높이려 했지만, 생각대로 되지 않았다. 뒤집은 이미지들을 추가하여 모델을 학습시켰는데, 이상하게 세로로 뒤집은 이미지를 추가하면 정확도가 내려갔다. 가로로 뒤집은 이미지만 추가했을 때 가장 정확도가 높아서 그 방법으로 과제를 제출했다.

업무

Backend.AI 는 여러가지 컴포넌트가 상호작용하면서 돌아가도록 구현되어 있다.

manager, agent, webui, common, client-py, kernels, storage-proxy, webserver 총 8개의 컴포넌트가 있다. 인턴십 기간에는 manager, agent, webui, common과 관련된 이슈들을 해결했다. 컴포넌트별로 진행했던 내용을 정리해보려 한다.

manager

API를 제공하고, 연산자원 모니터링 및 세션 스케줄링을 담당하는 컴포넌트이다. 핵심 서비스라 하는 일도 많고 코드도 많다. manager가 모든 컴포넌트들과 연결이 되어있어서 코드를 이해하는 게 어려웠지만, 전체적인 그림을 보는데 도움이 많이 되었다.

명령어 추가하기 (오래된 기록 삭제, 디스크 공간 확보)

PR(Merged): https://github.com/lablup/backend.ai-manager/pull/498

  • 이슈 내용

데이터베이스의 kernels 테이블에는 세션에 대한 정보들이 쌓인다. 이것들을 주기적으로 삭제하고, 디스크 공간을 확보하기 위해 postgreSQL의 vacuum 작업을 수행하여야한다.

→ 위 작업을 쉽게 수행할 수 있도록 mgr clear-history 명령어 추가하기

  • 진행과정 & 배운점

cli 명령어를 만들기 위해서 Click이라는 파이썬 패키지를 사용했다. 그리고 2가지의 옵션을 추가해서 조금 더 세부적으로 명령을 내릴 수 있도록 했다. 그런데 명령어를 실행했을 때, vacuum 작업을 하는 부분에서 다음과 같은 에러가 떴다.

VACUUM cannot run inside a transaction block

stackoverflow와 pretag라는 곳에 나온 답변들을 모두 시도해봤지만, 해결하지 못했다.

결국 공식문서밖에 답이 없었다. CREATE DATABASEVACUUM과 같은 명령어들을 트랜잭션 안에서 실행이 불가하기 때문에, AUTOCOMMIT 모드로 바꿔주어야한다. 그래서 아래의 방법을 사용하여 해결했다.

conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)

최대 컨테이너 수 조건을 검사하는 부분의 위치를 이동시키기

PR: https://github.com/lablup/backend.ai-manager/pull/504

  • 이슈 내용

세션을 생성할 때, 사용자는 세션의 이름, 타입, 자원, 클러스터의 모드, 클러스터의 사이즈와 같은 조건들을 설정할 수 있다. 그리고 이러한 세션 요청이 들어왔을 때 manager는 그 요구들이 적합한지 검사를 수행해야한다.

최대 컨테이너 수를 넘는지 확인하는 검사의 위치를 적절한 곳으로 이동시키기

  • 진행과정 & 배운점

세션이 큐에서 대기하다가 다른 세션 종료 후에 실행 가능 여부가 바뀔 수 있는 조건은 predicates에서 검사를 한다. 몇 초에 한 번씩 검사를 다시 수행하여 실행이 가능해졌는지 확인한다. 하지만 다른 세션의 종료 여부와 상관없이 정책 옵션만으로 실행 가능 여부가 결정된다면, 굳이 큐에서 대기할 필요가 없기 때문에 큐에 넣기 전에 정책 검증을 실행하는 게 좋다.

"사용자가 요청한 컨테이너 수가 최대 컨테이너 수를 넘는지"에 대한 검사 결과는 후자에 해당한다. 따라서 큐에 넣기 전에 검사를 수행할 수 있도록 위치를 이동시켰다.

자원그룹 변경 기능 추가하기

PR: https://github.com/lablup/backend.ai-manager/pull/511

  • 이슈 내용

데이터베이스 agents 테이블의 자원그룹을 변경하는 기능을 추가하는 이슈였다.

  • 진행과정 & 배운점

GraphQL API를 구현할 수 있는 도구를 제공하는 라이브러리인 Graphene을 사용했다. 자원 그룹 변경 요청에도 응답할 수 있도록 기존의 ModifyAgent mutation에 코드를 추가했다. 하지만 한 가지 문제가 있었다. 3초정도에 한 번씩 자원 그룹의 설정 값을 toml파일에서 읽어와서 db를 업데이트하기 때문에, 아무리 위의 mutation으로 바꾼다고 하더라도 덮어씌워지게 된다. 따라서 Agent의 mutation에서 데이터베이스의 자원그룹 값을 변경하는 것 뿐만 아니라, agent.toml(설정파일)에서도 값도 변경해주어야했다.

→ agent 컴포넌트에 toml 파일의 자원 그룹 값을 변경하는 RPC method를 추가하고, manager에서 그 method를 호출하는 방법으로 진행했다. 이건 아래에서!

가장 마지막에 진행했던 이슈인데, 이 기능을 추가하기 위해서는 webui, manager, agent 컴포넌트를 모두 업데이트해야했다. webui에서는 변경 요청을 보낼 수 있도록 UI요소를 추가해야했고, manager와 agent에서 그 요청을 적절하게 수행할 수 있도록 구현해야했다. webui는 작업하지 못했지만, manager와 agent컴포넌트는 완료하고 PR을 올려놓은 상태이다.(PR이 merge되면 webui도 작업을 해보려한다.)

Agent

agent 컨테이너들을 관리하는 컴포넌트인데, 작업할 일이 거의 없었다. 거의 끝나갈 무렵 위에서 언급한 대로 기능 추가를 위해 작업을 하게 되었다.

자원그룹 변경 기능 추가하기

PR: https://github.com/lablup/backend.ai-agent/pull/327

  • 이슈 내용

toml 설정 파일에서 자원그룹을 변경하는 RPC method를 추가해야했다. ModifyAgent mutation에서 db를 업데이트하기 전에 이 RPC method를 호출하여 toml의 자원그룹 설정 값을 변경할 것이다.

  • 진행과정 & 배운점

tomlkit 라이브러리를 처음 사용해봤다. 사람이 추가한 주석을 그대로 보존하면서 값을 수정할 수 있도록 도와준다. tomlkit에 대한 정보가 많이 없어서 좀 애를 먹었다. 파일 경로를 가지고 파일을 읽어온 후 수정하고 다시 저장까지 해야 하는데, 공식문서에는 String을 TOMLDocument 객체로 만들어주는 parse에 관련된 설명만 있었다. 그런데, parse를 사용하다가 에러가 나서 파일들을 타고 들어가다 보니 tomlkit의 api.py파일을 발견하게 되었다. 그 파일에 정의된 함수들을 보며, 사용법을 익혔다.

그래서 최종적으로 아래와 같이 RPC method를 추가했다.

    async def update_scaling_group(self, scaling_group):
cfg_src_path = config.find_config_file('agent')
with open(cfg_src_path, 'r') as f:
data = tomlkit.load(f)
data['agent']['scaling-group'] = scaling_group
with open(cfg_src_path, 'w') as f:
tomlkit.dump(data, f)
self.local_config['agent']['scaling-group'] = scaling_group
log.info('rpc::update_scaling_group()')

파일을 읽기 권한으로 열고, load하여 data라는 TOMLDocument 객체를 만들었다. 파일을 쓰기 모드로 열어 수정한 data를 저장했다.

읽기('r')와 쓰기('w') 권한을 나누고 싶지는 않았다. 하지만 'r+' 권한으로 시도하면 수정된 데이터로 덮어씌워지는 게 아니라, 원본 아래에 추가가 돼서 내용이 반복됐기 때문에 이 방법밖에 없었다. 이 이슈를 진행하면서 tomlkit에 대해 많이 알게 되었다. 정보가 많이 없어서 이 부분에 대해서는 따로 포스팅을 올리면 좋을 것 같다.

Common

TimeDuration Class 업데이트하기

PR(Merged): https://github.com/lablup/backend.ai-common/pull/99

  • 이슈 내용

manager의 명령어 추가하기 PR과 관련된 이슈이다. TimeDuration Class는 두 datetime 객체 간의 차이를 반환한다. mgr clear-history 를 위한 함수인 clear_history() 에서는 TimeDuration을 이용하여 expiratation_date를 계산하고 그 날짜 이전의 데이터들을 삭제한다. 그런데 TimeDuration에서는 years와 months 파라미터가 없다. 그래서 처음에는 아래와 같이 작성했다.

        unit = retention[-2:]
today = datetime.now()
if unit == 'yr':
yr = int(retention[:-2])
expiration_date = today - relativedelta(years=yr)
elif unit == 'mo':
mo = int(retention[:-2])
expiration_date = today - relativedelta(months=mo)
else:
duration = TimeDuration()
expiration_date = today - duration.check_and_return(retention)

TimeDuration을 months와 years를 지원한다면 아래와 같이 간결하게 코드를 찔 수 있다.

        today = datetime.now()
duration = TimeDuration()
expiration = today - duration.check_and_return(retention)
expiration_date = expiration.strftime('%Y-%m-%d %H:%M:%S')

→ months와 years를 지원할 수 있도록 TimeDuration 업데이트하기

  • 진행과정 & 배운점

timedelta에는 years와 months argument가 없었다. 그래서 dateutil.relativedelta.relativedelta를 사용해야했다. datautil은 datetime 모듈의 확장 패키지이다.

처음에는 return type을 통일하려 했는데, relativedelta는 실수 타입의 인자를 지원하지 않아서 Github Action test를 통과하지 못했다. 그래서 years와 months에만 relativedelta를 이용했다. 그리고 테스트 코드를 처음 작성해봤다.

    date = datetime(2020, 2, 29)
...
assert iv.check('1yr') == relativedelta(years=1)
assert iv.check('1mo') == relativedelta(months=1)
assert date + iv.check('4yr') == date + relativedelta(years=4)

iv는 TimeDuration object이다. '1yr'과 '1mo'를 넣었을 때 원하는 값이 나오는지 확인하는 테스트 코드이다. 윤년일 때도 잘 계산이 되는지 테스트한다.

Webui

약관/개인정보보호 창 종료 후 다시 열리지 않는 문제 해결하기

PR(Merged): https://github.com/lablup/backend.ai-webui/pull/1160

  • 이슈 내용

아래와 같이 약관 또는 개인정보보호방침 창의 x 버튼을 눌러서 닫은 후, 버튼을 누르면 다시 열리지 않았다.

창을 닫기 위해 x버튼과 dismiss 버튼 두 개가 있는데, dismiss를 눌러서 닫은 경우에는 다시 창이 열렸다. 하지만 x버튼의 경우 그렇지 않았다.

→ x버튼을 눌러서 창을 닫더라도 다시 열리도록 수정하기

  • 진행과정 & 배운점

Backend.AI webui에서는 웹 컴포넌트를 위한 라이브러리인 lit을 사용한다.

약관/개인정보보호방침 창은 lablup-terms-of-service 안에 backend-ai-dialog 컴포넌트가 있는 구조였다. dismiss 버튼은 lablup-terms-of-service 컴포넌트 안에 존재하고, x 버튼은 backend-ai-dialog 안에 존재한다.

→ 조건1. x 버튼을 눌렀을 때는 두 개의 컴포넌트가 모두 닫히도록 동작해야 한다.

창은 show 변수가 false일 때만 열린다.

→ 조건2. 창이 닫힐 때 show 변수가 false로 변경되어야 한다.

backend-ai-dialog를 닫을 때 "dialog-closed" customEvent를 발생시켰다. 그리고 그에 대한 EventListener를 lablup-terms-of-service 컴포넌트에 추가했다. 그리고 콜백함수에 위의 두 가지 조건들을 만족시킬 수 있도록 코드를 추가했다.

테이블 안의 아이템이 겹치는 문제 해결하기

PR(Merged): https://github.com/lablup/backend.ai-webui/pull/1170

  • 이슈 내용

session의 GPU값이 세 자릿수 이상일 때, 아래의 이미지처럼 오른쪽의 mount folder 아이템과 겹치는 문제가 발생했다.

  • 진행과정 & 배운점

처음에는 GPU 아이템에 따라 너비가 늘어나게 해서 해결할 생각이었다. 하지만 생각처럼 되지 않는 부분이 있어 조언을 구했다. 문제 해결 방법에 대해 같이 고민해주셨다. 그 대화를 통해 방향성을 잡을 수 있었다. 원래는 빨리 해결하는 것에 집중하다 보니 해결 방법이 생각나면 다른 고민 없이 그 방법으로만 해결하려 했다. 하지만 이 이슈를 진행하면서 사용자의 입장에서 바라보며 더 좋은 방법이 있을지 고민하는 시간을 가지는 게 중요하다는 것을 깨달았다.

결과적으로 아래와 같이 아이템의 배치를 변경하여 문제를 해결하였다. 관련성이 있는 요소들을 묶어서 배치했다.

기업 문화

한 마디로 정리하면, 매우 자유롭고 수평적이었다. 모두 oo님으로 부른다.(대표님도!)

  • 업무 분담이 엄격하지 않다

무조건 프론트엔드 또는 백엔드만 하는 것은 아니다. 다양한 업무를 경험할 수 있어서 그 점이 가장 좋았다. 장점이자 단점일 것 같은데, 나에게는 장점으로 다가왔다.

  • 자유로운 출퇴근 시간 + 재택 근무

일주일에 2번 사무실 근무가 권장되었으나, 코로나 상황에 따라 달라지는 것 같다. 출근 시간은 자유다. 퇴근 시간도 자유다. 하지만 그만큼 다들 일을 열심히 하신다.

  • 공식 언어는 영어

외국인 직원분들이 있다. 전체 회의, Github Issue & PR 에는 영어가 사용된다. 회사 채널에서는 질문 또는 대화를 하는 사람이 모두 한국인이면 한국어로 작성해도 되지만, 그게 아니라면 영어로 작성한다.

  • 점심 제공, 헬스비 지급, 휴가는 자유롭게, 법정 연차 휴가는 다 쓰자

기본적으로 사무실에서 근무를 하면 점심이 제공되고, 같이 먹는다. 물론 점심 약속이 있는 경우, 따로 먹어도 된다. 헬스비는 일부 지원이 되는데, 이건 안써서 잘 모르겠다. 휴가에 대해서는 정말 자유로운 분위기였다. 미리 말하기만 하면 된다. 개인별로 장비가 지급된다. 인턴십 기간동안 맥북과 마우스, 키보드를 받아서 들고 다녔다.

  • 모르는 게 있을 땐 질문!! 다들 친절하게 알려주신다

인턴십 기간 동안 제일 어려웠던 게 질문하는 것이었다. 기본적인 것도 모른다고 생각하실까봐 걱정돼서 구글링을 열심히 하고, 혼자 탐구하는 시간을 많이 가졌다. 시간이 지날수록 질문의 중요성을 깨달았다. 아무도 뭐라고 하지 않는다. 다들 잘 알려주셔서 질문할 때마다 많은 것을 배웠다. 그리고 에러때문에 앓고 있으면, 도와주기도 하셨다.

인턴십 기간이 두 달이라 기업 문화를 모두 이해하기에 짧은 시간이지만, 그 기간 동안 느꼈던 회사의 문화를 정리해보았다. 스타트업에 취업하고 싶다는 생각은 한 번도 못했는데, 래블업에서 인턴으로 일하면서 생각이 많이 바뀌었다. 다들 좋은 회사 문화를 만들기 위해 항상 노력하셨다.

후기

인턴을 시작할 때에는 머릿속에 걱정이 대부분이었다. 하지만 이슈를 해결하면서 조금씩 이해되기 시작하니 점점 즐거워졌다. 지원할 때 동기를 물어보는 질문에 좋은 분들과 함께 열정적으로 일해보고 싶다고 적었던 것 같은데, 기대했던 대로 좋았다. 그동안은 프론트엔드 개발만 했는데, 백엔드를 경험할 수 있었다. 생각보다 훨씬 흥미로웠기 때문에, 앞으로 백엔드를 더 공부해볼 계획이다.

확실히 실무는 달랐다. Github에서 code와 pull request tab만 썼었는데, Action을 처음 사용해봤다. 프로젝트가 어떻게 관리되는지 배울 수 있었다. git 명령어도 조금 더 다양하게 쓰게 되었다. 이곳에서 일하면서 시야가 많이 넓어졌다. 배운 것들이 앞으로 도움이 많이 될 것이라고 생각한다.

두 달이라는 시간이 짧게 느껴질 정도로 즐겁고 유익한 시간이었다. 마무리해야하는 게 아쉽지만, 나중에 좋은 모습으로 다시 뵐 수 있도록 열심히 공부하려 한다.