백엔드 개발자를 꿈꾸다

  22 Oct 2019


백엔드 개발자

백엔드 개발자를 꿈꾸는 이유

백엔드 개발자 가 담당하는 일의 범위는 너무나도 넓다. 웹어플리케이션 개발, 데이터 분석 엔지니어링, 분산 파일 시스템, DBMS와 같은 제품 만들기 등이 있을 것이고, 웹어플리케이션 개발의 백엔드 개발자는 서버와 데이터베이스 관리 역시 할 것이다.
컴퓨터공학과에서 공부하고, 개발하면서 개발자가 명심해야 할 세 가지는 다음과 같다.

1. 안정적인 시스템 2. 효율적인 코드 3. 획기적인 성능

아무리 많은 이용자가 이용하더라도 안정적이고 에러없는 프로그램을 만드는 것, 이러한 프로그램을 빠르고 효율적으로 작성하는 것은 무엇보다 중요하다. 프론트엔드 개발자와 디자이너의 경우, 유저들에게 더 주목을 받고 눈에 뜨일지는 모르지만 이 역시 안정적인 시스템과 내면적인 부분의 완성도가 뒷받침되어야 빛을 발할 수 있습니다.
새로운 Framework를 계속 배워나가고, 적응하며 내 것을 만드는 것 역시 개발자가 지녀야할 숙명이다.
새로운 도구가 나왔을 때도 적응할 수 있는 학습력/적응력/판단력이 본질이고 이것이 누적이되면 실력이 된다
현재 Django, React, Node, Express, Android, IOS 등등의 프레임워크 및 플랫폼을 다양하게 이용해보았다. 다양하게 공부하였고, 조금 깊이있게 공부한 프레임워크도 있지만 상대적으로 가볍게 공부한 프레임워크 역시 존재한다. 아직 제대로 개발을 시작한지, 2년정도밖에 안되었으니 남은 기간동안 더 열심히 개발하면 될 것이라 생각한다. 그래도 이러한 경험을 기반으로 새로운 프레임워크나 플랫폼에 대한 두려움은 많이 극복하였다.

데이터베이스

사용자의 요청량과 저장 용량이 많은 서비스에서는 하나의 저장소만을 사용하진 않는다. 다양한 저장소가 쓰이는 시대에도 RDB(관계형 데이터베이스)는 여전히 가장 우선시되는 저장소이다. 그래서 RDB를 잘 다루는 능력은 백엔드 개발자의 핵심 역량 중 하나이다.
개발 도중에 쿼리의 호출 횟수, 실행 계획이 비효율적인지 아닌지 확인하는 습관이 필요하다. 실제로 실무 개발자들은 느린 쿼리를 모니터링하고, DBA와 협업하여 성능 개선을 하는 작업을 꾸준히 하고 있다고 한다. (DBA에게 다 넘기는 것이 아니다!)
ORM같은 추상화된 프레임워크를 써서 직접 SQL을 작성하지 않는 경우에도, 이러한 작업들은 매우 중요하다.
(ORM : Object Relational Model로 객체지향 프로그래밍 언어와 데이터베이스 간에 호환되지 않는 데이터를 변환하는 프로그래밍 기법이다. 이를 관계 객체 매핑이라고도 한다.)

대용량 서비스의 경우, DB 쿼리를 만드는 스타일이 과거와 많이 달라졌다고 한다. 과거에는 서버간의 네트워크 호출 비용을 줄이기 위해 굉장히 많은 테이블을 한번에 조인하는 긴 SQL을 만드는 경우가 많았으나 요즘은 복잡한 JOIN은 가급적 피하는 경향이 생겼다. 데이터를 조회하는 SQL이 단순할수록, 데이터를 다른 저장소에 캐시하거나 분산해서 저장하기 쉬워진다. 대용량을 저장하는 UGC서비스(User Generated Service - 유튜브 같은 서비스)에서는 RDB 테이블 사이의 JOIN은 최대한 제약을 하고, 어플리케이션 레벨에서 여러 저장소의 연관된 데이터를 조합하기도 한다.
👉 분산 데이터 시스템이 나오면서 경향이 바뀐 것이 아닐까 추측해본다.

개발툴

위에서 언급했듯이

새로운 도구가 나왔을 때도 적응할 수 있는 학습력/적응력/판단력이 본질이고 이것이 누적이되면 실력이 된다!
  • 레벨0: 이미 쓰고 있는 개발도구의 사용법을 알려주거나 가이드 문서를 줘도 잘 못 씀
  • 레벨1: 알려주거나 같은 팀에서 만든 가이드 문서에 있는 만큼만 쓸 수 있음
  • 레벨2 개발도구의 공식 레퍼런스를 보고 사용법을 스스로 익힐 수 있음 자신이 경험한 사용법을 문서화해서 팀 내에 전파할 수 있음
  • 레벨3 여러 개발도구를 비교 분석해서 상황에 적합한 도구를 선택할 수 있음 공식 레퍼런스 문서에서 부족한 부분을 수정해서 기여할 수 있음
  • 레벨4 개발도구의 문제를 소스 코드를 수정해서 Fork/패치해서 사용할 수 있음

현재 나는 레벨2 단계가 아닐까 싶다! 공식 레퍼런스를 보고 공부할 때 가장 중요한 점은 역시 영어실력인 것 같다.😂

병렬처리

방대한 데이터를 처리하기 위해 구글이 내놓은 맵리듀스 기법 과 그것을 바탕으로 만들어진 하둡 라이브버리는 많은 사람들에게 사랑을 받고, 일반인들도 쉽게 빅데이터를 처리하는 원동력이 되었다.
무어의 법칙 : 컴퓨터 반도체의 성능은 18개월마다 2배가 된다
이 법칙은 2000년대 초반까지는 얼추 맞아 떨어졌으나, 2000년대 중반에 접어들면서 점점 트랜지스터를 작게 만드는 것이 어려워져 한계에 도달하게 되었다. 이후 프로세서의 속도는 거의 빨라지지 않아서 엔지니어들은 칩 하나의 메모리를 공유하는 두 개의 프로세서를 집어넣는 듀얼코어 를 선보였다. 하지만 이 역시 속도가 2배가 되지 않고 거의 변화가 없었는데, 그 이유는 두 개의 코어가 동시에 처리할 수 있는 병렬 처리 시스템이 되어있지 않았기 때문이다
해야할 일은 한 가지인데, 이를 두 사람이 공유하지 못하는 상황이 벌어진 것이다. 당시에는 대부분의 프로그램이 싱글 코어용으로 제작되어 있어서 연산을 순서대로 하나씩 했었다. 그래서 코어가 둘이어도, 한 코어만 계속 일하고 있던 것이다. 비효율을 막기 위해서, 듀얼 코어를 이용해서 속도를 빠르게 하려면 최대한 동시에 처리할 수는 부분을 개발자가 적절히 지정해주어야 했다. 하지만 이 역시 순차적인 부분이 많다면 동시에 처리할 수 없을 것이다.
암달의 법칙 : 순차적으로 해야하는 부분이 많으면, 아무리 자원을 많이 투입해도 더 이상 일이 빨라질 수 없다.

병렬처리 설계

병렬처리 설계의 핵심은 연산 순서와 공유 자원을 정하는 것이다. 연산의 순서가 잘못되면, 나와야 하는 결과값과 전혀 다른 값이 나올 것이다. 공유 자원이 잘못 처리가 되면, 두 프로세서가 하나의 자원을 차지하려는 경쟁 상태가 되고 이럴 경우 데드락 이 발생할 수 있다.

데드락 : 멀티 프로그래밍 환경에서 한정된 자원을 사용하려고 서로 경쟁하는 상황 어떤 프로세스가 자원을 요청하였을 때, 해당 자원을 사용할 수 없는 상황이 생기고 대기 상태에 들어가게 되는데 이때 대기 상태에 들어가게 된 프로세스들이 실행상태로 변경될 수 없을 때 데드락이 발생할 수 있다.

  1. 상호 배제(Mutual Exclusion) : 자원은 한번에 한 프로세스만이 사용할 수 있다.
  2. 점유 대기(Hold and Wait) : 하나의 자원을 점유하고 있으면서, 다른 프로세스에 할당되어 있는 자원을 점유하기 위해 대기하는 프로세스가 있다.
  3. 비선점 (No preemption) : 다른 프로세스에 할당된 자원은 사용이 끝날 때까지 강제로 빼앗을 수 없어야 한다. [Monitor공부할 때 뺏는 경우가 있었음. 작업의 우선순위가 정해져있었기 때문에 대기 큐로 들어갔음.]
  4. 순환대기(Circular Wait) : 프로세스의 집합에서 P0는 P1이, P1은 p2가 점유한 자원을, PN은 P0가 점유한 자원을 요구한다.
이 네 가지 경우를 피한다면, 교착 상태 즉 데드락을 피할 수 있다.
  1. 상호 배제 👉 여러 개의 프로세스가 공유 자원을 사용할 수 있도록 한다.
  2. 점유 대기 👉 프로세스가 실행되기 전 필요한 모든 자원을 모두 할당한다. (대기할 필요가 없어짐)
  3. 비선점 👉 자원을 점유하고 있는 가 다른 자원을 요구할 때, 점유하고 있는 자원은 반납하고 대기한다.
  4. 순환대기 👉 자원에 고유한 번호를 할당하고, 번호 순서대로 자원을 요구하도록 한다.

구글의 획기적인 기술, 맵 리듀스

구글은 성능이 약하고 싼 데이터 서버(Node)를 아주 많이 연결하는 방법을 사용하였다. 이 방식은 간단한 계산과 처리를 약한 서버에서 하고 그 결과를 모아서 최종 결과를 계산하는 방식이다. 여러 대의 서버를 연결하였기 때문에, 데이터 전송에서 맣ㄴ은 시간을 잡아먹는다. 그렇기 때문에 각각 서버에서 최대한 많은 계산을 해서 결과를 작게 만들어서 전송해야 한다. 그런데 각 서버들은 성능이 좋지 않아 복핮ㅂ한 계산을 하기엔 어려운 딜레마가 있었다. 또한 복잡한 설계를 하였을 때, 각 서버가 제대로 동작하는지 하나하나 체크하기도 어렵고, 매번 변경점을 모든 서버에 적용하는 것 역시 어려웠다.
이러한 시스템 설계를 간단하고 강력하게 만든 시스템이 바로 구글의 맵 리듀스이다. 맵 리듀스는 복잡한 설계없이 맵과 리듀스 두가지 행동방식을 주면 결과를 계산하는 기술이다. 맵은 주로 데이터를 변환하거나 추출하는 방법이고, 리듀스는 추출해낸 데이터를 처리하는 방법을 말한다.
노드들은 맵만 처리하고(데이터를 변환하거나 추출하는 간단한 작업), 중간 서버가 리듀스를 하며 데이터양을 줄이고, 그것을 다시 맵을 하고, 이를 반복하며 쉽고 명확하게 병렬처리를 할 수 있다.

병렬처리의 도입

Servlet 기반의 Java 웹 서버들은 기본적으로 사용자의 요청을 병렬적으로 처리한다. 그래서 객체가 멀티스레드에서 공유되는 것인지 아닌지를 의식하는 일이 상당히 중요하다. 클래스의 멤버 변수에는 항상 멀티스레드에서 접근해도 안전한 Thread-safe한 변수만 두면 된다는 단순한 규칙만으로도 많은 문제를 예방할 수 있다. 그런데 Local cache를 적용할 때는 멀티스레드에서 공유된 객체가 쉽게 눈에 안띌 수도 있다. 그래서 Cache 대상의 객체는 Immutable하게 유지하는 것이 안전하다. (이 내용은 자세히 모르겠당..)

사용자에게 요청을 처리하는 스레드 외에도 별도의 스레드 풀로 실행해야 할 작업이 종종 생기기도 한대. 사용자에게 주는 응답은 영향을 주지 않지만, Update 구문을 DB에 실행하는 작업의 경우, Java에서는 Executors, ThreadPoolExecutor에 있는 많은 옵션들이 정교한 제어를 하는데 도움이 된다. new Thread()로 직접 스레드를 생성하는 방식은 JDK5이후로 권장하지 않는다.

테스트

많은 사람이 협업해서 개발하고 지속적으로 개선해나갈 소프트웨어라면, 테스트코드를 작성하는 일은 매우 중요하다.
테스트 코드를 작성하는 능력도 백엔드 개발자의 핵심 역량 중 하나이기도 하다. FE개발이 분업화되고, 서비스간의 API 호출이 많아지면서 최근 백엔드 개발자의 업무는 API 서버 개발에 더 집중되고 있다. 최종적으로 UI와 통합하기 전에 개발한 API를 스스로 테스트할 필요성도 매우 크다.
라이브러리를 배포 후, 이를 적용한 서비스에서 오류가 발생했을 경우 재배포하는 과정이 비용도 크고 신뢰를 잃게하는 요인이 되기 때문이다.

  • 유지보수 기간의 생산성을 높여주고 새로 프로젝트에 투입될 사람에게도 이득을 주는 테스트
  • 프로젝트 오픈 일정 직전까지의 코드 변경과 버그 발견에 도움을 주는 테스트
  • 오늘 당장 프로그램을 목표한 곳까지 작성하는 일들을 더 빨리 마치게 해주는 테스트

자료구조

JDK의 Collection Framework의 소스를 볼 때에도, 기본적인 자료구조에 대한 이해가 필요합니다. 대용량 데이터를 어떻게 저장하고 탐색할지를 결정할 때도 자료구조는 중요합니다. 이미지 처리처럼, 특화된 분야의 알고리즘이 실무에서 응용되는 경우(PHOLAR의 흔들림 보정 원리)가 그 예입니다.

이진 트리

부모보다 작은 값은 왼쪽에, 부모보다 큰 값은 오른쪽에 두는 규칙을 기반으로 한 자료구조로 검색속도가 빨라질 수 있다. 하지만 이진트리의 모양에 따라서 해당 자료구조의 효율이 결정된다. 이진트리가 만들어질 때, 한쪽으로 쏠리게 되는 형태가 나올 수 있다. 이런 경우, 배열에 비해 좋을 게 없다. 이진 검색의 시간복잡도는 O(logN)이다.최대한 트리를 완전 이진트리에 가깝게 하는 것이 좋다. 균형트리(Balanced Tree)를 유지하는 것이 중요한 문제가 되며 AVL 트리를 배우는 것은 자가 균형 트리를 유지하는 방법 중 하나를 알아보기 위함이다.

HEAP은 완전 이진트리이며 최댓값이나 최솟값을 빠르게 찾아내기 위한 자료구조로 활용된다. 노드를 추가하거나 삭제할 때마다 일정한 처리를 함으로써 루트 노드는 항상 최댓값이거나 최솟값이 되도록한다.
최소 신장 트리(최소 스패닝 트리-MST)는 그래프의 모든 정점을 포함하는 트리이다. N개 노드의 간선 수는 N-1이다. 최소 신장트리의 경우 간선의 가중치가 동일하지 않을 때, 엣지 가중치의 합이 최소인 신장트리를 말한다.
  • 배열 : 순차탐색이 용이하지만, 중간 삽입과 삭제가 느리다.
  • 링크드리스트 : 중간 삽입과 삭제가 용이하지만 임의 검색이 느리다. (B-tree에서도 단말 노드간의 연결을 위해 연결리스트가 사용된다)
  • 스택 : First In Last Out - DFS(깊이우선탐색)
  • 큐 : First In First Out - BFS(너비우선탐색)
  • 덱(Dequeue) : 앞 뒤로 삽입, 삭제가 가능하다.
  • 그래프 : 2차원 배열을 통해 인접행렬을 표현하기도 하고, 각 정점마다 연결된 정점을 리스트로 표현하는 인접 정점 리스트로 표현하기도 하고(나 같은 경우, 벡터를 이용한다), 변들을 리스트로 가지는 엣지 리스트로 표현하기도 한다.

Hash Table or Map

해시 테이블은 해시 함수로 인덱스를 결정하여 O(1)의 시간 복잡도로 원하는 데이터를 찾기 위해 고안된 자료구조이다. 어떤 값을 해시 함수로 거쳐 나온 결과 값을 인덱스로 하여 해당 인덱스로 바로 접근하는 아이디어이다. 해시 함수의 충돌을 처리하기 위해서는 Overflow chaning, Open Hashing 등의 방법을 사용한다. 정적 해싱방법은 충돌 처리가 실용적이지 않아서, 동적 해싱 방법을 사용하며 Exendible Hashing, Linear Hashing 등이 존재한다.

개발 프레임워크

JVM 생태계에선 Spring(동적 웹 개발)/Netty(비동기 프로토콜) 기반의 프레임워크들이 꾸준한 발전을 하고 있다.

  • Spring
    • 자바 객체를 직접 관리한다. 각각의 객체 생성, 소멸과 같은 라이프 사이클을 관리하며 스프링으로부터 필요한 객체를 얻어 올 수 있다.
    • 제어 반전(Inversion of Control) 을 지원한다. 컨트롤의 제어권이 사용자가 아니라 프레임워크에 있어서 필요에 따라 스프링에서 사용자의 코드를 호출한다.
    • 관점 지향 프로그래밍(AOP : Aspect-Oriented Programming) 을 지원한다. 트랙잭션이나 로깅, 보안과 같이 여러 모듈에서 공통적으로 사용하는 기능의 경우 해당 기능을 분리하여 관리할 수 있다.

JAVA 생태계에서 보통 웹 어플리케이션을 개발하는데까지는 비동기 방식의 개발이 확산되지는 못했다. 비동기 개발 자체가 가지는 진입장벽과 기술적인 난이도가 있고, RDB를 호출하는 어플리케이션이 대부분인데 비동기 JDBC 스펙의 구현체가 공식적으로 나오지 않은 탓도 있다. 모든 서버 어플리케이션이 비동기로 개발되어야 할 필요는 없지만, 트레픽이 많아서 동기/쓰레드 기반으로는 자원을 효율적으로 쓸 수없는 시스템에서는 택할만한 선택지이다.

백엔드 개발자를 꿈꾸는 학생개발자에게 : Naver_D2

...