티스토리 뷰

회고

백엔드 혼자 맡게 된 이야기

onaeonae1 2026. 4. 4. 16:38

회사 사정으로 인해 기존에 3인이었던 백엔드 개발팀이 1인으로 줄게 되었다.
따라서 혼자서 기존 서비스 운영 + 신규 개발을 뛰어야 하는 상황인데
인원 감축 전/후로 이에 대응한 부분을 작성한다.

감축 전

참고로 나는 인원 감축에 대해 사전에 어느정도 알고 있어서 대략 4주 정도는 준비할 시간이 있었다.

 

우선 인원이 줄어들어도 할 건 해야한다.
다시 말해 운영 + 기능 개발 + 구조 개선이 인원이 줄어들었음에도 정상적으로 돌아갈 수 있도록 사전 작업이 필요했다.
현 회사에 합류한지 대략 1년 정도 지났는데 그동안 기능 개발에 대해서는 많이 파악해둬서 큰 문제가 없었다.
그런데 운영, 구조 개선 쪽 업무는 아직 미흡한 부분이 좀 있어서 이 부분을 개선하는 데 집중했다.

운영

회사의 서비스는 다양한 매출/마케팅 채널로부터 데이터를 수집하여 그것으로부터 분석된 데이터를 고객사에 제공하는 Batchjob 이 핵심 기능이다.

따라서, 이 경우 문제가 될 수 있는 지점은 상당히 다양한데 요약하자면 다음과 같다.

  1. 데이터 수집
  2. 데이터 적재
  3. 데이터 변환
  4. 데이터 분석

이들에 대해 다음과 같은 방식을 통해 운영 난이도를 낮추는데 집중했다.

  1. 대부분의 단계에 멱등성을 두도록 처리
  2. 모든 중간 결과를 임시로 저장하도록 처리 -> CSV + Parquet + log
  3. 발생할 수 있는 오류들을 카테고리화 + 모니터링 알림 시 이를 사용하도록 처리
  4. 백오피스(django-admin)에서 쉽게 모니터링하고 처리할 수 있도록 함
  5. 디버깅 하는 방법들을 문서화 (claude.md)

3번에 대해 좀 더 구체적으로 설명하면, 기존에는 에러가 발생해도 "어딘가에서 에러 남" 이 정도만 알 수 있었다.

Before — 에러가 뭉뚱그려 올라오던 상태

수집 배치잡 모니터 결과

데이터 수집 오류 발생 (12건)
- channel_1 (3건): A사, B사, C사
- channel_2 (2건): D사, E사
...

에러의 원인이 인증 문제인지, 타임아웃인지, 데이터 자체 문제인지 구분이 안 됐다.
3명일 때는 "아 그건 무시해도 돼"가 통했지만, 혼자 보려면 불가능하다.

After — 에러 카테고리화

class ConnectionErrorCategory(models.TextChoices):
    AUTH = "auth", "인증 오류"
    TIMEOUT = "timeout", "타임아웃"
    DATA = "data", "데이터 오류"
    EXECUTION = "execution", "실행 오류"
    UNKNOWN = "unknown", "알 수 없는 오류"

이걸 기반으로 Slack 리포트도 카테고리 × 데이터소스로 모으고, 신규/기존(N일째 미조치)을 구분하게 바꿨다.

수집 배치잡 모니터 결과

데이터 수집 오류 발생 (신규 3건 / 기존 9건)

[신규]
- channel_1 | 인증 오류 (2건): A사, B사
- channel_2 | 타임아웃 (1건): C사

[기존 — 미조치]
- channel_1 | 인증 오류 (5건): D사(3일째), E사(7일째), ...
- channel_3 | 데이터 오류 (4건): F사(2일째), ...

이제 리포트만 보면 "이건 지금 봐야 하는 건지, 기다려도 되는 건지" 를 즉시 판단할 수 있다.
여기에 admin에서 버튼 한번으로 멱등성 있는 재처리를 걸 수 있게 해두니, 처음 보는 에러가 생기더라도 수집된 로그로부터 파악 → 재처리까지의 루프가 빨라졌다.

개발

앞서 언급했듯 코드에만 정의된 불문율 같은 것들을 정리하는데 집중했다.
이러한 로직들을 방치하면 반드시 생산성을 떨구게 되는데 기존의 상황을 간략하게 정리하면 다음과 같다.

  1. 어떤 자원(데이터소스 커넥터)을 생성하려면 기획서에 정의된 데이터대로 주고받아야함.
  2. 이에 대해 수정 가능 여부 + 타입 정의 + 처리 방식 같은게 전부 jsonb type 으로 코드에서 다 처리하고 있음
  3. 이러한 자원이 아까 말한 batchjob 문제와 엮여 있음.
  4. FE/BE 둘다 코드로 이걸 관리하고 있으므로 수정하려면 코드 배포 해야함

나는 이에 대해 Spec 이라는 것을 추가했다.

Before ->  커넥터마다 규칙이 코드에 흩어져 있던 상태

def validate_connection_info(datasource, *, auth_data, action_type, **kwargs):
    if action_type == "connect":
        if not auth_data.get("account_id"):
            raise Error("account_id는 필수입니다")
    elif action_type == "update":
        # account_id는 수정 불가... 였나? 코드 봐야 앎
        auth_data.pop("account_id", None)
    # 이런 분기가 커넥터 50개에 각각 다르게 존재

After  -> 선언적 값으로 관리

always = {"connect": True, "update": True, "reconnect": True}
connect_only = {"connect": True, "update": False, "reconnect": False}
never = {"connect": False, "update": False, "reconnect": False}

spec = [
    DatasourceSpec(
        name="account_id",
        label="계정 ID",
        type="text",
        visible_at=always,
        required_at=connect_only,
        writable_at=connect_only,  # connect 때만 입력 가능, 이후 수정 불가
        is_identifier=True,
    ),
]
# 이 값만 보면 FE/BE 누구든 동작을 즉시 파악 가능

간단히 정리하면:

  1. 자원을 생성하기 위해 보내야 하는 데이터의 명세를 BE에서 무조건 관리
  2. 생성/수정/재생성 상황에 대해 데이터의 특정 필드가 언제 쓰이는지 + 필드 자체에 대한 정보등을 다 DB 에 저장
  3. BE에서는 DB 에 적힌 명세로부터 자원 관련 action의 validness 판단을 하도록 공통 코드화 (기존에는 자원 종류별로 제각각)
  4. FE도 명세로부터 자원 관련 action 시의 화면을 템플릿화함

이렇게 해두니깐 FE/BE 둘다 불필요한 시간 소모를 줄이고 빠르게 개발할 수 있었다.

이후에는 문서화 + 테스트 작성에 집중했는데, 문서화가 그렇게 거창하게 한 것은 아니고 다음의 목표로 처리했다.

  1. context 가 아예 없는 상태에서 claude.md 만 보고 바로 작업할 수 있도록 claude.md 를 작성 -> 불문율이 없게
  2. context 가 최대한 fit 하게 처리되도록 claude.md 를 도메인 단위로 계층화 -> 토큰 낭비+context 오염 없이 개발되게
  3. 기능 개발하느라 밀린 테스트 케이스들 싹 다 추가 -> agent 한테 개발 시킬 때 신뢰할 수 있게

요약하자면 claude code 한테 일 시키기 좋은 구조로 바꿨다는 뜻이다. (신규 입사자가 오더라도 마찬가지)

감축 후

일단 당장 실제 감축이 일어난 지 일주일이 되었는데, 생각보다 할만하다.

운영이나 기능 개발 모두 크게 어렵지 않게 대응할 수 있었다. 당장 감축된 후 바로 Batchjob 전반이 멈추는 처음 보는 장애가 발생한 적이 있었는데, 앞에서 운영 편하게 해둔 조치로 인해 생각보다 쉽고 빠르게 복구할 수 있었다.

그럼에도 불구하고 감축 후 바로 부족함을 느끼고 추가한 부분들이 있다.

혼자 돌리는 개발 루프

3명일 때의 개발 루프는 이렇다:

  • 개발 → PR → 다른 사람이 리뷰 → 머지 → 배포

혼자가 되면 리뷰어가 없다. 그렇다고 리뷰 없이 머지하면 품질이 떨어진다.

AI 코드 리뷰

GitHub Actions에 Claude Code를 연결해서 PR마다 자동 리뷰를 달게 했다.
리뷰 관점을 마크다운으로 정의해두면 그 기준으로 체크해준다.

## 리뷰 관점

### 반드시 확인
- 버그 가능성 (엣지 케이스, off-by-one, None 처리 등)
- N+1 쿼리 또는 비효율적인 DB 접근
- 타입 힌트 누락 (파라미터, 리턴 타입 모두)
- DI 순환참조 위험
- Celery task에서 서비스 import가 local import인지
- 보안 이슈 (SQL injection, 인증/권한 누락 등)
- 멀티테넌트 격리 (다른 client 데이터 접근 가능성)

### 하지 말 것
- 코드 스타일 지적 (ruff/black이 처리함)
- 사소한 네이밍 제안
- 이미 동작하는 코드에 대한 리팩토링 제안
- 칭찬이나 인사말

핵심은 "하지 말 것"을 명시한 것이다. 이게 없으면 AI 리뷰가 그냥 노이즈로 차버린다.
사람 리뷰를 완전히 대체하진 못하지만, "이상한 걸 올려놓고 모르고 넘어가는" 상황은 줄어들었다.
그리고 리뷰 없이 개발하는 것보다 훨씬 반응성이 있어서 재밌기도 하다

빌드 캐시

혼자 개발-리뷰-배포를 돌려야 하니 루프 자체가 빨라야 한다.
GitHub Actions 빌드 시 Docker layer 캐시를 적용했다. (기존에 안되어 있었음..)

- name: Build, tag, and push image to Amazon ECR
  uses: docker/build-push-action@v6
  with:
    context: ***
    push: true
    cache-from: type=gha
    cache-to: type=gha,mode=max

 

코드만 바뀌었으면 의존성 레이어는 캐시에서 가져오므로 빌드 시간이 크게 줄었다.

회고

생각보다 할만한데...?

 

개인적으로 이러한 변화는 기존에 진작에 처리되었어야 할 내용들이라고 생각한다.
그럼에도 왜 그게 유지되었냐면, 그냥 그렇게 굴려도 되었기 때문이다.

 

기능 개발이 바쁘다고 리팩토링 안하고, 최대한 편리하게 운영할 수 있는데 방치하다가 계속 손해보고
사람은 어느정도 경로 의존성을 갖기 때문에 진짜 외부에서 충격이 오는게 아니라면 잘 바뀌지 않는다.

그래서 인원 감축이 몸은 좀 힘들 수 있어도 내 성장에 있어서는 좋은 충격이 되었다고 생각한다.


원래 개발자라면 시간/비용 상 효율적인 문제해결을 해야하는데, 그 부분을 내가 놓치고 있었다.
다행히 이번 기회에 코드베이스의 운영/개발 비효율성을 고치는 재밌는 경험을 할 수 있었다.

 

물론 현재 개선한 부분에서도 다음과 같은 문제가 있어서 고쳐나가야 한다.

1. 아직 완전히 자동화된게 아님. -> 사람이 하기 편리해진 거지 진짜 빡센 자동화와는 거리가 멀다

2. 개발자가 다치거나 장기 부재하면 여전히 문제가 된다

3. 따라서 최대한 유지보수 비용 줄이고 자동화 가능하도록 방법을 찾을 필요가 있다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함