백엔드 개발자 면접 질문 정리

백엔드 개발자 면접 예상 질문 및 주요개념을 정리해보았습니다.


인덱스란 무엇인가?

인덱스는 말 그대로 책의 맨 처음 또는 맨 마지막에 있는 색인이라고 할 수 있다. 이 비유를 그대로 가져와서 인덱스를 살펴본다면 데이터는 책의 내용이고 데어티가 저장된 레코드의 주소는 인덱스 목록에 있는 페이지 번호가 될 것이다. DBMS도 데이터베이스 테이블의 모든 데이터를 검색해서 원하는 결과를 가져 오려면 시간이 오래 걸린다. 그래서 칼럼의 값과 해당 레코드가 저장된 주소를 키와 값의 쌍으로 인덱스를 만들어 두는 것이다.

DBMS의 인덱스는 항상 정렬된 상태를 유지하기 떄문에 원하는 값을 탐색하는데는 빠르지만 새로운 값을 추가하거나 삭제, 수정하는 경우에는 쿼리문 실행 속도가 느려진다. 결론적으로 DBMS에서 인덱스는 데이터의 저장 성능을 희생하고 그 대신 데이터의 읽기 속도를 높이는 기능이다. SELECT 쿼리 문장의 WHERE 조건절에 사용되는 컬럼이라고 전부 인덱스로 생성하면 데이터 저장 성능이 떨어지고 인덱스의 크기가 비대해져서 오히려 역효과만 불러올 수 있다.

Primary Index vs Secondary Index

클러스터란 여러 개를 하나로 묶는다는 의미로 주로 사용되는데, 클러스터드 인덱스도 크게 다르지 않다. 인덱스에서 클러스터드는 비슷한 것들을 묶어서 저장하는 형태로 구현되는데, 이는 주로 비슷한 값들을 동시에 조회하는 경우가 많다는 점에서 착안된 것이다. 여기서 비슷한 값들은 물리적으로 인접한 장소에 저장되어 있는 데이터들을 말한다.

클러스터드 인덱스는 테이블의 프라이머리 키에 대해서만 적용되는 내용이다. 즉 프라이머리 키 값이 비슷한 레코드끼리 묶어서 저장하는 것을 클러스터드 인덱스라고 표현한다. 클러스터드 인덱스에는 프라이머리 키 값에 의해 레코드의 저장 위치가 결정되며 프라이머리 키 값이 변경되면 그 레코드의 물리적인 저장 위치 또한 변경되어야 한다. 그렇기 때문에 프라이머리 키를 신중하게 결정하고 클러스터드 인덱스를 사용해야 한다.

클러스터드 인덱스는 테이블 당 한 개만 생성할 수 있다. 프라이머리 키에 대해서만 적용되기 떄문이다. 이에 반해 non 클러스터드 인덱스는 테이블 당 여러 개를 생성할 수 있다.

CI CD

CI/CD는 애플리케이션 개발 단계를 자동화하여 애플리케이션을 더욱 짧은 주기로 고객에게 제공하는 방법입니다. CI/CD의 기본 개념은 지속적인 통합, 지속적인 서비스 제공, 지속적인 배포입니다.

지속적인 통합이 제대로 구현되면 애플리케이션 코드의 새로운 변경 사항이 정기적으로 빌드 및 테스트를 거쳐 공유 리포지토리에 병합됩니다. 따라서 여러 명의 개발자가 동시에 애플리케이션 개발과 관련된 코드 작업을 할 경우 서로 충돌하는 문제를 이 방법으로 해결할 수 있습니다.

개발자가 애플리케이션에 적용한 변경 사항이 병합되면 이러한 변경 사항이 애플리케이션을 손상시키지 않도록 자동으로 애플리케이션을 구축하고 각기 다른 레벨의 자동화된 테스트(일반적으로 단위 테스트 및 통합 테스트) 실행을 통해 변경 사항이 애플리케이션에 제대로 적용되었는지를 확인합니다.

“CD”는 지속적인 서비스 제공(Continuous Delivery) 및/또는 지속적인 배포(Continuous Deployment)를 의미하며 이 두 용어는 상호 교환하여 사용됩니다.

지속적인 제공이란 개발자들이 애플리케이션에 적용한 변경 사항이 버그 테스트를 거쳐 리포지토리(예: GitHub 또는 컨테이너 레지스트리)에 자동으로 업로드되는 것을 뜻하며, 운영팀은 이 리포지토리에서 애플리케이션을 실시간 프로덕션 환경으로 배포할 수 있습니다.

지속적인 배포(또 다른 의미의 “CD”: Continuous Deployment)란 개발자의 변경 사항을 리포지토리에서 고객이 사용 가능한 프로덕션 환경까지 자동으로 릴리스하는 것을 의미합니다.

프로덕션 준비가 완료된 빌드를 코드 리포지토리에 자동으로 릴리스하는 지속적 제공의 확장된 형태인 지속적 배포는 애플리케이션을 프로덕션으로 릴리스하는 작업을 자동화합니다.

실제 사례에서 지속적 배포란 개발자가 애플리케이션에 변경 사항을 작성한 후 몇 분 이내에 클라우드 애플리케이션을 자동으로 실행할 수 있는 것을 의미합니다

Monolitic vs MSA

Monolithic Architecture

단일 서비스 개발 방식, 하나의 프로젝트로 구성되어 있으며 단일 패키지로 배포하는 아키텍쳐

소규모 프로젝트에 적합.

장점

  • 통합 시나리오 테스트가 수월
  • 배포가 간단하다

단점

  • 서비스가 커짐에 따라 빌드/테스트 시간이 오래 걸림
  • 개발 언어가 종속
  • 선택적으로 확장 불가, 서비스마다 관여하는 비중이 달라도 뭉쳐있기 때문에 전체 확장으로 진행
  • 하나의 서비스가 다른 모든 서비스에 영향을 준다

MicroService Architecture

하나의 큰 어플리케이션을 여러 개의 작은 어플리케이션으로 쪼개어서 배포하는 아키텍쳐, 독립적인 기능을 수행하는 작은 단위의 서비스로 나누어 개발

대형 프로젝트에 적합하고 트래픽을 많이 요구하는 곳에서 필요

장점

  • 서비스별 배포 가능
    • 배포 시에 전체 서비스 중단이 필요없고 CI/CD가 수월
  • 특정 서비스 확장 용이
  • 장애 발생시 부분적인 대처 가능, 부분적인 장애에 대해서 격리 및 대처가 수월

단점

  • 데이터가 여러 서비스에 분산되어 있어 한번에 조회하기 어렵다
  • 서비스 간 호출시 API를 이용하기 때문에 통신 비용과 letency(지연시간)이 증가
  • 서비스가 분리되어 있어 트랜잭션과 테스트가 복잡

GIL

Global Interpreter Lock, 파이썬 인터프리터가 한 스레드만 하나의 바이트코드를 실행시킬수 있도록 해주는 Lock

하나의 스레드에 모든 자원을 허락하고 그 후에는 Lock을 걸어 다른 스레드는 실행할 수 없게 막아버림

멀티쓰레드일 경우 thread context switch 에 따른 비용도 발생하기 때문에 싱글쓰레드보다 시간이 오래 걸리는 문제발생

python은 기본적으로 garbage collection과 reference counting 을 통해 할당된 메모리를 관리

파이썬의 모든 객체는 reference count, 해당 변수가 참조된 수를 저장하고 있음. 여기서 문제가 발생

멀티쓰레드인 경우 여러 쓰레드가 하나의 객체를 사용한다면 reference count를 관리하기 위해서 모든 객체에 대한 lock 이 필요할 것

이러한 비효율을 막기 위해서 python 에서 GIL을 사용하게 되었음. 하나의 Lock 을 통해서 모든 객체들에 대한 reference count 의 동기화 문제를 해결

한 프로세스 내에서, Python 인터프리터는 한 시점에 하나으 쓰레드에 의해서만 실행될 수 있다

뮤텍스 란 멀티 쓰레드 환경에서 여러 개의 쓰레드가 어떠한 공유 자원에 접근 가능할 때 그 공유 자원에 접근하기 위해 가지고 있어야 하는 일종의 열쇠

Python에서 모든 것은 객체이고, 객체는 모두 참조 횟수를 가진다. 따라서 GC의 올바른 동작을 보장하려면 결국 모든 객체에 대해 뮤텍스를 걸어줘야 한다는 말이 된다.

ORM 이슈

ORM Object-relational mapping 은 객체와 데이터베이스 시스템을 연결(맵핑) 해주는 라이브러리다.

왜 쓰지 말아야 하는지에 대해 논의하기전에, 먼저 ORM의 장점을 살펴보자.

  • 중복 코드 방지?
  • 다른 데이터베이스로 쉽게 교체 가능
  • 여러 테이블에 쉽게 쿼리를 날릴 수 있음
  • 인터페이스를 작성하는 시간을 아껴 비즈니스 로직에 집중할 수 있음

단점

  • 프로젝트의 복잡성이 크면 구현하는 난이도가 상승

N+1 problem

django ORM은 Lazy Loading 방식. ORM에서 명령을 실행할 때마다 데이터베이스에서 데이터를 가져오는 것이 아니라 모든 명령 처리가 끝나고 실제로 데이터를 불러와야 할 시점이 왔을 때 데이터베이스에 쿼리를 실행하는 방식.

N+1 problem 은 쿼리 1번으로 N건의 데이터를 가져왔는데 원하는 데이터를 얻기 위해 이 N건의 데이터를 데이터 수 만큼 반복해서 2차적으로 쿼리를 수행하는 문제

이 문제 해결방식으로 Eeger-Loading 방식이 있다.

사전에 쓸 데이터를 포함하여 쿼리를 날리기 떄문에 비효율적으로 늘어나는 쿼리 요청을 방지할수 있다.

select_related : fk, one-to-one 처럼 single - valued relationship 에서만 사용가능. SQL join을 사용하는 방법

prefetch_relatd: “ + many-to-many, many-to-one 등 모든 relationship에서 사용 가능 SQL where in 구문을 사용하는 방법

DAO vs DTO

DAO (Data Access Object)

데이터베이스의 data에 접근하기 위한 객체. 데이터베이스에 접근 하기 위한 로직 & 비즈니스 로직을 분리하기 위한 목적

데이터 접근을 목적으로 하는 객체. 커넥션은 하나만 두고 여러 사용자가 DAO의 인터페이스를 사용하여 필요한 자료에 접근하도록 하는 것이 DAO 개념

DB에 대한 CRUD 처리. 어플리케이션 호출을 데이터 저장 부분에 매핑함으로써 DAO는 데이터베이스의 세부 내용을 노출하지 않고 특정 데이터 조작 기능을 제공할 수 있게 함

DTO (Data Transfer Object)

계층 간 데이터 교환을 하기 위해 사용하는 객체, DTO는 로직을 가지지 않는 순수한 데이터 객체(getter, setter만 가진 클래스)

데이터가 포함된 객체를 한 시스템에서 다른 시스템으로 전달하는 작업을 처리하는 객체

Data에 접속하는 객체. 여기서 Data란 데이터베이스도 될 수 있고 파일도 될 수 있으며 메모리도 될수 있다

DTO를 이용하는 이유는 프로세스 간의 커뮤니케이션이 주로 개별 호출이 부담스러운 작업일 경우가 많은 원격 인터페이스(예: 웹 서비스)에 의해 이루어지기 떄문

대부분의 개별 호출이 클라이언트와 서버 간의 왕복 시간을 소모하기 때문에 호출 횟수를 줄이는 방법 중 하나는 몇 번의 호출에 의해 전송될 데이터를 모으는 DTO를 이용해서 한번만 호출하게 하는 것이기 때문.

유저가 입력한 데이터를 DB에 넣는 과정을 예시로 한다면

  • 유저가 자신의 브라우저에서 데이터를 입력하여 form에 있는 데이터를 DTO에 넣어서 전송
  • 해당 DTO를 받은 서버가 DAO를 이용하여 데이터베이스로 데이터를 집어넣는다

VO (Value Object)

값 오브젝트로써 값을 위해 사용. read-only 특징( 사용하는 도중에 변경 불가능하여 오직 읽기만 가능)을 가집니다

  • DTO와 유사하지만 DTO는 setter를 가지고 있어 값이 변할 수 있음

좋은 문서화에 대해

훌륭한 문서를 만들기 위해서 따라야 할 8가지 원칙

  • 명확하고친절한 문서를 작성해야 한다
    • 문서를 작성하면서 사용자가 어떤 것을 이미 알고 있을 거라고 가정하지 말아야 한다
  • 프로젝트의 모든 측면을 자세히 설명하는 포괄적인 문서를 작성해야 한다
    • 문서화되지 않은 기능이나 예외는 사용자에게 혼란으로 이어질 수 있으며 나중에 참여한 개발자는 필요한 답변을 찾기 위해 문서 대신 코드를 뒤지는데 많은 시간을 허비
  • 빠르게 살펴볼 수 있는 문서를 작성해야 한다
    • 명확한 제목, 기호 목록이나 링크를 사용하면 문서를 쉽게 읽도록 만들 수 있음
  • 소프트웨어 사용법에 대한 예제를 제공하는 문서를 작성해야 한다
  • 필요한 경우 같은 내용을 반복적으로 포함하는 문서를 작성해야 한다
    • 사용자는 모든 문서를 다 읽는 것이 아니며, 일부 정보는 여러 문서에 분산될 수 있다
  • 최신 상태로 유지할 수 있는 문서를 작성해야 한다
  • 누구나 기여하기 쉬운 문서를 작성해야 한다
    • 문서를 쉽게 작성하게 하는 가장 간단한 방법은 코드와 마찬가지로 소스 관리시스템에서 텍스트 자체를 관리하는 것
  • 쉽게 찾을 수 있는 문서를 작성해야 한다
    • 리드미(README) 파일을 최신의 상태로 유지하고 리드미 파일 앞부분에 사용자에게 필요한 문서의 목록을 링크로 제공해주기만 해도 검색 가능성을 간단하게 높일 수 있음

API 버전관리

Rest API에서 버전을 관리하는 방법은 4가지가 있다.

  • URL versioning

  • Query parameter versioning

  • Header versioning

  • MIME type versioning

URL과 Query parameter 를 이용하는 방법은 일반 브라우저에서 실행가능하나 MIME, Header 정보를 이용한 버전 관리는 일반 브라우저에서 사용할수 없다.

URL versioning:

버전 번호가 API 엔드포인트 URL에 포함

예를 들면, ‘https://example.com/api/v1/users’ 에서 v1 이 버전 번호가 된다

개발자가 다른 버전의 API를 구현 및 접근하기가 편하고 클라이언트는 쉽게 리소스를 캐시할 수 있다

그러나 URL 구조를 변경하기가 어렵고 URL이 길어지거나 통제하기 어려워진다

무엇보다 코드베이스에 매우 큰 공간이 필요해진다. 새로운 버전이 생성된다는 것은 API 전체를 새로 분기하는 것을 의미한다

Query parameter versioning:

‘https://example.com/api/users?version=v1’

앞선 방식과 유사하게 버전 번호가 API 엔드포인트에 포함되지만 version 이라는 query parameter에 버전을 명시하는 방법

API 버전을 쉽게 만들 수 있으며 최신 버저능로 기본 설정하기 쉽다.

API 버전으로 요청을 라우팅할 때 쿼리 파라미터를 사용하는 것이 더 어렵다

Header versioning:

postman에서 header에 X-API-VERSION을 추가하여 version을 별도로 요청 가능

개발자가 다른 버전의 API에 접근하려면 request header에 특정 버전 번호로 요청해야한다. 이 접근방식은 이전 방식보다 매우 유연하고 API 구조를 변경하지 않고도 사용가능하다.

다만 클라이언트는 모든 요청 헤더에 버전 번호를 포함해 요청해야 한다

ADR

ADR = Architecture Decision Records

아키텍처적인 결정을 왜 그렇게 내렸는지 코드 베이스안에 기록해 놓는 것.GitHub은 iOS/Android 모바일팀에서 이걸 적용하고 있으며, 왜 필요한지를 설명한 글

당신을 위한게 아니라, 미래의 당신을 위한 것

ADR은 내가 내린 결정에 대한 반성과정이 아니고, 지금부터 6~12개월후에 이 아키텍처를 결정했을때의 마인드셋을 기억하는데 도움이 됨.ADR은 결정이 내려지는 시점을 포착하여 회의/줌 미팅/Slack/코드에서 다뤄진 PoC 까지를 모두 포함.머리에 있는 이 컨텍스트를 말로 끄집어 내서 나중에 이 아키텍처를 다시 살펴볼 때 해당 컨텍스트를 다시 머리에 넣을수 있게 하는 것.

진짜 보너스는 누군가 몇달후에 왜 GitHubAPIClient 모듈이 이렇게 동작하는지 당신을 비난하면서 물어볼 때 나타남.30분 페어링해서 코드를 설명하는 것 보다, 이 ADR을 던져주고 그 모듈을 빌드하는 동안 내린 결정에 대해 설명할 수 있게 됨.

당신을 위한게 아니라, 당신의 동료를 위한 것

ADR은 한줄짜리 “이 기능은 피쳐-#3128의 구현입니다” 라는 설명보다 더 긴 내용을 적을 것.동료들이 왜 이 기능이 다른 방식이 아닌 이 방식으로 만들었는 지를 이해하는데 도움이 되는 좀 더 긴 설명 형식.(ADR 내에 “고려했던 대안들” , “장점과 단점” 등으로 표현 )

당신에게는 간단한 것도, 동료들에게는 복잡할 수 있음.시간을 좀 내서, 결정을 내릴 때 생각한 과정을 적어두면 팀원들이 당신의 머리 속에 들어올 기회를 주게 됨.ADR을 작성하면 “Decision Socialization(의사 결정의 사회화)”가 가능해짐.이렇게 하면, 개별적으로 결정을 내리는 대신 팀이 유지 관리에 대한 책임을 지는 결정을 내리게 함.

풀 리퀘스트를 올리기전에 ADR을 작성하면 이를 검토하는 팀으로 부터 더 좋은 PR리뷰를 받을수 있음.

당신을 위한게 아니라, 미래의 동료를 위한 것

ADR은 당신이 얼마나 똑똑한 지 보여 주거나, 사람들이 당신이 만든 아키텍쳐를 보고 어리둥절하게 하는 것이 아님.ADR은 새로운 팀원이 들어왔을 때 그들이 현재의 코드베이스와 코드베이스가 지난 시간동안 어떻게 발전해왔는 지를 이해하는데 도움이 됨.

팀이 확장되고 성장하면서 팀원간의 커뮤니케이션 경로는 늘어남.이렇게 내린 결정을 적어두면 팀이 성장하면서 새로 합류하는 사람들과도 의사소통 하는데 도움이 됨.

최상의 시나리오는, 당신의 팀원이 새 ADR을 작성해서 당신이 예전에 내렸던 결정을 대체하고, 미래엔 당신의 동료로부터 배울 수 있게 되는 것.

“더 많은 ADR을 작성하세요. 우리 팀이 커지고 코드베이스가 복잡해질 때마다 ADR은 미래의 우리와 현재 팀원과 미래의 팀원을 도울 수 있는 좋은 방법 입니다.”

참고자료

인덱스란 무엇인가 CICD MSA N+1 problem 개발문서화 ADR