ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 내가 받았던 백엔드 인터뷰 질문들 (Java, Spring)
    SE General 2021. 9. 28. 18:36
    반응형
     

    4년차 자바 백엔드 기술 질문들

    최근 '예상치 않게' 백엔드 기술 인터뷰에 참여하게 되었습니다. 당황한만큼 질문에 대한 응답도 제대로 하지 못했는데요. 이번 글에서는 기억에 남는 질문들을 정리하고, 그에 대한 답을 정리

    kadensungbincho.tistory.com

     

    실패의 쓰라림도 이후에 일이 잘 되고 나서는 좋은 경험이었다고 바라보게 됩니다. 

     

    받은 질문에 대해 좀 더 정확한 답변을 찾아보며, 인터뷰에 할애한 시간이 스스로에게 조금이라도 도움이 되도록 정리해 보았습니다.

     


    A

    오라클 조인 종류?

    간단하게 Inner, Outer, Left, Right [1] 등에 대한 질문이었음

    과도하게 Broadcast Join, Nested loop 등을 생각하느라 답변을 잘 하지 못함

     

     

    톰캣 ajp Connector 말고 다른 Connector?

    8기준 크게 HTTP Connector들(NIO, NIO2, APR) [3]과 AJP Connector 존재.

     

     

    톰캣의 구조?

    Service, Engine, Host, Context, Connector 등의 객체로 구성된 구조. 서블릿 컨테이너 명세를 구현한 것.

    Image from [2]

     

    스택 2개로 큐 만들기? 성능 개선 방법?

    하나는 insert용, 하나는 뒤짚기용 [4]. 성능 개선은 lazy 정도 밖에 떠오르지 않았음.

     

     

    Connection에서 Timeout 종류 [5]

    connect: 3-way handshake가 timeout 시간 안에 끝나야 함

    read: 클라이언트가 서버로부터 timeout 시간 안에 응답을 받아야 함

    write: 클라이언트가 서버에 timeout 시간 안에 성공적으로 write해야 함

    HTTP keep-alive (client side): HTTP 헤더 일부로 클라이언트가 timeout 시간 안에 서버의 응답을 받지 못하면 서버가 살아 있는지 체크하기 위해 요청을 보냄. 죽었으면 Connection 끊음

    Request (server side HTTP): 서버가 클라이언트로부터 timeout 시간 이상 데이터를 받지 않으면 서버는 Connection을 드랍함 (용어가 정확한지 모르겠음, HTTP keep-alive를 서버 관점에서 보는 것인 듯 함)

     

     

    Web Server의 Timeout이 매우 길고 요청이 무수히 많고 쓰레드 풀도 무한이며 작업은 그냥 무한한 sleep이라면, 가장 먼저 고갈되는 자원은?

    아마도 CPU-bound로 생각됨 [6]

     

     

    TCP Keepalive 길때 생기는 문제는?

    새롭게 진입하려는 커넥션이 자원을 사용하지 못해 생성되지 못함.

     

     

    스프링 MVC 동작 방법?

    서블릿 구현체로 서블릿 <-> DispatcherServlet <-> Controller 간의 요청, 응답을 주고 받음. 

    Image from [7]

     

     

    MQ 쓰는 이유?

    Kafka를 만든 Jay Kreps 글에서 로그의 역할과 관련되어 답변하려 했음 (이미지가 떠올랐음). 

    MQ이기에 Kafka와 같은 장점은 많은 제약이 있어서(여러번 consume, 복잡도 줄이는 것? 등), 앞뒤 간의 의존성을 없앨 수 있는 부분이 크다고 답변함. (대략 decouple이라는 점과 상통하는 듯 [9])

    Before - Image from [8]
    After - Image from [8]

     

    이벤트 소싱 형태에서 데이터의 상태를 복원하는 방법?

    Write-ahead log 같은 것을 이벤트 소싱 형태로 스트림으로 흘려주고, replay를 통해 복제할 수 있다고 함.

    CDC와 같은 도구들도 언급 [10].

     

     

     

    B

    Interface vs abstract class [12]

    가볍게 이해하고 있는 부분만 언급했는데, default method를 예로들며 추가 질문이 들어왔음. Polymorphism과 연관되어 어떠한 답변을 기대한 듯 하였음.

     

    Abstract class는 언제 쓸까?

    - 객체가 무엇인가와 관련되어 사용 [13]

    - 상태와 기능을 공유하려 할 때 [14]

    - 상속 개념을 사용할 때 (인터페이스의 default method로 무색하긴 하나 [12])

    - non-public 멤버를 선언하려할 때 (인터페이스에서는 public이어야 함)

    - 추후에 새로운 메소드들을 더할 때. 인터페이스에 새로운 메소드를 추가하면, 해당 인터페이스 implement한 모든 클래스가 새로운 메소드를 구현해야 되기에.

    - 컴포넌트의 여러 버젼을 생성하려 할 때. 추상 클래스만 업데이트하면 상속하는 모든 클래스가 변경됨. 인터페이스를 쓰면, 일단 한 번 생성하면 변경하기 어렵기에 새로운 버젼을 위해 새로운 인터페이스를 생성하여야 함.

    - 좀 더 뛰어난 forward 호환성 이점을 제공. 일단 클라이언트가 인터페이스를 사용하기 시작하면, 그것을 변경하기는 매우 어려움.

    - 여러 컴포넌트들이 사용하는 공통된 기능을 제공하고자 할 때.

     

    Interface는 언제 쓸까?

    - 객체가 무엇을 할 수 있는지와 관련되어 사용 (일종의 contract) [13]

    - 상태와 기능에 대한 promise를 제공하기 위해 [14] 

    - 연관성이 약한 여러 객체들에 일반적인 기능들이라면 인터페이스를 사용.

    - API가 변경되지 않는다고 생각되면 좋은 선택

    - 다중 상속과 비슷한 요구사항을 원할 때

    - 작고 간결한 기능 모음을 디자인 할 때 사용

     

     

    브라우저를 켜서 특정 사이트 접속 시 발생하는 일을 OSI 7계층 관점에 기반해 설명해달라

    [31, 32]

     

     

    mutex와 semaphore의 차이는? [27, 28, 29, 30]

    mutex

    mutex는 mutual exclusion을 제공하는 locking mechanism입니다. critical region은 task한 task가 '소유하게(Principle of Ownership)' 됩니다. 

     

    mutex는 이러한 ownership의 존재로 인해 semaphore의 아래와 같은 문제점을 해결해줍니다:

     

    • Accidental release
    • recursive deadlock
    • deadlock through Task Death: Death detection을 통해
    • priority inversion
    • semaphore as a signal: mutex는 ownership으로 인해 synchronization에 사용될 수 없음. 그렇기에 사용처를 명확히 함.

     

    몇몇 OS 내부의 mutex는 구현에 따라 Recursion, Priority inheritance, Death Detection을 지원하거나 지원하지 않을 수 있습니다. 

     

    semaphore

    semaphore는 signaling mechanism으로 resource를 접근으로부터 protecting하지 않습니다. Semaphore의 Giving과 Taking은 근본적으로 decoupled되어 있습니다. 크게 binary와 counting semaphore가 존재합니다.

     

    예로, 주차장을 관리하는 시스템의 경우 처음에 주차장 갯수를 count에 설정합니다. 이후 자리가 사용되면서 count는 줄어들게 되는데요. count가 0이 되면 다음 차는 주차가 blocked됩니다. 이후 차 1대가 나가면서 count는 1로 증가하고 기다리던 차가 사용할 수 있게 됩니다. 

     

    좀 더 시그널링적인 측면을 잘 나타내고 있는 예는 아래와 같습니다:

       Task A                      Task B
       ...                         Take BinSemaphore   <== wait for something
       Do Something Noteworthy
       Give BinSemaphore           do something    <== unblocks

    Task B는 특정 이벤트 발생을 기다립니다. Task A는 특정 이벤트 발생을 알리기 위해 어떠한 액션을 취하고, Task B는 그러한 부분을 확인하게 됩니다. 

     

    특히, binary semaphore에서 B가 semaphore를 take한 후에 A가 semaphore를 놓아주었다는 부분입니다. 이렇게 Binary semaphore는 리소스 접근에 대한 protecting을 하지 않습니다.  

     

     

    AOP란? AOP의 주된 패턴은?

    개념은 잘 말했는데 안써봐서 그런지 패턴은 무엇이고, 내부는 어떻게 되어 있는지에 대한 질문에 답변 못함.

     

    AOP는 프로그래밍 패러다임으로 cross-cutting하여 특정 '측면'을 분리하고 모듈성을 높일 수 있음 [15]. 

     

    AOP의 주된 패턴은 Proxy로 Spring AOP는 jdk dynamic proxies를 스탠다드로 사용함. 또한, CGLIB proxies도 사용할 수 있며 AspectJ를 사용해 annotation을 사용할 수 있음 [16]. 

     

    Proxy 패턴을 보면, 정적 및 동적 프록시 패턴이 있음. [17]

     

    정적은, 

     

    직접 @Override하거나

    package com.test.proxy;
    
    public class Person {
        public void run(){
            System.out.println("Person Class run method");
        }
    }
    package com.test.proxy;
    
    public class PersonStaticProxy extends Person {
        @Override
        public void run() {
            System.out.println("Person Class run Before method execution");
            super.run();
            System.out.println("Person Class run After the execution of the method");
        }
    }
    package com.test.proxy;
    
    public class Test {
        public static void main(String[] args) {
            Person person = new PersonStaticProxy();
            person.run();
        }
    }
    Person Class run Before method execution
    Person Class run method
    Person Class run After the execution of the method

     

    인터페이스와 구현체 타겟 클래스를 Proxy 클래스에 embed하여 아래와 같이 구현 가능:

    package com.test.proxy;
    
    public interface MathematicDao {
        public void add(int a, int b);
    }
    package com.test.proxy;
    
    public class MathematicImpl implements MathematicDao {
        @Override
        public void add(int a, int b) {
            System.out.println("a plus b The values of are:" + (a + b));
        }
    }
    package com.test.proxy;
    
    public class MathematicProxy implements MathematicDao {
        MathematicImpl mathematic;
    
        public MathematicProxy( MathematicImpl mathematic){
            this.mathematic = mathematic;
        }
    
        @Override
        public void add(int a, int b) {
            System.out.println("add Before method execution");
            this.mathematic.add(a, b);
            System.out.println("add After the execution of the method");
        }
    }
    package com.test.proxy;
    
    public class Test {
        public static void main(String[] args) {
            MathematicDao md = new MathematicProxy(new MathematicImpl());
            md.add(11, 22);
        }
    }
    add Before method execution
    a plus b The value of is: 33
    add After the execution of the method

     

    그러나 타겟 클래스가 100개면 Proxy 클래스도 100개를 작성해야 되서 중복이 많이 발생함.

     

    동적은 jdk dynamic agent 또는 cglib dynamic agent를 사용해서 구현할 수 있는데, 동적이든 정적이든 타겟 클래스 메소드에 변형을 가하진 않음.

     

    먼저 jdk dynamic agent 기반은:

    package com.test.proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class DemoProxyHandler implements InvocationHandler{
        private Object obj;
        public DemoProxyHandler(Object obj) {
            this.obj = obj;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
            System.out.println(method.getName() + "Before implementation");
            Object res = method.invoke(obj, args);
            System.out.println(method.getName() + "After implementation");
    
            return res;
        }
    }
    package com.test.proxy;
    
    public interface MathematicDao {
        public void add(int a, int b);
    }
    package com.test.proxy;
    
    public class MathematicImpl implements MathematicDao {
        @Override
        public void add(int a, int b) {
            System.out.println("a plus b The values of are:" + (a + b));
        }
    }
    package com.test.proxy;
    
    import java.lang.reflect.Proxy;
    
    public class Test {
        public static void main(String[] args) {
            MathematicDao m = new MathematicImpl();
    
            DemoProxyHandler dp = new DemoProxyHandler(m);
    
            Object obj = Proxy.newProxyInstance(m.getClass().getClassLoader(), m.getClass().getInterfaces(), dp);
    
            MathematicDao mathematicDao = (MathematicDao) obj;
    
            mathematicDao.add(1, 3);
        }
    }
    add Before implementation
    a plus b The value of is: 4
    add After implementation

     

    cglib은 low-level bytecode 기술을 사용함. bytecode 기술을 통해 클래스에 대한 서브클래스를 생성하고, 서브클래스에서 메소드 인터셉션 기술을 통해 부모 클래스 메소드에 대한 모든 콜을 인터셉트하고 cross-cutting 로직을 수행함:

    package com.test.proxy;
    
    public class CglibProxyHandler implements MethodInterceptor{
    
        private Object target;//The target object of the delegate
    
        public Object getInstance(Object target) {
            this.target = target;
    
            Enhancer enhancer = new Enhancer();
    
            enhancer.setSuperclass(target.getClass());
    
            enhancer.setCallback(this);
    
            return enhancer.create();
        }
    
        @Override
        public Object intercept(Object enhancer, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    
            Object res = null;
    
            System.out.println(method.getName() + "Before method execution");
    
            try {
                res = methodProxy.invokeSuper(enhancer, args);//Equivalent to calling the original method
            }catch(Exception e) {
                e.printStackTrace();
                System.out.println(method.getName() + "Method execution exception");
            }
    
            System.out.println(method.getName() + "After the execution of the method");
    
            return res;
        }
    }
    package com.test.proxy;
    
    public class Person {
        public void run(){
            System.out.println("Person Class run method");
        }
    }
    package com.test.proxy;
    
    import java.lang.reflect.Proxy;
    
    public class Test {
        public static void main(String[] args) {
            CglibProxyHandler cph = new CglibProxyHandler();
          
            Person p = (Person) cph.getInstance(new Person());
    
            p.run();
        }
    }
    Person Class run Before method execution
    Person Class run method
    Person Class run After the execution of the method

     

    JDK는 인터페이스를 구현하는 클래스 내 메소드만 프록시할 수 있음. cglib는 상속에 기반한 프록시를 하여 static, final 클래스를 프록시 할 수 없음. cglib은 private, static 메소드 프록시 불가능.

     

    AOP는 프로그래밍적 사고 방식으로 특정 메소드에 대해 non-intrusive extension이 가능하게 함.

     

    Spring AOP에는 Aspect, Join Point, Advice, Pointcut, Introduction, Target object, AOP Proxy, weaving 등의 개념이 존재하는데, 구현은 대략 아래와 같이 가능함:

     

    Advice 클래스 생성:

    package com.test.proxy;
    
    public class MethodAdvice {
        public void before(JoinPoint jp) {
            System.out.println("Before advice ");
        }
    
        public void after(JoinPoint jp) {
            System.out.println("Post notification");
        }
    
        public void afterReturning(JoinPoint jp, Object obj) {
            System.out.println("Notification after return, return value:" + obj);
        }
    
        public void afterThrowing(JoinPoint jp, Exception e) {
            System.out.println("Exception notification, abnormal:" + e.getMessage());
        }
    }

    config 설정:

      <bean id="methodAdvice" class="com.test.proxy.MethodAdvice"></bean>
      
     <aop:config>
     
      <aop:pointcut expression="execution( * com.test.proxy.*.*(..))" id="pt"/>
       
       <aop:aspect ref="methodAdvice">
      
        <aop:before method="before" pointcut-ref="pt"/>
       
        <aop:after method="after" pointcut-ref="pt"/>
        
        <aop:after-throwing method="afterThrowing" pointcut-ref="pt" throwing="e"/>
        
        <aop:after-returning method="afterReturning" pointcut-ref="pt" returning="obj"/>
       </aop:aspect>
      </aop:config>
      
      <bean id="student" class="com.test.proxy.Student"></bean>
    package com.test.proxy;
    
    public class Student {
        public void study() {
            System.out.println("Student Class study method");
        }
    
        public String getStr(String s) {
            System.out.println("Student Class with return value");
            return s + "!!";
        }
    }
    package com.test.proxy;
    
    public class Test {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            Student stu= (Student) ctx.getBean("student");
    
            stu.study();
    
            stu.getStr("hello");
        }
    }
    Before advice 
    Student Class study method
     Post notification
     Before advice 
    Student Class with return value
     Post notification
     Notification after return, return value:hello!!

     

     

    Python GIL

    쓰레드 풀 이야기 하다 파이썬 이야기로 흘러서 GIL 질문이 들어옴. 어버버 하다 이상하게 답변.

     

    Global Interpreter Lock으로 mutex로 언제나 오직 1개의 쓰레드만 Python interpreter의 control을 허용하도록 함. [18, 19]

     

    파이썬은 가비지 콜렉션을 위해 reference counting을 사용하는데, 여러 개의 쓰레드가 reference 수를 동시에 변경하면 reference leak이 발생함. 그러한 부분을 막기 위해서는 공유되는 모든 객체에 lock을 걸어 reference를 추적해야 하는데, 이것보다는 interpreter에 lock을 걸어서 오직 1개의 쓰레드만 reference count를 변경할 수 있도록 하는 것이 더 쉽고 싱글쓰레드 상에서 빠름.

     

    CPU-bound면 매우 문제가 되나 대부분 프로그램이 IO-bound이고, 기존에는 쓰레드별로 균등하게 lock time을 분배했으나 Python 3.2에서는 쓰레드의 GIL acquisition request 수에 따라(CPU-bound, IO-bound 작업이 동시에 존재할 때 CPU-bound에 더 배분하기 위해) 분배함. 

     

    CPython 말고 다른 구현체도 존재하니 대안으로 사용할 수 있고 (PyPy도 GIL을 달고 있긴 함 [20])

     

     

    Filter & HandlerInterceptor

    Filter: webserver의 일부로 Spring 프레임워크에 속하지 않음. filter를 사용해 request를 조작하거나 servlet에 닿는 것을 막을 수도 있음. Coarse-grained tasks인 인증, 로깅 및 auditing, image와 data 압축, Spring MVC와 동떨어진 기능에 적절.

    HandlerInterceptor: Spring MVC 프레임워크의 일부로 DispatcheerServlet과 Controller 중간에 위치함. 애플리케이션 로깅과 같은 cross-cutting concerns, 상세한 인가 체킹, Spring context나 model 조작에 적절.

    Image from [21]

     

    Polymorphism이란? [22]

    Consequently, you can write programs that expect an object with a particular interface, knowing that any object that has the correct interface will accept the request. Moreover, dynamic binding lets you substitute objects that have identical interfaces for each other at run-time. This substitutability is known as polymorphism.

     

     

    Java primitive, reference type의 memory location

    Image from [23]

     

    @Transactional 내부구조? [24]

    So when you annotate a method with @Transactional, Spring dynamically creates a proxy that implements the same interface(s) as the class you're annotating. And when clients make calls into your object, the calls are intercepted and the behaviors injected via the proxy mechanism.

     

    As you observed, though, the proxy mechanism only works when calls come in from some external object. When you make an internal call within the object, you're really making a call through this reference, which bypasses the proxy. There are ways of working around that problem, however. I explain one approach in this forum post in which I use a BeanFactoryPostProcessor to inject an instance of the proxy into "self-referencing" classes at runtime. I save this reference to a member variable called me. Then if I need to make internal calls that require a change in the transaction status of the thread, I direct the call through the proxy (e.g. me.someMethod().)

     

     

    Java hashmap 구조?

    Hash Table로 Chaining을 사용해 구현됨 [25]

     

     

    C

    Logging을 Plain Java로 직접 구현한다고 하면 어떻게 구현하겠는가?

    질문의도를 파악하다 오래 걸림. 프레임워크를 떠나서 본질적인 CS 기반에서 많이 생각하라고 조언 받음.

     

    대략적인 답변은 "메모리에 쓰고, 주기적으로 파일로 쓴다, 그러한 처리는 추가적인 쓰레드로 구현하고 Spring Controller가 싱글톤이니 하나의 static 멤버로 큐를 두고 가져가는 형태로 구현하겠다" 정도.

     

     

    64 bit 숫자 데이터를 CRD하는 앱을 만든다고 하면 자료구조는 무엇을 쓰고 어떻게 구현하겠는가?

    외운 답을 뱉지 말고, 하나씩 구체적으로 구현하는 레벨로 설명해줄 것을 당부받음. 해시 테이블은 떠올랐으나 원하는 답변을 찾기까지 추가적인 질문이 계속됨. 무슨 해시 함수 쓸지 말하다가 실수해서 좀 더 자세히 공부할 필요가 있다고 느낌 [26].

     

    해시 테이블에 추가질문이 들어와 Chaining, Open Addressing 등을 이야기 하다가,

    - skew가 있어서 O(N)되면 어쩌냐? -> Universal hashing하면 줄어든다고 말하고 싶었으나 정확하게 말하기 힘들 것 같아 패스

    - Table doubling을 하면 4기가에서 8기가로 doubling하면 느리지 않냐? -> 조금씩 커지면 1.2, 1.3배씩 키우겠다고 답변했다가 원하는 답변이 아니라고 느낌

     

    우여곡절 끝에 double hashing을 떠올리고, (dynamic) perfect hashing을 떠올리고 답변

     

     

    Struts, DWR, Spring 비교?

    주요 특징은 아래 표를 통해 비교해 볼 수 있습니다 [33, 34]:

      Spring Struts DWR
    정의 IoC와 DI를 구현하기 위해 사용되는
    애플리케이션 프레임워크
    Java Servlet API와 MVC 프레임워크를
    확장하기 위해 사용되는 프레임워크
    East Ajax for Java
    Java와 JavaScript의 쉬운 연동을 위한 Java 라이브러리
    아키텍쳐 레이어드 아키텍쳐 -  
    프레임
    워크
    lightweight heavyweigth heavyweigth
    연동 ORM, JDBC 기술 등 연동은 되나 매뉴얼 코딩이 필요함 Spring, Struts, Hibernate 등
    커플링 loose tight tight

     

    Struts workflow - Image from [34]

     

     

    Struts를 Spring으로 변경한 이유는?

    전체적으로 일관된 프레임워크를 사용해 복잡도를 줄임, 테스트가 용이하도록.

     

     

     

    리팩토링을 진행하며 기술적으로 챌린징했던 점은?

    질문 시 원하는 것이 "엄청난 트래픽으로 발생한 문제를 탁월한 기술력으로 해결"과 같은 기술적 난제 해결 사례라고 느꼈으나 사실 그런 것은 없고 파악하기 힘든 히스토리, 알 수 없는 담당자, 레거시가 많았기에 기술적으로 조금 포장해서 말하다가 빙빙 돔. 

     

     

    D

    Map에서 해시 기반과 트리 기반 구현의 차이 [35, 36, 37]

    Java HashMap은 AbstractMap class를 extend하며 Map interface를 implement함. Map 구현체는 보통 bucketed hash table로 동작하나 buckets이 너무 커지면 TreeNodes의 노드들로 변환되어 각각의 구조가 java.util.TreeMap과 유사해짐. 

     

    TreeMap은 AbstractMap class를 extend하고 NavigableMap interface를 implement함. TreeMap은 요소들을 Red-Black tree에 저장함(Self-Balancing Binary Search Tree). 

     

      HashMap TreeMap
    inheritance  extends AbstractMap
    implement Map
    extends AbstractMap
    implement NavigableMap
    order 보장하지 않음 보장
    Null 한 개의 null key와 여러 null 값 가능 null 허용 안함 (compareTo()가 NullPointerException을 발생시키기 때문)
    성능 add(), remove(), contains() 모두 O(1)
    그러나 memory overhead(다수의 버킷 미사용 시), 성능저하(collisions 많을 때) 발생 가능함.

    Java 8 전에는 Collisions을 위해 Chaining을 사용했으나, JEP 180에서 다수(TREEIFY_THRESHOLD를 넘길 때) Collisions 발생 시에 TreeMap과 유사한 구조로 변경을 통해 최악의 성능 O(n)을 O(log n)이 되도록 함.
    add(), remove(), contains()에 대해 일반적으로 O(log n)의 성능을 제공.
    HashMap 대비 memory를 아낄 수 있음.

     

     

    hash collision?

    두 개의 다른 데이터의 해시 값이 같을 때를 말함. 

     

     

    Resolution 방법 [36] 

    • Chaining
    • Open Addressing
      • Linear probing
      • Quadratic probing
    • Dynamic resizing
    • Double hashing

     

    Java List와 Stream의 차이

    Stream은 아래와 같은 부분에서 Collections과 다르다 [37]:

    • No storage: 스트림은 요소를 저장하는 데이터 스트럭쳐가 아님. 대신 컴퓨터 연산의 파이프라인을 통해 데이터구조, 배열, generator function, I/O 채널 등의 원천에서부터 데이터를 '나르는' 기능을 함. 
    • Functional: 스트림에 대한 연산은 결과물을 만드나 원천의 값을 변경하지 않음. 
    • Laziness-seeking: 필터링, 매핑, 중복제거와 같은 스트림 연산은 최적화를 위해 lazy하게 구현될 수 있음. 
    • Possibly-unbounded: collections은 finite size를 가지는 반면 스트림은 그렇지 않음. limit(n)이나 findFirst()와 같은 short circuiting 연산들은 infinite 스트림에 대한 컴퓨팅을 finite 시간 안에 처리할 수 있도록 함. 
    • Consumable:  스트림의 요소들은 스트림이 존재하는 동안 단 1번만 방문(visit)될 수 있음. Iterator와 같이 원천의 같은 요소를 다시 방문하려면 새로운 스트림이 생성되어야 함. 

     

     

     

    HDFS를 CAP theorem에 기반해 설명하면? [38]

     

    HDFS는 일관성(Consistency)과 Partition tolerance를 지원하고, Availability 부분에 trade-off가 존재함.

     

    Consistency

    HDFS 문서 상에 아래와 같은 내용들은 HDFS가 일관성을 가지고 있다는 사실을 잘 보여줌

    "Create. Once the close() operation on an output stream writing a newly created file has completed, in-cluster operations querying the file metadata and contents MUST immediately see the file and its data."

    "Update. Once the close() operation on an output stream writing a newly created file has completed, in-cluster operations querying the file metadata and contents MUST immediately see the new data.


    "Delete. once a delete() operation on a path other than “/” has completed successfully, it MUST NOT be visible or accessible. Specifically, listStatus(), open() ,rename() and append() operations MUST fail."

     

    Availability

    문서 상의 아래와 같은 내용은 Available하지 않다는 부분을 시사함. HA를 제공하나, 네임노드는 SPOF로 다운 시 파일 시스템에 대한 접근이 일시적으로 불가능해짐.

     

    "The time to complete an operation is undefined and may depend on the implementation and on the state of the system."

     

     

    Partition Tolerance

    Partition은 분산 시스템에서의 통신 단절을 말함 [39]. HDFS의 데이터노드 중 한 대의 노드가 다운되거나 사라져도 시스템은 영향을 받지 않고 운영 될 수 있음.

     

     

    OAuth를 잘 아는가?

    잘 모른다고 대답.

     

    접근 위임을 위한 open standard (authorization protocol). 주로 인터넷 사용자가 웹사이트나 애플리케이션이 다른 웹사이트에 존재하는 사용자의 정보에 비밀번호 없이 접근할 수 있도록 허용해주는 데에 사용됨. 

     

    High-level overview of Oauth 2.0 flow - Image from [40]

     

     

    Spring Security를 많이 써보았는가?

    많이 안써봤다고 대답.

     

    Java 애플리케이션에 인증가 인가를 제공함 [41]. 

     

     

     

    (Coding) List를 Java Array만으로 구현하기

    [42]

     

     

    메시지 서비스를 데이터베이스를 사용해 구현한다고 할 때, 대략적인 ERD는?

     

     

    A sample ERD - Image from [44]

     

    A sample - Image from [46]
    A sample - Image from [47]
    Image from [48]
    Image from [49]

     

     

    위 메시지 서비스를 Kafka를 사용해 구현한다면 어떠한 방식으로 구현될 것 같은가?

    Live Comment Function Architecture - Image from [43]
    Line Live Chat Architecture - Image from [50]

     

    Image from [51, 53]

     

     

    Line Messaging Architecture - Image from [52]

     

     

    Reference

    [1] https://docs.oracle.com/javadb/10.8.3.0/ref/rrefsqlj29840.html

    [2] https://kadensungbincho.tistory.com/62

    [3] https://tomcat.apache.org/tomcat-8.5-doc/config/http.html#Connector_Comparison

    [4] https://stackoverflow.com/a/39089983/8854614

    [5] http://www.allenlipeng47.com/blog/index.php/2020/01/05/timeouts/

    [6] https://softwareengineering.stackexchange.com/a/70717

    [7] https://kadensungbincho.tistory.com/58 

    [8] https://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying

    [9] https://aws.amazon.com/message-queue/benefits/

    [10] https://kadensungbincho.tistory.com/98

    [11] https://www.tutorialspoint.com/when-to-use-an-abstract-class-and-when-to-use-an-interface-in-java

    [12] https://byexample.xyz/java/8/default/

    [13] https://stackoverflow.com/a/479168/8854614

    [14] https://stackoverflow.com/a/479154/8854614

    [15] https://www.baeldung.com/aspectj

    [16] https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop

    [17] https://www.fatalerrors.org/a/proxy-pattern-and-concrete-implementation-of-spring-aop-programming.html

    [18] https://realpython.com/python-gil/

    [19] https://www.youtube.com/watch?v=Obt-vMVdM8s 

    [20] https://doc.pypy.org/en/latest/faq.html#does-pypy-have-a-gil-why

    [21] https://www.baeldung.com/spring-mvc-handlerinterceptor-vs-filter

    [22] Design Patterns Elements of Reusable Object-Oriented Software

    [23] https://stackoverflow.com/a/32049775/8854614

    [24] https://stackoverflow.com/a/1099284/8854614

    [25] https://www.geeksforgeeks.org/internal-working-of-hashmap-java/

    [26] https://kadensungbincho.tistory.com/122

    [27] https://stackoverflow.com/a/86021/8854614

    [28] https://www.geeksforgeeks.org/mutex-vs-semaphore/

    [29] https://blog.feabhas.com/2009/09/mutex-vs-semaphores-%E2%80%93-part-1-semaphores/

    [30] https://barrgroup.com/embedded-systems/how-to/rtos-mutex-semaphore

    [31] https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/How_the_Web_works

    [32] https://kadensungbincho.tistory.com/70

    [33] https://www.educba.com/spring-vs-struts/

    [34] https://netbeans.apache.org/kb/docs/web/quickstart-webapps-struts.html

    [35] https://www.baeldung.com/java-treemap-vs-hashmap

    [36] https://stackoverflow.com/a/31161193/8854614

    [37] https://stackoverflow.com/a/39432937/8854614

    [38] https://stackoverflow.com/questions/58796173/how-does-the-cap-theorem-apply-on-hdfs

    [39] https://www.ibm.com/cloud/learn/cap-theorem

    [40] https://en.wikipedia.org/wiki/OAuth

    [41] https://docs.spring.io/spring-security/site/docs/current/reference/html5/

    [42] https://www.geeksforgeeks.org/internal-working-of-arraylist-in-java/

    [43] https://tech.kakao.com/2020/06/08/websocket-part1/

    [44] https://vertabelo.com/blog/database-model-for-a-messaging-system/

    [45] https://dba.stackexchange.com/questions/268388/chat-conversation-history-entity-relationship-diagram

    [46] https://dba.stackexchange.com/questions/268388/chat-conversation-history-entity-relationship-diagram

    [47] https://stackoverflow.com/a/8351589/8854614

    [48] https://stackoverflow.com/a/6542556/8854614

    [49] https://stackoverflow.com/a/6425511/8854614

    [50] https://engineering.linecorp.com/en/blog/the-architecture-behind-chatting-on-line-live/

    [51] https://codetiburon.com/create-chat-app-like-whatsapp/

    [52] https://engineering.linecorp.com/en/blog/line-storage-storing-billions-of-rows-in-sharded-redis-and-hbase/

    [53] https://stackoverflow.com/a/29137325/8854614

     

     

    웹페이지 요청 후 응답을 받기까지... (feat. DHCP, UDP, IP, Ethernet, DNS, ARP, BGP, TCP, HTTP)

    4년차 자바 백엔드 기술 질문들 최근 '예상치 않게' 백엔드 기술 인터뷰에 참여하게 되었습니다. 당황한만큼 질문에 대한 응답도 제대로 하지 못했는데요. 이번 글에서는 기억에 남는 질문들을

    kadensungbincho.tistory.com

     

    반응형
Kaden Sungbin Cho