Skip to content

기술면접 대비용 질문 정리 레포지토리입니다

Notifications You must be signed in to change notification settings

seyoung755/interview-question

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 

Repository files navigation

interview-question

기술면접 대비용 질문 정리 레포지토리입니다. 실제 면접에서 받았던 질문이나 대비를 하며 예상 질문을 작성해두었습니다. 혹시라도 틀린 내용이 있다면 알려주시면 정말 감사하겠습니다!🙇‍♂️

답변할 때 생각할 점

  • 가능한 짧고 간결하게 답변한다.

    • 너무 디테일하게 말하면 불리해질 수 있다. 핵심을 중심으로 간결하게 답변하고 꼬리질문에 대응하는 게 유리하다.
  • 모르는 내용은 솔직하게 모른다고 답변한다.


프로그래밍 전반

상속의 단점은 무엇일까요?

상속은 코드를 재사용하기 위해 널리 사용되는 방법이지만 가장 좋은 방법은 아닙니다. 두 가지 이유에서 좋지 못한데, 첫 째는 상속이 캡슐화를 위반한다는 점에 있습니다. 자식 클래스는 부모 클래스의 구조를 모두 알고 있어야만 코드를 재사용할 수 있게 됩니다. 그러므로 자식 클래스에서 부모 클래스의 구현 등을 세부적으로 알게 되어 캡슐화가 약화됩니다. 캡슐화가 약화된다는 말은 부모 클래스의 변경이 자식 클래스에게 그대로 전달됨을 의미한다.

둘 째는 설계가 유연하지 않다는 점이다. 상속은 부모 클래스와 자식 클래스 사이의 관계를 컴파일 시점에 결정한다. 따라서 런타임에 객체의 종류를 바꾸는 유연성을 발휘할 수 없다. 만약 할인 정책의 구현체를 정액 할인에서 정률 할인으로 변경하고자 할 때 상속을 이용한 설계에서는 인스턴스를 직접 변경해야 한다. 이미 생성된 객체의 클래스를 바꾸는 기능을 지원하지 않으므로, 원하는 타입의 새 인스턴스를 만들고 상태를 복사하는 번거로운 과정이 수반된다. 하지만 인스턴스 변수로 연결한 기존 방법을 사용하면 실행 시점에 간단한 setter 메소드를 통해서도 변경이 가능해진다.

운영체제

프로세스와 스레드의 차이에 대해서 설명해주세요.

프로세스는 메모리에 올라가서 실행되고 있는 프로그램입니다. 모든 프로세스는 code/data/heap/stack으로 이루어진 독자적인 주소공간을 가지고 있습니다. 반면 스레드는 프로세스 내에서 가지는 실행의 흐름입니다. 하나의 프로세스에서 여러 스레드를 통해 동시에 여러 작업을 처리할 수 있습니다. 스레드들은 프로세스의 스택을 제외한 주소 공간을 공유할 수 있습니다. 그렇기 때문에 공유 영역의 자원을 사용할 때는 동기화 문제를 염두에 두어야 합니다.

스레드에 대해서 설명해주세요

스레드는 프로세스 내에서 실행되는 흐름의 단위입니다. 프로세스의 주소 영역 중 code, data, heap을 공유합니다. 독자적인 실행을 위해 PC와 stack 등의 영역은 독자적으로 가집니다. 이런 특성 때문에 스레드 간 context switch는 프로세스의 경우보다 가볍습니다. 그렇지만 한 프로세스 내에서 메모리 영역을 공유하고 있으니, heap 영역의 객체를 사용할 때 동기화 문제가 발생할 수 있습니다. 때문에 싱글 스레드에서는 고려하지 않던 동기화 문제 해결을 위해 lock, semaphore, monitor 등의 방법을 고민해야합니다.

그리고 무조건 스레드를 만드는 것이 유리하냐? 라고 생각할 수 있습니다. 하지만 스레드를 만드는 것 또한 비용이 많이 드는 작업이므로 스레드를 무조건 많이 만든다고 해서 좋지 않습니다. 이론 상 코어의 개수만큼만 스레드가 동시에 작업이 가능하므로 코어의 개수에 맞게 스레드를 설계하는 것이 좋습니다. 이와 관련하여 하이퍼쓰레딩 기술은 한 코어당 두 개의 스레드를 작업할 수 있도록 하여 멀티태스킹 능력을 향상시켰습니다.

* 동기와 비동기, 블로킹과 논블로킹의 차이에 대해 설명해주세요.

먼저 동기와 비동기에 대해 설명해보겠습니다. 동기는 Synchronous이므로 시간을 맞춘다는 의미입니다. 메서드를 리턴하는 시간에 결과를 전달받는 시간과 일치하면 동기, 일치하지 않으면 비동기입니다. 리턴하는 시점과 결과를 전달받는 시점이 다르다는 것은 무엇일까요?

이 때 등장하는 개념이 Future입니다. 비동기적으로 실행한 함수의 결과는 일반적인 타입으로 받아올 수 없습니다. Java에서는 Future라는 형태로 비동기적으로 실행한 함수의 결과를 받아올 수 있습니다. 리턴하는 시점이 아닌, 그 결과를 필요해서 꺼낼 때 전달받으므로 비동기적으로 실행한다고 볼 수 있습니다.

이 때 비동기 작업의 결과가 출력될 때 직접 꺼내는 것이 아닌 콜백을 통해 어떤 작업을 실행하도록 할 수도 있습니다. 자바의 ListenableFuture 라는 인터페이스를 활용하여 콜백을 등록하거나, CompletableFuturethen~()를 사용할 수도 있습니다.

블로킹과 논블로킹은 함수 호출 후 제어권을 돌려받는 지 아닌지에 대한 영역입니다. https://www.youtube.com/watch?v=HKlUvCv9hvA

위 링크에 따르면, 내가 직접 제어할 수 없는 대상을 상대하는 방법입니다. I/O를 한다든지, 멀티 스레드 동기화에 해당합니다. 쉽게 말하면 어떤 작업을 시킨 다음 작업이 모두 끝날 때까지 기다렸다가 실행하는 방식입니다. 논블로킹 방식은 어떤 작업을 시킨 다음 곧장 빠져나와서 자신의 작업을 진행합니다.

즉, 다른 주체가 작업할 때 자신의 제어권이 있는지 없는지를 기준으로 나뉩니다.

한 마디로 요약하면 동기/비동기는 결과의 반환 시점, 블록/논블록은 제어권에 대한 기준입니다.

멀티스레드와 논블로킹 I/O

한 프로세스에 스레드가 3개 존재한다고 합시다. T1은 파일을 읽어오는 역할을 담당합니다. T1의 작업 소요 시간은 읽어오는 파일의 크기와 직결됩니다. 크기가 크다면, 이 파일을 일부 단위로 쪼개서 읽어와야할 수도 있습니다. T2는 읽어온 파일이 영상이라면, 영상 처리를 하는 작업을 합니다. 보통은 영상을 읽는 것보다 처리를 하는 것이 오래 걸립니다. 그러므로 영상 처리와 영상 읽기 작업을 별도의 스레드에서 처리하는 것이 좋습니다. 그 다음 처리한 영상을 전송하는 작업을 한다고 합니다. T3는 소켓을 열어서 통신하는 역할을 담당합니다.

이 경우에 T1 -> T2 -> T3의 작업은 순차적으로 이루어져야 합니다. 이 때 등장하는 개념이 동기화입니다. 이 작업이 모두 순서에 맞게 동기화가 되어야 정상적인 작업의 흐름대로 진행되기 때문입니다.

동기화를 위해서 먼저 Queue를 사용합니다. T1은 파일을 읽어서 Queue 1에 등록합니다. T2는 루프를 돌면서 Queue 1을 감시합니다. Queue 1에 처리할 데이터가 생기면, T2는 영상을 처리해서 Queue 2에 등록합니다. 이 때, T2처럼 Queue 2를 보고 있던 T3는 그제서야 소켓을 열어 해당 file을 전송합니다.

이 때 주의할 점은 T1, T2 간에, T2, T3 간에 동시에 Queue를 접근할 수 있습니다. 그렇기 때문에 항상 Queue에서 뭔가를 꺼내거나 삽입할 때에는 lock을 거는 형태로 관리하게 됩니다.

CPU 스케줄링 알고리즘에 대해서 소개해주세요.
가상 메모리 기법에 대해서 설명해주세요.
페이지 교체 알고리즘에 대해서 소개해주세요.
운영체제에 나오는 스케줄링을 실제로 사용한다면 좋을 것 같은 예시가 있을까요?

네트워크

UDP와 TCP의 차이점에 대해 설명해주세요

UDP는 비연결형 프로토콜로, 신뢰성 있는 통신을 지원하지 않습니다. 송신 측에서는 단지 데이터를 전송할 뿐 수신자가 잘 받았는지 확인하지 않습니다. (듣고있든 말든 갑자기 전화가 걸려와서 말하고 끊는다) 그렇기 때문에 성능에 큰 이점이 있습니다. 연결이 이루어지지 않기 때문에 1:1, 1:N, N:M 등으로 통신할 수 있습니다. 지원하는 기능이 없으므로 TCP에 비해 Header 크기가 작고, checksum 필드를 통해 오류 체크정도는 할 수 있습니다.

반면 TCP는 연결지향 프로토콜로 송신측과 수신측이 미리 연결된 상태에서 신뢰성 있는 통신을 하게 됩니다. (전화를 받으면, 안부를 묻고(3-way handshaking) 용건이 끝나면 인사를 하고(4-way handshaking) 대화를 종료한다.) TCP는 흐름 제어, 혼잡 제어, 순서 보장 등의 기능을 지원하여 송신 측의 데이터가 잘 전달될 수 있도록 보장해줍니다. 서버와 클라이언트는 1:1로 연결되는 전 이중 방식이며, 점대점 방식입니다.

브라우저에 www.google.com을 쳤을 때 일어나는 일

먼저 브라우저는 주소창을 통해 들어온 uri를 파싱하여 호스트를 알아내고 HTTP Request message를 만들어 OS에게 전송을 요청합니다. 이 때, 호스트의 이름을 통해 IP 주소를 알아내기 위해 DNS 서버에 질의합니다. (여기서 공유기를 쓴다면 공유기에 요청하고, 공유기가 DNS에 질의하는 경우도 있습니다. 또는 ISP가 제공해주는 DNS에 질의하게 됩니다.) 질의하기 전에 호스트 이름이 cache되었는지 브라우저나 운영체제 캐시를 확인합니다. 그리고 없다면 루트 네임서버부터 서브 도메인 순으로 찾게 됩니다.

이렇게 찾은 IP 주소를 통해 TCP 연결을 하고 소켓을 통해 요청 메세지를 전달합니다. (소켓 연결 시 http는 80포트, https는 443 포트로 연결합니다. 이 때, https는 tcp handshake 뒤에 TLS handshake를 진행합니다.) 서버는 요청 메세지를 받고 응답하는 HTTP 메세지를 소켓을 통해 클라이언트에게 전달합니다.

TLS handshake는 어떻게 일어나는가?

TLS handshake는 https 연결을 위해 TCP handshake 이후에 발생합니다. 먼저 클라이언트는 자신이 지원하는 암호화 제품군과 클라이언트 무작위 문자열을 서버에게 보냅니다.

서버는 공개 키와(포함하지 않을 수도 있음) 서버 인증서를 클라이언트에 전달합니다. 클라이언트는 인증서를 통해 서버가 내가 요청한 서버가 맞는지, 검증된 서버가 맞는지 확인합니다.

그리고 클라이언트는 예비 마스터 암호는 공개 키로 암호화되어 있고, 서버가 개인키로만 해독할 수 있습니다.

클라이언트와 서버는 클라이언트 무작위, 서버 무작위, 예비 마스터 암호를 통해 세션 키를 만듭니다. 이 세션 키를 통해 암호화된 완료 메시지를 전송하고, 클라이언트로 알고있는 정보로 복호화하여 핸드셰이크가 완료됩니다. 그리고 세션 키를 통한 대칭 키 방식으로 통신을 진행합니다.

이 때, 계속 공개 키 방식이 아닌 대칭키 방식을 쓰는 이유는, 공개 키 방식에 들어가는 오버헤드가 크기 때문입니다. 한 번 공개 키 방식으로 대칭 키를 교환한 이후에는 http 방식으로 메세지를 교환하게 됩니다.

CORS에 대해서 설명해주세요

CORS는 Cross-Origin Resource Sharing으로 서로 다른 도메인 간 자원 공유를 말합니다. 예를 들어 클라이언트 서버의 도메인 A에서 도메인 B인 WAS로 자원을 요청하여 받는 경우에 발생합니다. 브라우저는 요청한 도메인과 받는 도메인이 다른 경우에 안전하지 않은 응답이라고 판단하여 이를 버리게 됩니다.

이를 해결하려면, WAS에 응답을 보내줄 때 올바른 CORS 헤더를 추가하여 보내야 합니다. 응답을 받는 클라이언트의 도메인을 헤더에 추가해줘야 합니다.

비동기 이벤트 기반이란?

https://niklasjang.tistory.com/57 https://happyer16.tistory.com/entry/대용량-트래픽을-감당하기-위한-Spring-WebFlux-도입

일반적으로 Spring MVC는 synchoronous blocking I/O 기반의 구조로 실행된다. 이는 하나의 I/O 작업이 수행될 때 스레드가 계속 점유된 상태로 기다리다가 완료되어서야 응답이 이루어지는 구조이다. 즉, one-request-per-thread의 구조이다. 이는 유저가 많아졌을 때 스레드 개수의 한계 등으로 불리한 면이 있다.

Spring MVC

Spring MVC에서는 Tomcat을 통해 요청이 관리된다. 클라이언트가 요청하면 스레드 풀에 있는 스레드를 매칭하여 서비스를 하고, 현재 스레드 풀에 스레드가 모자라다면 Queue에서 대기하는 구조이다. 기본적으로 Tomcat은 200개의 Thread pool size를 가진다. 만약, 동시 요청의 수가 이 스레드풀의 사이즈보다 많아지기 시작하면 사용자의 지연시간이 치솟을 것이다. 이런 현상을 Thread pool hell이라 한다. 특정 작업이 지연을 일으키기 시작하면서 스레드 풀 반납이 늦어지고, 동시에 유저의 지연시간도 늘어나게 된다. 지연을 일으키는 대표적인 작업은 DB, Network 작업 등의 I/O 작업이다. blocking 방식에서는 I/O가 처리될 때까지 스레드를 잡아두고 있기 때문에 이런 작업이 빈번하게 요청되는 경우에 Thread pool hell이 발생하기 쉽다.

Spring WebFlux

Spring WebFlux는 이런 I/O로 인한 지연을 줄이기 위해 다른 구조를 제공한다. 하나의 요청 당 하나의 스레드가 생성하는 것이 아니라 다수의 요청을 적은 Thread로 처리한다. Worker Thread의 기본 사이즈는 서버의 Core 개수로 설정이 되어있다.

스레드는 적지만 Non-blocking 형태로 효율적으로 스레드를 관리하기 때문에 동시에 많은 사용자가 있을 때 성능을 향상시킬 수 있다. 물론 I/O 작업 중 하나라도 blocking 방식이 있다면, 결국 blocking이 발생하므로 말짱 도루묵이 될 수 있다. 예를 들어 DB Connection 방식이 blocking이라면, 그 DB I/O가 종료될 때까지 스레드 하나가 blocking 될 것이다. MongoDB, REDIS 등의 NoSQL은 non-blocking DB connection을 지원한다고 한다.

그럼 무조건 WebFlux?

그럼 무조건 효율적인 WebFlux를 사용하는 것이 좋아보인다. 하지만 트래픽이 작을 때에는 MVC와 WebFlux 모두 일정한 성능을 보인다. 다만 커넥션 풀 대비 많은 사용자가 발생하는 부분부터 WebFlux의 성능이 뛰어나다는 것이다. 그리고 WebFlux와 같은 비동기 방식은 디버깅이 매우 힘들다는 단점이 있다. 생산성이나 난이도 측면에서 동기 방식인 MVC가 유리하다는 점도 고려해야 한다.

* Netty는 무엇이고 왜 사용할까요?

https://velog.io/@sihyung92/how-does-springboot-handle-multiple-requests https://www.youtube.com/watch?v=pu2Y4nVWixo

Java의 전통적인 TCP 통신 방식은 Socket 라이브러리를 사용하는 방식이다. 소켓을 생성하고 포트에 바인딩한 뒤, while문을 돌면서 요청을 받아들이고 요청이 들어오면 stream을 취득합니다. 이런 식으로 구성했을 때 사용자가 많아지면 thread가 계속해서 늘어나게 되므로 장애의 원인이 되기 쉽습니다.

Netty의 개념 중 Channel, EventLoop, EventLoopGroup 등의 요소를 가집니다.

Channel은 I/O 작업을 수행하는 요소 또는 네트워크 연결입니다. EventLoop는 Channel의 I/O를 처리하고 수명주기 동안 한 Thread에 바인딩됩니다. Channel은 생명 주기동안 하나의 EventLoop에 바인딩되며 EventLoop는 여러 Channel을 할당받아 처리할 수 있습니다.

데이터베이스

인덱스는 무엇이고 왜 사용하나요?

인덱스는 관계형 데이터베이스에서 테이블을 효과적으로 저장하기 위한 자료구조입니다. 테이블은 disk에 저장되는데, disk는 random I/O에 매우 지연이 발생하는 구조입니다. DBMS도 데이터베이스 테이블의 데이터를 일일이 가져오려면 random access가 많이 발생하여 시간이 오래 걸린다. 인덱스의 목적은 random access를 줄여 데이터를 찾아오는 시간을 줄이는 데에 있습니다.

기본적으로 MySQL에서는 기본적으로 B+ Tree 형태로 인덱스를 만듭니다. 칼럼의 값과 해당 레코드가 저장된 주소를 key-value로 삼아 인덱스를 만든다.

장점으로는 목적에서 알 수 있듯이, SELECT 쿼리의 성능이 빨라집니다. 특히 범위 검색 연산을 수행하는 속도를 크게 향상시켜 줍니다. 단점으로는 자료구조를 추가로 만들기 때문에 INSERT, UPDATE, DELETE 발생 시 인덱스가 없을 때보다 시간이 추가로 들게 됩니다.

인덱스를 사용하면 좋은 경우는 언제일까요?

인덱스의 성능은 Selectivity에 좌우됩니다. 인덱스로 조회한 결과가 너무 많으면, 결국은 조회한 결과를 모두 random access하게 된다. 보통은 전체 레코드의 15~30% 이내로 조회될 때만 효과적으로 인덱스를 사용할 수 있다. 조회 결과가 그 이상 넘어갈 때는 MySQL Optimizer가 내부적으로 그냥 full scan을 진행한다. 그리고 인덱스를 실제로 잘 타고 있는지 쿼리 플랜을 활용하면 알 수 있다.

트랜잭션의 격리 수준에 대해 설명해주세요

트랜잭션의 격리 수준이란 서로 다른 트랜잭션 간 얼마나 떨어뜨려놓냐에 대한 레벨입니다.

가장 낮은 단계인 Read Uncommitted는 다른 트랜잭션에서 commit하지 않은 내용도 읽어올 수 있습니다. 이렇게 되면, commit되지 않고 rollback 된 내용이라고 읽어와서 사용하여 정합성이 깨어질 수 있습니다. 이런 현상을 dirty read라고 합니다.

다음 단계인 Read Committed는 다른 트랜잭션에서 commit한 내용만 읽어올 수 있는 격리 수준입니다. 이렇게 되면 dirty read 문제는 발생하지 않습니다. 하지만, 트랜잭션 A가 커밋하기 전에 읽어온 내용과 커밋 이후에 읽어온 내용이 달라지는 non-repeatble read 현상이 발생합니다.

다음 단계인 Repeatable read는 자신의 트랜잭션보다 이전에 시작한 트랜잭션의 내용만 읽어올 수 있습니다. 그렇기 때문에 한 트랜잭션 내에서 한번 읽어온 값은 계속 같은 값만 읽어갈 수 있도록 한다. UPDATE가 일어나면 UNDO 영역에 백업해두고 실제 레코드를 변경한다. 그리고 UNDO에 있는 레코드를 읽어옵니다. MySQL의 InnoDB에서는 이 UNDO와 레코드 단위 잠금을 통해 MVCC를 구현합니다. 하지만, INSERT/DELETE에 대해서는 정합성이 깨지게 되는데 이를 Phantom read라고 합니다.

마지막 단계인 Serializable은 모든 트랜잭션이 직렬적으로 실행되도록 하는 격리수준입니다. 한 마디로 한 트랜잭션이 테이블에 접근하고 있으면, 다른 트랜잭션은 접근하지 못하고 기다려야 합니다. 이 경우 모든 이상현상이 발생하지 않지만 성능에 큰 문제가 생겨서 보통 사용하지 않습니다.

ACID에 대해서 설명해주세요

ACID란 트랜잭션이 안전하게 수행되기 위한 성질입니다.

Atomicity는 트랜잭션이 원자적으로 실행되어야 한다는 성질입니다. 트랜잭션의 작업은 모두 성공하거나 모두 실패해야 합니다. Consistency는 트랜잭션이 테이블에 변경 사항을 적용할 때 미리 정의되거나 예측할 수 있는 방식만 취합니다. 트랜잭션 전후로 제약 조건을 모두 만족해야 합니다. Isolation은 모든 트랜잭션이 다른 트랜잭션으로부터 독립되어야 한다는 뜻이다. Durability는 트랜잭션의 결과가 로그로 남아서 영속적으로 기록된다는 성질이다. 도중에 오류가 발생하더라도 로그가 남아 장애를 복구할 수 있도록 해야 한다.

DB 커넥션 풀의 용도는 무엇일까요?

https://d2.naver.com/helloworld/5102792 https://www.youtube.com/watch?v=NMt9wgRsnjw

웹 서비스를 식당이라고 한다면, 주문을 하는 아르바이트생은 웹 서버에 해당한다. 그리고 주문을 주방장에 전달하게 되고 주방장은 WAS에 해당한다. 주방장은 식자재 창고에서 재료를 꺼내 주문에 해당하는 음식을 꺼내놓는다. 이 때 식자재 창고가 DB이다.

당연하게도 식당은 손님의 요청에 미리 아르바이트생도 준비해야 하고, 주방장도 준비해야 하고, 식자재도 준비해야 한다.

그렇기 때문에 미리 Web과 WAS 사이에, WAS와 DB 사이에는 미리 연결을 해놓는다.

이 때 WAS와 DB 사이에 미리 연결을 해놓는데, 연결의 주체는 Thread이다. 예를 들어 10개의 스레드를 미리 DB에 연결을 해놓는 것이다.

그리고 DB 연결 요청이 들어오면 여유있는 스레드를 가져다가 요청을 처리한다. 엔터프라이즈 어플리케이션의 대부분의 병목현상은 이 과정에서 발생한다.

이런 미리 연결을 해놓고 관리하는 형태를 커넥션 풀이라고 한다. 이 때 커넥션 풀의 유휴성과 평균 쿼리 실행 시간 등을 고려하면 TPS라는 지표로 정량화시킬 수 있다.

만약 DB 커넥션 풀의 처리량을 넘어서는 스레드는? maxWait 값 등을 통해 커넥션을 얻기 위해 대기 상태에 둘 수 있다.

DB 커넥션 풀의 종류에 대해서 아는 것이 있다면 말씀해주세요

Spring boot에서 기본적으로 사용되는 커넥션 풀은 HikariCP 입니다. 특정 스레드가 커넥션을 요청하면, HikariCP는 이전 사용했던 커넥션이나 유휴 커넥션 중 하나를 반환해준다. 만약 유휴 커넥션이 없다면, 다른 스레드가 커넥션을 반납하기를 기다린다. 그 동안 기다리는 커넥션은 HandOffQueue를 polling하게 되고, 반납하면 기다리던 스레드가 커넥션을 받아서 작업을 처리한다.

커넥션 풀의 크기는 사용하는 유저에 비례하여 적절하게 잡는 것이 좋은데, 600여명의 유저를 대응하는데에 15~20개의 커넥션 풀만으로도 충분하다고 한다.

자바

* 자바의 Concurrent 패키지는 어떻게 동기화를 하는가?

concurrent 패키지의 자료구조들은 모두 멀티스레드 환경에서 동기화가 되도록 구현되어 있습니다. 그럼 어떻게 동기화가 되는 것일까요? Java의 ConcurrentLinkedQueue에 보면 이 자료구조는 아래 링크에 따라 효율적인 논블로킹 알고리즘을 채용했다고 합니다. https://www.cs.rochester.edu/~scott/papers/1996_PODC_queues.pdf

대략적인 내용은 논블로킹이 되는 구조가 성능에 유리하다는 것이고, lock-free한 알고리즘인 CAS(Compare-And-Swap)이라는 알고리즘이 소개됩니다. Compare-And-Swap이란 주어진 값과 메모리에 있는 값이 동일하다면 값을 업데이트하고 그렇지 않으면 하지 않는 것입니다. 이는 synchronized 처럼 임계 영역에 도달하면 블로킹 시키는 것이 아니라, 모든 스레드를 논블로킹으로 접근할 수 있도록 하되, 작업 시점의 기준값과 메모리 상의 값을 비교하여 일치하면 작업을 수행하고, 일치하지 않으면 중간에 다른 스레드가 끼어들었다고 판단하여 재시도를 합니다. (AtomicIntegergetAndSetInt()를 보면 do-while 문을 통해, compareAndSet이 true일때까지 반복합니다.)

또한, 메모리 상의 값과 비교하기 위해 각 스레드가 캐시가 아닌 메모리에 직접 기록할 수 있도록 volatile 키워드를 사용합니다. 매 번 변경된 데이터는 메모리 상에 반영되므로 모든 스레드가 동일한 메모리 상의 값을 참조할 수 있도록 합니다.

일반적으로 lock을 사용하는 것보다 매우 빠르면서도 스레드 세이프하다는 장점이 있습니다. 그러나 compare-and-swap은 ABA 문제를 만날 수 있습니다. compare하는 순간에 old value와 이미 수정된 값이 같은 현상입니다. 이 때 별도의 카운터를 통해 값이 갱신될 때마다 수정하여, 값이 같더라도 카운터 값이 다르면 수정하지 않는 식으로 해결할 수 있다고 합니다.

자바에서 싱글턴 패턴을 구현해보세요.

스레드 세이프하게 싱글턴을 구현하는 방법

자바의 String은 불변객체라고 하는데, 이 개념에 대해서 설명해주세요
자바에는 참조형 변수가 있는데 왜 Call by reference가 없을까요?

Spring

* 스프링의 IoC, DI는 왜 할까?
결론적으로 OCP, DIP를 준수하여 좋은 객체지향 코드를 짜기 위함입니다. 객체지향에서는 객체간의 의존관계를 통해 하나의 기능을 수행하게 됩니다. 그런데 의존관계의 설정을 사용자 코드에서 하게 되면, 의존관계가 바뀔 때마다 코드를 수정해야 합니다. 이런 것이 OCP를 위반하는 코드이므로, 스프링에서는 IoC 컨테이너를 통해 빈을 등록해놓고 필요한 의존관계를 빈에 주입해주게 됩니다.

개발자가 객체를 생성하거나 의존관계를 설정하지 않고 스프링이 알아서 해주기 때문에 이것을 IoC라고 부르게 됩니다.

Web

세션과 쿠키는 무엇일까?

세션은 유저 정보를 저장하여 식별하기 위해 서버에서 유저마다 저장하는 정보입니다. 우리 서버에 어떤 유저가 로그인했다면, 세션 ID를 발급하여 서버쪽의 세션 저장소에 저장하여 관리하게 됩니다. 쿠키는 유저로 하여금 어떤 정보를 저장하도록 하는 것입니다. 서버가 응답 시 Header에 Set-Cookie 라는 속성을 통해 원하는 정보를 저장하도록 브라우저에 제안할 수 있습니다. 주로 세션과 쿠키는 로그인 처리를 위해 사용됩니다. 사용자는 로그인을 하여 세션 ID를 발급받고 서버는 이를 저장한 뒤, 쿠키를 통해 유저에게 전달합니다. 로그인된 사용자는 이후 요청부터 쿠키를 통해 자신의 세션 ID를 같이 전달합니다. 서버는 세션 저장소로부터 정보를 꺼내 진짜 이 사용자가 맞는지 Authentication(인증)한 후 요청을 처리하게 됩니다.

이렇게 세션과 쿠키를 사용하는 이유는 http가 무상태성(stateless)을 가지기 때문입니다. 그러기 위해서 로그인 처리 등을 하려면 세션과 쿠키의 도움을 받아야 합니다.

세션과 쿠키 방식의 단점과 대안

세션과 쿠키 방식으로 로그인을 구현하면 웹 어플리케이션 서버를 확장했을 때 로그인 처리에 고려할 점이 생깁니다. 보통 로그인을 하면 로그인을 한 서버 내부에 세션이 저장되게 됩니다. 그런데 WAS를 확장하여 로드 밸런서를 통해 부하를 적당히 여러 WAS에 분산하고 있다면, 어느 서버로 요청이 가게 될 지 알 수 없습니다. 즉, 로그인을 한 서버와 다음 요청을 하는 서버가 다르다면 세션 정보를 공유하지 않고 있기 때문에 로그인이 풀리는 문제가 발생합니다.

이를 보완하기 위해서는 여러 WAS가 공유하는 세션 DB 서버를 따로 두어야 합니다. 이 때 보통 key-value 기반의 메모리 DB인 REDIS가 많이 사용됩니다. 세션의 정보는 그리 크지 않아 메모리에 저장하기 용이하고 REDIS는 매우 빠른 속도를 자랑하기 때문입니다.

하지만 서버가 더 규모가 커진다면 이 방식도 부담이 됩니다. 계속 세션 정보를 동기화하기 위해 관리해야 하고, 세션 저장소의 크기도 계속 커지기 때문입니다. 또한 세션 서버가 장애가 났을 때를 대비하여 수평 확장을 고려해야 합니다.

그래서 나온 것이 토큰 기반의 인증입니다. 따로 서버 쪽에 유저 식별 정보를 저장하지 않고, 토큰 안에 유저 정보를 담는 것입니다. 그래서 어느 서버로 토큰이 가든지 간에 서버에서 검증하여 사용자를 식별하고 요청을 처리할 수 있습니다.

이 때 사용되는 대표적인 토큰이 JWT입니다.

JWT의 구현 방식과 장단점

JWT는 유저 정보를 해싱 알고리즘을 사용하여 암호화한 다음 토큰으로 만든 것입니다. 주로 HS256이나 RS256 알고리즘이 사용됩니다. (두 방식의 차이는 대칭키 방식이냐 공개키 방식이냐에 따라 다름) JWT는 header, payload, signiture의 세 부분으로 나뉩니다. header에는 토큰 타입과 사용한 암호화 알고리즘의 정보가 써있고, payload에는 사용자 정보가, signiture에는 비밀키로 암호화한 서명 정보가 써있습니다. header나 payload는 인코딩만 될 뿐 따로 암호화되지 않아 누구나 볼 수 있습니다. 하지만 signiture는 secret key로 암호화되있으므로 비밀 키를 알고 있는 사람만 복호화할 수 있습니다. 그렇기 때문에 서버에서는 이 signiture를 통해 올바른 토큰인지 확인할 수 있는 것이기 때문입니다. (통상적인 로그인 환경에서는 secret key는 서버만 알고 있고, 자기가 서명한 토큰을 발급하고 유저가 전달한 토큰이 자기가 서명한게 맞는지 확인한다.)

장점으로는 별도의 세션을 저장하지 않고 토큰 내에 유저 식별정보를 담아서 오기 때문에 확장에 편리해집니다. 단점으로는 payload에 그대로 유저 정보가 노출되기 때문에 민감한 정보는 담을 수 없고 한번 발급된 토큰은 만료될 때까지 계속 사용이 가능하다는 점입니다. 즉, 토큰이 탈취되면 무방비 상태에 빠집니다. 이를 방지하기 위해 토큰의 만료시간을 짧게 하고, refresh token으로 계속 토큰을 갱신하게끔 처리할 수 있습니다.

보통 refresh token은 access token 발급 시에 서버가 발급하여 db에 저장하고, 유저쪽에 전달합니다. 유저도 이 refresh token을 안전한 저장소에 저장합니다. 유저는 access token 만료 시에 refresh token을 제시하여 새로운 access token을 받게 됩니다.

이 때 물론 refresh token을 탈취해서 해커가 새로운 access token을 발급하면 어떡하느냐? 라는 의문이 생깁니다. 이는 서버 쪽에서 refresh를 할 때 사용자 요청이 올바른 요청인지 보안 절차를 거쳐야 합니다. 또한 https 통신을 사용하여 refresh token의 교환을 안전하게 처리할 필요가 있습니다.

그럼 왜 access token 접근 시에는 이런 보안 절차를 거치지 않느냐? 매 access token을 통한 요청마다 이런 보안 절차를 거치게 되면 병목현상이 발생하게 됩니다. 그렇기 때문에 간단한 token 검증 절차만 거치고 서버는 모든 요청을 들여보내게 됩니다. 즉, access token이 만료되기까지는 빈틈이 생기는 셈입니다.

JWT refresh token의 저장 위치

유저에게 refresh token을 발급하면, 유저는 이를 어디에 저장해야할 지 고민이 생깁니다.

첫 번째 선택지는 쿠키에 저장하는 방법입니다. 쿠키의 옵션을 통해 httpOnly, Secure 옵션을 통해서 브라우저에서 쿠키를 열어볼 수 없도록 하고 https 통신 위에서만 브라우저가 서버에 쿠키를 보내도록 할 수 있습니다. 이 방식으로는 xss 공격을 방어할 수 있다고 합니다. js 코드를 통해 쿠키에 접근하려고 해도 https 통신을 할 때에만 접근이 가능하기 때문입니다.

단점으로는 CSRF 공격에 취약합니다. 쿠키가 자동으로 요청 시에 날아가기 때문에 공격자가 request url를 클릭하도록 유도하면 요청을 위조하기 쉽습니다. refresh token을 통한 요청을 위조하여 access token을 취득한다음 개인정보를 빼낼 수 있습니다.

두 번째 선택지는 local storage에 저장하는 방법입니다. 이 방법은 반대로 request시 자동으로 가는 쿠키가 아니고, js 코드에 의해 헤더에 담겨서 전송되므로 url을 위조하더라도 CSRF를 방어할 수 있습니다. 또한 반대로 XSS에 취약해집니다. js를 통한 악성코드를 심어두었다면 접근이 너무 쉬워집니다.

다른 해법으로는 refresh token을 서버에서만 관리하는 방법도 있습니다. token 발급 시에 DB 인덱스값만 보내고, refresh token은 DB에 저장합니다. 인덱스 값은 해시 처리 등을 통해 서버만 알 수 있도록 하여 보안을 강화한 형태로 전달합니다. 이렇게 되면 클라이언트는 refresh token을 탈취당할 위험이 줄어들긴 합니다. 하지만 서버의 부하를 줄이기 위한 JWT의 목적과 조금 멀어지는 느낌이 듭니다.

그나마 쿠키에 HttpOnly, Secure 옵션을 추가하여 저장하는 형태가 좋다고 합니다.

여담으로 access token은 자동으로 요청에 전달되는 쿠키가 아닌 자바스크립트 로컬 변수에 저장하여 헤더에 토큰을 담아서 매 요청마다 보내도록 하는 것이 좋다고 합니다. (쿠키에 사용할 경우 CSRF에 취약)

* RESTful 하다는 것은 무엇일까요?

https://www.youtube.com/watch?v=RP_f5dMoHFc

REST가 등장한 역사는 어떻게 인터넷에서 정보를 공유할 것인가?에서 시작된다. 정보들을 하이퍼텍스트로, html로 표현하고 식별자로 URI를 만들고, 전송 방법으로 HTTP라는 프로토콜을 만들었다. HTTP 1.0의 등장 이후 http가 깨지지 않고 어떻게 발전할 수 있을지 고민했다.

HTTP Object Model이라는 이름으로 나온 기술은 Representational State Transfer이 된다.

그리고 API라는 것이 만들어지기 시작한다. 먼저 RPC라는 프로토콜이 생기고, 이것은 이후 SOAP이 된다. 플리커 API는 REST와 SOAP 버전의 두 가지 api를 공개했다. 메세지 분량이 REST가 압도적으로 줄어들어, REST의 폭발적인 발전이 이뤄진다.

2006년에는 AWS 의 자사 api 사용량이 REST가 85%에 육박할 정도로 독점적이 되었다.

로이 필딩은 발전하는 REST 개념에 지속적으로 부정을 했다. 진짜 REST 아키텍쳐가 아니었다는 뜻이다. REST는 분산 하이퍼미디어 시스템(예: 웹)을 위한 아키텍쳐 스타일이다.

아키텍쳐 스타일이란 제약조건의 집합이다. 제약조건을 모두 지켜야 REST를 따르고 있다고 말할 수 있다. REST를 구성하는 스타일은,

  • client-server
  • stateless
  • cache
  • uniform interface
  • layered system
  • code-on-demand (optional)

uniform interface의 제약조건

  • identification of resources
  • manipulation of resources through representations
  • self-descriptive messages
  • hypermedia as the engine of application state

Self-descriptive message

GET / HTTP/1.1 이라는 요청 메시지가 있다고 하면, 이게 어디로 가는지 알 수 없다. 그래서 Host 를 통해 어디로 가는지 적어줘야 비로소 self-descriptive 해진다.

응답 또한 content-type 등의 정보를 통해 메세지를 보고 부가적인 정보 없이 해석이 가능해야 한다.

HATEOAS

애플리케이션의 상태는 하이퍼 링크를 통해 전이되어야 한다. 일반적인 게시글 페이지를 이동하는 형태가 이것을 만족한다고 할 수 있다. json으로 응답할 경우에도 Link를 통해 다른 상태로 갈 수 있는 하이퍼링크를 명시해줄 수 있다.

왜 Uniform interface?

  • 독립적 진화를 하기 위해서!
    • 서버와 클라이언트가 각각 독립적으로 진화한다.
    • 서버의 기능이 변경되어도 클라이언트를 업데이트할 필요가 없다.
    • How do I improve HTTP without breaking the Web.

웹은 REST를 지키고 있다.

  • 웹 페이지를 변경했다고 해도 웹 브라우저를 업데이트할 필요는 없다.
  • 웹 브라우저를 업데이트했다고 웹 페이지를 변경할 필요도 없다.
  • HTTP 명세가 변경되어도 웹은 잘 동작한다.
  • HTML 명세가 변경되어도 웹은 잘 동작한다.

한 마디로 요약하자면, 하이퍼 텍스트를 포함한 self-descriptive한 메시지의 uniform interface를 통해 리소스에 접근하는 API이다.

시스템 전체를 통제 가능하거나, 진화에 관심이 없다면 굳이 REST를 따르지 않아도 된다.

REST를 만들기 힘들 이유

우리가 사용하는 HTTP API에서는 json을 사용한다. json은 html에 비해 표준으로 정해진 것이 없다. html은 사용하는 태그의 종류가 모두 표준으로 명세되어있지만, json은 구체적인 key-value에 대해서는 표준으로 정하지 않는다. 그래서 우리는 API 문서를 통해 해석해야만 한다.

그래서 json을 Self-descriptive하게 만들려면,

  1. media-type을 IANA에 등록한다.
  2. Profile을 통해 명세를 링크한다.

HETEOAS를 만들려면,

  1. data에 직접 link를 넣는다.
  2. HTTP 헤더로 Link나 Location으로 표현한다.

설게 관련

넷플릭스와 같은 서비스를 디자인한다면?

https://www.youtube.com/watch?v=7OZ7R0VoMZM

넷플릭스는 웹 스트리밍 서비스이므로 이럴 때는 Web, WAS, DB의 3요소 중 Web server에도 굉장히 많은 신경을 써야 한다.

스트리밍에 관련해서는 컨텐츠 압축이나 보안(DRM) 등 도메인과 관련된 얘기도 나올 수 있다.

일반적으로는 Web server에서는 부하 분산, WAS에는 인증과 서비스 로직, 과금 등이 중요 이슈이다.

일반적인 구조를 설명하면 다음과 같을 것 이다.

서비스 서버와 별도로 영상 파일을 보관하는 미디어 서버를 일반적으로 따로 두게 된다. 사용자는 서비스 서버를 통해 로그인을 통한 인증 과정을 거친다. 그러면 서비스 서버는 구매한 영상이나 서비스하는 영상을 보여준다. 클라이언트는 영상을 선택하고, 재생될 것이라는 기대를 한다. 그럼 서비스 서버는 사용자가 넘긴 정보를 통해 인증을 거쳤는지 확인하고 선택한 영상을 미디어 서버에서 직접 사용자에게 넘길 수도 있다. 이 경우에는 미디어 서버를 HTTP 기반의 서버로 설계하겠다는 뜻이고, 다른 선택지는 HLS 등의 다른 프로토콜을 사용할 수도 있다.

추가적으로 클라이언트가 자주 조회하는 컨텐츠가 존재하기 마련이다. 미디어 서버의 동시접속자가 10명밖에 안된다고 한다면, 동시에 영상을 볼 수 있는 사용자가 10명밖에 되지 않는다.

그렇기 때문에 부하 분산이 필요하다.

글로벌 유저에 서비스하기 위해서, 이런 아이디어를 생각해볼 수 있다. 웹 서버는 클라이언트가 접속한 ip에 따른 ISP에 따라 각 ISP에 미디어 서버를 직접 연결해준다. (KT, SKT 등에 직접 미디어 서버를 연결하고 영상 사본을 보관했다가 바로 서비스한다.) 이 것은 CDN 서비스를 모방한 개념이다.

또 하나의 이슈는 미디어 서버에 저장된 영상 파일을 HDD로부터 I/O해오는 과정에 대한 것이다. 일반적으로 I/O는 매우 느리기 때문에 RAM에 올려놓고 서비스하거나 해야 하는데 영상의 크기가 크다면 문제가 될 수 있다.

추가적으로 많은 사용자가 동시에 I/O 요청을 디스크에 하게 되면 급격하게 I/O 속도가 느려진다. 그렇기 때문에 미디어 서버의 커널 레벨에서 여러 튜닝이 들어가야 한다.

주요 키워드

  • WebRTC: 화상 회의 등을 구현하는 오픈소스 기술
  • 콘텐츠 보호(ts 등)

함수형 프로그래밍

일급 함수와 고차 함수에 대해 설명해주세요.

일급이란 값으로 다룰 수 있고, 변수에 담을 수 있으며, 함수의 인자로 사용될 수 있고, 반환 값으로 사용될 수 있다. 일급 함수란 함수를 값으로 다룰 수 있는 것을 말한다. 이 때 고차함수는 함수를 인자로 받아서 실행하거나 함수를 만들어 리턴하는 함수를 말한다. Java stream의 map, reduce 등이 이에 해당한다.

*가 붙은 내용은 이해가 더 필요한 내용입니다.

About

기술면접 대비용 질문 정리 레포지토리입니다

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published