처음 신청해봤던 프리온보딩 챌린지였다. 사전미션을 열심히 공부했던 기억이 남아 기록을 남긴다
가상 면접 사례로 배우는 대규모 시스템 설계 기초를 바탕으로 강의내용이 구성되었었다.
https://github.com/minjeong-j/wanted-pre-onboarding-challenge-be-task-July
사전과제 질문들은 아래와 같았다. 면접 질문으로도 잘 나오는 질문들이다.
1) 동시에 같은 DB Table row를 업데이트 하는 상황을 방어하기 위해 어떻게 개발할 것인지 설명해주세요.
2) TCP와 UDP의 차이를 작성해주세요.
3) 웹브라우저에 네이버를 검색하고 화면에 '네이버'를 검색하고 화면에 네이버 화면이 출력이 될 때까지 내부적으로 어떤 동작들이 수행이 되는지 설명해주세요.
4) 본인이 주력으로 사용하는 언어에서 설계적 결함 한 가지를 작성해주세요.
5) 본인이 주력으로 사용하는 언어에서 자료구조와 관련된 클래스가 내부적으로 어떻게 동작하는지 한 가지 사례를 정하여 작성해주세요.
동시에 같은 DB Table row를 업데이트 하는 상황을 방어하기 위해 어떻게 개발할 것인지 설명해주세요.
> 분산락 방법(distrubuted lock)
1. 개념 : 경쟁상황에서 하나의 공유자원에 접근할 때, 데이터의 결함이 발생하지 않도록 원자성(atomic)을 보장하는 방법을 말함
(예시 : MySQL의 네임드락, Redis, Zookeeper 등)
2. 분산락을 사용하는 이유 : 자바 스프링 기반의 웹 어플리케이션은 기본적으로 멀티쓰레드에서 구현된다. 따라서 여러 쓰레드가 접근할 수 있는 공유자원에 대해 경쟁상태가 발생하지 않도록 별도의 처리가 필요하다. 일반적인 서비스에서는 서버를 다중화하여 부하를 분산, 이런 분산환경에서 동시성 문제를 다루기 위해 분산락이 등장한다.
> Java 기반 웹프로그램 기준 데이터베이스의 lock을 이용하는 방법
제고를 확인하는 순간 리소스를 점유합니다. 해당 레코드의 트랜잭션 동안 lock을 점유해 현재 재고가 몇건인지 파악하여 재고가 틀리면 롤백이 이루어지도록 합니다.
1) 쿼리를 통헤 락 점유 : For update를 통해 트랜잭션 수행동안 락을 획득해 멀티 스레드 환경에서도 리소르를 점유
<select id="selectStockOfProduct" resultType="int">
SELECT PRODUCT_STOCK
FROM PRODUCT
WHERE PRODUCT_NAME=#{productName}
FOR UPDATE
</select>
2) 서비스 레이어에 트랜잭션을 걸기 :
DB와 관련된 트랜잭션이 필요한 서비스 클래스 혹은 메소드에 @Transactional 어노테이션을 달아줍니다.
@Transactional이 붙은 메소드는 메소드가 포함하고 있는 작업 중에 하나라도 실패한 경우 전체 작업을 취소합니다.
@Transactional
public String getKakaoPayUrl(OrderDTO orderDTO, HttpServletRequest request) throws RunOutOfStockException {
/*재고 확인*/
int productStock = productMapper.selectStockOfProduct(OrderDTO orderDTO.getItemName());
if(OrderDTO.getQuantity() > productStock) {
throw new RunOutOfS
}
/*재고 차감*/
/*
같은 메소드 내 update 쿼리 추가
=> 예외가 발생하면 전부 롤백이 되어야 함.
=> 트랜잭션의 Atomicity : 원자성. 트랜잭션과 관련된 일은 모두 실행되던지 모두 실행되지 않도록 하던지를 보장하는 특성
*/
updateStock(productStock - orderDTO.getQuantity(), orderDTO.getItemName(), "product_name");
}
>교착 상태(Deadlock)을 DB에서 사용할 수 있는 가장 단순한 방법 : 프로시저
1) 트랜잭션을 짧게 유지하고 트랜잭션 중 사용자 입력방지를 해줍니다.
2) 하나의 트랜잭션을 완료하면 commit 합니다.
START TRANSACTION
update item set itemCode='1234' where itemId = 2;
COMMIT;
TCP와 UDP의 차이를 작성해주세요.
가장 중요한 차이는 규격입니다.
TCP(Transmission Control Protocol)는 데이터의 경계를 구분하지 않고 3-way handshaking 과정을 통해 연결 후 통신을 시작하는데 흐름 제어와 혼잡 제어를 지원하며 데이터의 순서를 보장합니다.
시작, 데이터 전송, 끝의 순서로 전송되어 속도가 느리다는 단점이 있지만 신뢰성있는 데이터 전송을 지원하기 때문에 주로 기업이나 은행처럼 대용량의 데이터나 중요한 데이터 전송에 이용됩니다. 또한 일대일, 일대다(Broadcast), 다대다(Multicast) 통신을 사용하는 UDP와는 다르게 TCP는 일대일 통신만을(Unicast) 사용합니다.
UDP(User Datagram Protocol)는 시작과 끝이 따로 존재하지 않습니다. 인터넷 상에서 서로 정보를 주고 받을 때 정보를 보낸다는 신호나 받는다는 신호절차를 거치지 않고, 보내는 쪽에서 일방적으로 데이터를 전달하는 통신 프로토콜입니다. TCP와 다르게 연결 설정이 없으며 혼잡 제어를 하지 않기 때문에 전송 속도가 빠릅니다. 대신 데이터 전송에 대한 보장을 하지 않기 때문에 패킷 손상이 발생할 수 있어 신뢰성보다 연속성, 속도가 중요한 실시간 스트리밍, 온라인 게임에 주로 사용됩니다.
- 흐름제어 : 보내는 측과 받는 측의 데이터 처리 속도 차이를 조절해주는 것
- 혼잡제어 : 네트워크 내의 패킷 수가 넘치게 증가하지 않도록 방지하는 것
웹브라우저에 네이버를 검색하고 화면에 '네이버'를 검색하고 화면에 네이버 화면이 출력이 될 때까지 내부적으로 어떤 동작들이 수행이 되는지 설명해주세요.
1. 알아야 할 사전 지식 :
1) IP 주소 : 컴퓨터들이 인터넷 상에서 서로를 인식하기 위해 지정받은 식별용 번호 ipv4(32비트)로 구성되어 있으며, ipv4의 부족으로 ipv6(128비트)도 사용하고 있습니다.
2) Domain과 DNS : ip는 12자리의 숫자로 되어있기 때문에 매번 사이트에 접근할 때 마다 ip 주소를 쓰기는 어렵습니다. 그래서 12자리의 숫자 대신 www.naver.com이라는 도메인 네임을 사용할 수 있게 구성됩니다. 도메인 네임은 편의성을 위해 만든 주소이므로 실제로는 컴퓨터가 이해할 수 있는 ip주소로 변환하는 작업이 필요한데 도메인 네임과 함께 해당하는 ip주소값을 한 쌍으로 저장하고 있는 데이터베이스를 DNS(DomainNameSystem)라고 합니다.
2. 과정 :
1) 브라우저에 네이버를 검색하면 www.naver.com은 도메인 네임이기 때문에, 해당 도메인 네임을 DNS 서버에서 검색합니다.
2) DNS는 www.naver.com에 대응하는 ip 주소를 URL 정보와 함께 응답으로 돌려줍니다.
3) 웹페이지 URL 정보와 전달받은 ip 주소는 HTTP 프로토콜을 사용하여 TCP 통신을 통해 해당 ip 서버에 요청을 보냅니다.
전달받은 ip 주소를 이용해 웹브라우저는 웹 서버에 해당 웹사이트에 맞는 HTML 문서를 요청.
4) 요청을 받은 네이버 서버는 요청 내용에 대한 처리 과정을 거쳐 HTTP 응답 메시지를 만듭니다.
5) 생성된 HTTP 응답메시지는 TCP프로토콜(통신)을 통해 원래 컴퓨터 클라이언트에게 전송합니다.
6) 브라우저는 받은 응답메시지를 HTTP프로토콜을 사용해 웹페이지를 데이터로 변환해 사용자에게 naver 화면을 출력해줍니다.
Java에서 설계적 결함 한 가지를 작성해주세요.
> 제네릭
제네릭(Generics) : 데이터 형식에 의존하지 않고, 하나의 값이 여러 다른 데이터 타입들을 가질 수 있도록 하는 방법.
클래스 내부에서 지정하는 것이 아닌 외부에서 사용자에 의해 지정되는 것을 의미한다.
=> 특정(Specific) 타입을 미리 지정해주는 것이 아닌 필요에 의해 지정할 수 있도록 하는 일반(Generic) 타입이라는 것이다.
//객체<타입> 객체명 = new 객체<타입>();
ArrayList<Integer> list = new ArrayList<Integer>(10);
ArrayList<String> list = new ArrayList<String>();
자바 제네릭의 한계
: 제네릭을 활용해 컴파일 타임에 타입 체크를 하고 나면 제네릭 인자로 넘겨져 온 타입은 Type erasure라는 절차를 통해 제거된다. 따라서 인자로 넘겨진 타입은 런타임에서는 활용될 수 없다.
1) 예시1
ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if (li.getClass() == lf.getClass()) { // evaluates to true
System.out.println("Equal");
}
=> 이 코드는 제네릭 타입 인자 값이 다르지만 같은 클래스로 인정된다. 런타임에는 타입이 지워지기 때문이다.
2) 예시2
public class MyClass<E> {
public static void myMethod(Object item) {
if (item instanceof E) { //Compiler error
...
}
E item2 = new E(); //Compiler error
E[] iArray = new E[10]; //Compiler error
}
}
=> 다음 코드에서 instanceof, new 등 연산자를 활용할 수 없는 이유도 Type erasure때문이다.
Java에서 자료구조와 관련된 클래스가 내부적으로 어떻게 동작하는지 한 가지 사례를 정하여 작성해주세요.
ArrayList
1. ArrayList를 선택한 이유 : ArrayList는 간단하게 사용할 수 있어 프로젝트에서 많이 사용할 수 있는 Collection Framework라 선택하였습니다.
2. ArrayList와 HashMap의 차이 : 100개 중 97번 째 값을 찾는다고 했을 때 ArrayList는 1부터 for문을 돌려서 찾고 HashMap은 key-value 값이라 찾는 속도가 더 빠릅니다.
3. ArrayList 사례
1) 생성
ArrayList<String> list = new ArrayList<>();
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
ArrayList를 새로 생성하여 기본 생성자 elementData 필드에 DEFAULTCAPACITY_EMPTY_ELEMENTDATA를 할당합니다.
transient Object[] elementData;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
elementDataelementData는 ArrayList 요소가 저장되는 배열 버퍼입니다.
- 버퍼(buffer) : 데이터를 한 곳에서 다른 곳으로 전송하는 동안 일시적으로 그 데이터를 보관하는 임시 메모리 영역 - 입출력속도 향상을 위해 사용함
transient : 직렬화(Serialize) 대상에서 제외되지만, 직렬화가 불가능하다는 점은 아니라 ArrayList는 이에 따라 다른 직렬화 로직을 따르게 됩니다.
DEFAULTCAPACITY_EMPTY_ELEMENTDATA로 비어있는 Object 배열을 생성하는데 이 필드가 ArrayList의 역할을 할 필드로, 자료를 추가하고 삭제하면 위 배열에 추가되고 삭제됩니다.
2) ArrayList에 요소 추가
list.add("hello")
list.add("Java")
추가한 데이터를 확인해보기 위해 add Method를 확인해보면 다음과 같습니다.
public boolean add(E e) { //E : String, e : "hello"
modCount++;
add(e, elementData, size); //add("hello", elementData, 0)
return true;
}
modCount를 증가시키고 add(e, elementData, size) 함수를 실행하고 마지막으로 true를 return합니다
- modCount : ArrayList가 상속한 AbstractList 클래스에 있는 필드로 ArrayList 자료구조에 얼마나 수정이 되었는지에 대한 횟수를 체크하는 필드.
여기에 add() 메소드를 통해 String 객체 하나를 넣으면 ArrayList 클래스 내부에 grow 메소드를 호출합니다.
private Object[] grow() {
return grow(size + 1);
}
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity));
}
private int newCapacity(int minCapacity) {
// overflow-conscious codeint oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
//...
}
//...
}
코드 상에서 확인할 수 있듯이 grow에 기존의 size 값에서 1을 더한 값을 인자로 넣게됩니다. (초기 생성자에 의해 처음 size는 0으로 설정) newCapacity 메소드에서는 처음에는 elementData가 DEFAULTCAPACITY_EMPTY_ELEMENTDATA로 설정되었기에 DEFAULT_CAPACITY와 size 하나 증가한 값과 비교해서 큰 값으로 return하게 됩니다.
즉 처음에는 요소 한개를 add하면 Arrays.copyOf(elementData, newCapacity(minCapacity)); 이부분에서 10개의 저장공간을 가진 배열을 elementData 변수에 할당하게 됩니다.(ArrayList size와 완전 별개입니다. 배열의 크기가 10개로 된 것뿐입니다.)
참고로 copyOf(A, capacity)에서 기존 A배열의 크기보다 더 큰 capacity 값을 설정하면 잉여공간에 해당 배열의 타입 default 값으로 설정됩니다.
'개발 기록 > backend' 카테고리의 다른 글
[백엔드] 프리온보딩 객체지향 2차시 - 프로젝트로 시작하는 객체지향 (1) | 2024.01.09 |
---|---|
[백엔드] 원티드 프리온보딩 챌린지 객체지향 과제 - 설계 (0) | 2024.01.08 |
[백엔드] 프리온보딩 객체지향 1차시 - 소프트웨어 가치에서 시작하는 객체지향 (1) | 2024.01.07 |
[백엔드] 원티드- Mysql 기본기 다지기_사전미션 (0) | 2023.12.29 |
[백엔드]원티드 - 프로젝트 유연함을 높이는 객체지향 첫걸음_사전미션 (0) | 2023.12.27 |