week 2 incheol - GANGNAM-JAVA/JAVA-STUDY GitHub Wiki

알고리즘

기술 문제

JAVA (장지수)

  • 자바에서 쓰레드를 구현하는 방법과 동기화되는 과정을 설명하시오.

    • 쓰레드 구현 방법

      1. Runnable 인터페이스를 구현한 클래스를 만든다. (run() 메서드로 구현한다)

        Runnable은 run( ) 이라는 단 하나의 메소드를 제공한다 Runnable 인터페이스로 구현한 클래스를 쓰레드로 바로 시작할 수는 없다. Thread 클래스의 생성자에 해당 객체를 추가하여 시작해주어야만 한다. 

      2. Thread 클래스를 상속받아 사용한다.

        Thread 클래스는 많은 메소드를 포함하고 있다. Thread 클래스를 상속하여 만든 클래스는 start()메소드를 바로 호출할 수 있다. 쓰레드를 시작하는 메소드는 start()이며, 쓰레드가 시작하면 수행되는 메소드는 run()이다.

    • 동기화 과정

      • ???
  • hashCode() 값이 모두 같다면 어떤 일이 벌어지나요? hashcode()와 equls()는 언제 사용하고 왜 사용하는지?

    hashcode()와 equals()는 무엇인가?

    • hashcode() : 객체를 식별할 하나의 정수값을 말한다.(동일성 비교에 사용)
    • equals() : 두 객체의 내용이 같은지 비교한다(동등성)
    • equals()를 오버라이드 하지 않는다면 인스턴스의 주소값을 비교한다. String 객체는 내부적으로 equlas() 메서드를 오버라이드 하고 있어서 우리가 안전하게 사용할 수 있다.

    hashCode()가 왜 필요한가?

    equals는 필드 전체를 비교해야 하므로 느리다. 그에 비해 해시값을 사용하면 일정 계산과 계산 결과의 비교만으로 끝나기 때문에 빠르게 객체 비교를 판별할 수 있다. 그렇기 때문에 HashMap이나 HashSet에서는 아래와 같은 로직을 사용한다.

    • 해시값으로 객체를 비교한다.
    • 해시값이 동일한 경우에 한해 euqals 메서드로 비교한다.

    https://i.imgur.com/dShPCEh.png

    HashMap이나 HashSet에서 hashcode가 동일하면 어떻게 값을 저장하는가?

    • jdk 8 버전부터는 hashcode index가 동일할 경우 linked-list를 사용했는데 jdk8 이후에는 사이즈가 커지면 red-black-tree를 사용하게 되면서 성능이 향상되었다.

    사이즈가 커지는 기준은 어떻게 되고 그 기준으로 정한 이유는 무엇인가?

    • linked list와 red-black-tree를 사용하는 기준은 동일한 index에 모인 entity 개수가 8보다 클 경우에 tree를 사용한다. 6개 보다 작을 경우엔 linked list를 사용하게 된다.
    • 6개와 8개 기준에 따라 내부 로직을 변경하는 이유는 사이즈에 따라 변환하는 성능상 부하를 줄이기 위한 목적으로 구분되었다.
  • 패러럴GC, G1GC의 차이점 설명해주세요

    Parallel GC

    JDK 8 이전에 기본으로 사용했던 GC로 Serial GC에서 멀티 스레드 방식으로 진화한 방식이다. Minor GC를 처리하는 스레드를 여러개로 늘려 좀 더 빠른 동작이 가능하게 한 방식이다.

    Serial GC 방식은 무엇인가?

    Serial GC는 Mark-Sweep-Compaction 알고리즘을 사용한다.

    Mark-Sweep-Compaction 방식이란?

    • 사용되지 않는 객체를 식발하는 작업(Mark)
    • 사용되지 않는 객체를 제거하는 작업(Sweep)
    • 파편화된 메모리 영역을 앞에서부터 채워나가는 작업(Compaction)

    G1 GC

    JDK 8부터는 G1 GC를 기본으로 설정되어 있다. GC의 대상 영역이 여러개의 region으로 나뉘어 있기 때문에 GC가 일어나면 전체 heap에 대해서 GC를 하지 않고 일부 region에서만 GC를 수행한다.

    전체 heap에 대해서 GC가 일어나지 않고 일부 region에서만 동작하기 때문에 큰 heap을 가질 경우 유리하다.

SPRING (이경희)

  • Spring의 AOP의 특징과 어드바이스 종류와 사용된 proxy 패턴에 대해 설명해보세요.

    AOP란 무엇인가?

    AOP는 공통의 관심사(aspect)를 추상화해 잘 보관하고 있다가 필요한 곳에 동적으로 삽입하여 부가기능을 구현해주는 기술이다.

    스프링에서는 공통의 관심사를 추상화해 보관하고 있는 객체를 어드바이스(Advice)라고 하고, 이 부가 기능이 타깃 구현체에서 적용될 시점을 포인트컷(PointCut)이라 부른다.

    스프링은 AOP를 구현하기 위해 프록시 객체를 생성한다. 프록시 객체를 만드는 방법에는 두 가지 방식이 사용된다.

    • JDK Dynamic Proxy : java.lang.reflect를 사용하는 방식이다. 인터페이스에 대해서만 프록시를 객체를 만들 수 있으므로 concreate class에서는 사용할 수 없다.
    • CGLIB : Code Generator Library의 약자로 개발자가 작성한 빈 클래스를 상속받아 프록시 객체를 만드는 것이다.
      • 클래스에서도 AOP를 적용할수 있어서 장점이 있다.
      • Final class에서는 사용할 수 없다.

    어드바이스 종류는 무엇이 있을까?

    • @Around : 타켓의 메서드가 호출되지 이전과 이후 시점에 모두 처리해야 할 때 사용한다.
    • @Before : 타겟의 메서드가 실행되기 이전 시점에 사용한다.
    • @AfterReturning : 타겟의 메서드가 정상적으로 실행된 이후 시점에 사용한다.
    • @AfterThrowing : 타겟의 메서드가 예외를 발생된 이후 시점에 사용한다.
    • @After : 타겟의 메서드가 예외 또는 정상적으로 실행된 경우 두 케이스 모든 시점에 사용한다.

    프록시 패턴이란 무엇일까?

    AOP에서 사용하는 프록시와 디자인 패턴의 프록시 패턴은 차이가 있다.

    • 프록시 : 클라이언트와 사용 대상 사이에 대리 역할을 맡은 오브젝트 (프록시는 프록시 패턴과 데코레이터 패턴을 모두 적용하고 있다)
    • 프록시 패턴 : 타깃에 대한 접근 방법을 제어하려는 목적

    프록시 패턴과 데코레이터 패턴은 어떻게 구분할 수 있을까?

    사용의 목적이 기능의 부가인지, 접근 제어인지를 구분해보면 각각 어떤 목적으로 프록시가 사용됐는지, 그에 따라 어떤 패턴이 적용됐는지 알 수 있다.

    • 접근 제어 → 프록시 패턴
    • 부가 기능 → 데코레이터 패턴
  • 스프링에서 @Transactional은 어떻게 동작하며 어떤 옵션이 있는가?

    2. 동작 원리

    @Transactional 어노테이션을 기준으로 설명하겠다.

    트랜잭션은 Spring AOP를 통해 구현되어있다.

    더 정확하게 말하면, 어노테이션 기반 AOP를 통해 구현되어있다. (import문을 보면 알 수 있다)

    import org.springframework.transaction.annotation.Transactional;
    

    따라서, 아래와 같은 특징이 있다

    • 클래스, 메소드에 @Transactional이 선언되면 해당 클래스에 트랜잭션이 적용된 프록시 객체 생성
    • 프록시 객체는 @Transactional이 포함된 메서드가 호출될 경우, 트랜잭션을 시작하고 Commit or Rollback을 수행
    • CheckedException or 예외가 없을 때는 Commit
    • UncheckedException이 발생하면 Rollback

    3. 주의점

    1) 우선순위

    @Transactional은 우선순위를 가지고 있다.

    클래스 메서드에 선언된 트랜잭션의 우선순위가 가장 높고, 인터페이스에 선언된 트랜잭션의 우선순위가 가장 낮다.

    클래스 메소드 -> 클래스 -> 인터페이스 메소드 -> 인터페이스
    

    따라서 공통적인 트랜잭션 규칙은 클래스에, 특별한 규칙은 메서드에 선언하는 식으로 구성할 수 있다.

    또한, 인터페이스 보다는 클래스에 적용하는 것을 권고한다.

    • 인터페이스나 인터페이스의 메서드에 적용할 수 있다.
    • 하지만, 인터페이스 기반 프록시에서만 유효한 트랜잭션 설정이 된다.
    • 자바 어노테이션은 인터페이스로부터 상속되지 않기 때문에 클래스 기반 프록시 or AspectJ 기반에서 트랜잭션 설정을 인식 할 수 없다.

    2) 트랜잭션의 모드

    @Transactional은 Proxy Mode와 AspectJ Mode가 있는데 Proxy Mode가 Default로 설정되어있다.

    Proxy Mode는 다음과 같은 경우 동작하지 않는다.

    • 반드시 public 메서드에 적용되어야한다.
      • Protected, Private Method에서는 선언되어도 에러가 발생하지는 않지만, 동작하지도 않는다.
      • Non-Public 메서드에 적용하고 싶으면 AspectJ Mode를 고려해야한다.
    • @Transactional이 적용되지 않은 Public Method에서 @Transactional이 적용된 Public Method를 호출할 경우, 트랜잭션이 동작하지 않는다.
  • 스프링에서 인증과 인가를 처리하는 방식에 대해 설명해보세요.

    https://k.kakaocdn.net/dn/cAp74D/btqAWyBRZsE/Lk6EL0R680ykd45G6A5rK1/img.png

    위 그림의 동작 플로우를 간단히 설명하면 다음과 같습니다.

    1. 사용자가 로그인 정보와 함께 인증 요청(Http Request)

    2. AuthenticationFilter가 이 요청을 가로챕니다. 이 때 가로챈 정보를 통해 UsernamePasswordAuthenticationToken이라는 인증용 객체를 생성합니다.

    3. AuthenticationManager의 구현체인 ProviderManager에게 UsernamePasswordAuthenticationToken 객체를 전달합니다.

    1. 다시 AuthenticationProvider에 UsernamePasswordAuthenticationToken 객체를 전달합니다.

    2. 실제 데이터베이스에서 사용자 인증정보를 가져오는 UserDetailsService에 사용자 정보(아이디)를 넘겨줍니다.

    3. 넘겨받은 사용자 정보를 통해 DB에서 찾은 사용자 정보인 UserDetails 객체를 만듭니다. 이 때 UserDetails 는 인증용 객체와 도메인용 객체를 분리하지 않고 인증용 객체에 상속해서 사용하기도 합니다.

    4. AuthenticationProvider는 UserDetails를 넘겨받고 사용자 정보를 비교합니다.

    5. 인증이 완료되면 권한 등의 사용자 정보를 담은 Authentication 객체를 반환합니다.

    6. 다시 최초의 AuthenticationFilter에 Authentication 객체가 반환됩니다.

    7. Authentication 객체를 SecurityContext에 저장합니다.

    최종적으로 SecurityContextHolder는 세션 영역에 있는 SecurityContext에 Authentication 객체를 저장합니다. 세션에 사용자정보를 저장한다는 것은 스프링 시큐리티가 전통적인 세션-쿠키 기반의 인증 방식을 사용한다는 것을 의미합니다.

DATABASE/SQL (정인철)

  • SQL의 HINT를 설명해주세요.

    SQL 힌트란?

    힌트는 SQL 튜닝의 핵심 부분으로 일종의 지시구문이며 SQL에 포함되어 쓰여져 Optimizer의 실행계획을 원하는 대로 바꿀수 있게 해준다. 디비의 Optimizer 라고 해서 항상 최선의 실행계획을 수립할 수는 없으므로 테이블이나 인덱스의 잘못된 실행 계획을 개발자가 직접 바꿀 수 있도록 도와주는 것이 HINT 이다.

    힌트의 특징 네가지

    • 실행 계획을 제어한다.
    • 에러가 발생하지 않는다. (힌트의 시작 /**/ 은 문법에 맞게 작성되어야 한다.)
    • 선택 또는 취소 될 수 있다. (힌트의 문법이 올바르더라도 힌트는 옵티마이저에 의해 버려질 수도 있고 선택되어질 수도 있다.)
    • 다양한 종류의 힌트가 있으며 버전업이 될때마다 계속 추가된다.

    힌트의 종류 (MYSQL 기준)

    • STRAIGHT_JOIN : 여러 테이블 조인시 조인순서를 from의 순서대로 Access
    • USE INDEX / FORCE INDEX / IGNORE INDEX : 인덱스 사용/강제/무시
    • SQL_CACHE / SQL_NO_CACHE : 조회된 결과를 재사용하기 위해 쿼리 캐시에 선택적으로 저장할 수 있다.
    • SQL_CALC_FOUND_ROWS : SQL_CALC_FOUND_ROWS 힌트를 사용하는 경우 limit 절과는 상관없이 조건에 일치하는 모든 레코드를 검색해 결과 레코드가 몇건이나 되는지 계산한다. 그렇치만 사용자 에게는 Limit 절에 제한된 건수 만큼만 레코드를 반환한다.
    • FOUND_ROWS : FOUND_ROWS는 직전 쿼리에서 검색된 결과 row 수를 반환한다.
  • memcached보다 redis의 공통점과 차이점은?

    캐시를 사용하는 이유?

    일단 Cache는 “많은 시간이나 연산이 필요한 일에 대한 결과를 저장해 두는 것” 이라고 할 수 있다. 속도면에서는 각각의 서버의 메모리에 들고 있는 것이 유리합니다. 그런데, 여러 서버에 있는 데이터를 동기화 하는 것은 사실 쉬운 일이 아닙니다. 그리고 데이터량이 많으면, 결국은 한 서버에 둘 수 없어서, 여러 서버로 나뉘어야 합니다.

    쿠키와 세션은 무엇이지?

    쿠키

    사용자의 브라우저에 저장되고, 통신할 때 HTTP 헤더에 포함되는 텍스트 데이터 파일

    이름, 값 만료기간(지정 가능), 경로 정보가 있고 키와 값으로 구성되어 있다

    해당 사용자의 컴퓨터를 사용한다면 누구나 쿠키에 입력된 값을 쉽게 확인 가능 -> 보안성이 낮다!

    세션

    서버'에 저장되는 쿠키. 클라이언트와 서버의 통신 상태. 주로 중요한 데이터를 저장 시 사용

    브라우저를 종료할 때까지 유지 됨

    사용자 로컬이 아닌 서버에 직접 저장되므로, 세션 내의 데이터를 탈취하는 것은 어려움 -> 보안성이 비교적 높음

    Redis는 무엇인가?

    • 자료구조가 다양합니다. String, Set, Sorted Set, Hash List 등 다양한 자료구조를 제공합니다.
    • 현재는 JSON 타입에 대해서도 지원한다.
    • 메모리 뿐만 아니라 디스크도 사용하기 때문에 데이터 복구시 유용하다.
    • 싱글 스레드를 사용한다.
    • 다양한 Eviction 정책을 통해 세밀한 Eviction 제어가 가능하다

    왜 Redis에서 실제 사용하는 메모리보다 더 많은 메모리를 요구할까?

    • Redis의 Copy on Write가 가장 기본적인 이유
    • 리눅스에서는 자식 프로세스 생성에 대해 메모리 공간을 공유한다.
    • 하지만 부모 프로세스가 데이터를 넣거나, 수정하거나, 지우게 되면 메모리 공간을 공유할 수 없게된다. 이때 부모 프로세스는 해당 페이지를 복사한 다음 수정한다.
    • 그렇기 때문에 Redis에서는 실제 사용하는 메모리보다 임시로 데이터를 복사하고 수정하기 위해 더 많은 메모리 영역을 필요로 하는 것이다.

    Memcached는 무엇인가?

    • 멀티스레드 아키텍처를 지원한다.
    • 참고한 블로그에 "Single Thread인 Redis에 비해 Memcached는 Multi Thread를 지원하기 때문에 서버 Scale up에 유리하다"라고 나와있는데, 멀티스레드와 스케일업의 상관관계에 대해서 좀 더 생각해볼 필요성이 있겠다.
    • Redis 보다 적은 메모리를 요구한다.
    • Redis와 Memcached 사이에 기본적으로 데이터를 저장하고 캐싱하는 방식에 차이가 있는 것 같다. Memcached는 정적인 데이터 저장에 유리하다. Redis는 Copy-on-Write 방식을 사용하기 때문에 실제 사용하는 메모리보다 더 많은 메모리를 요구한다.

    Redis와 Memcached 장단점

    Redis > Memcached

    1. 다양한 자료구조를 지원합니다.

    String만 지원하는 Memcached에 비해 Redis는 더욱 다양한 자료구조를 지원하여 더 다양한 타입의 자료를 저장할 수 있습니다. 이는 Memcached에 비해 Redis가 가지는 강력한 장점 중 하나입니다.

    2. 데이터 복구가 가능합니다.

    프로세스의 돌발 종료, 서버 종료 등 돌발 상황에서 Memcached는 모든 데이터가 유실되지만, Redis는 데이터를 Disk에도 저장하기 때문에 메모리에서 유실된 데이터를 복구할 수 있습니다.

    3. 다양한 Data Eviction 정책을 지원합니다.

    Memcached는 LRU 알고리즘을 통한 Eviction을 지원합니다. 하지만 Redis는 6가지 Eviction정책을 통해 더욱 세밀한 Eviction 제어가 가능합니다.

    Memcached > Redis

    1. 멀티스레드를 아키텍처를 지원합니다.

    Single Thread인 Redis에 비해 Memcached는 Multi Thread를 지원하기 때문에 서버 Scale up에 유리합니다.

    2. Redis에 비해 적은 메모리를 요구합니다.

    HTML과 같은 정적인 데이터를 캐싱하는 것에는 Memcached가 유리합니다. Redis는 Copy&Write 방식을 사용하기 때문에 실제 사용하는 메모리보다 더 많은 메모리를 요구합니다.

  • 트랜잭션 격리 수준 (Isolation Level) 에 대해서 설명하세요.

    트랜잭션이 보장해야 하는 ACID

    • 원자성 (Atomicity) : 한 트랜잭션 내에서 실행한 작업들은 하나의 작업으로 간주한다. 모두 성공 또는 모두 실패되어야 한다.
    • 일관성 (Consistency) : 모든 트랜잭션은 일관성 있는 데이타베이스 상태를 유지한다. 이를테면 DB에서 정한 무결성 조건을 항상 만족.
    • 격리성 (Isolation) : 동시에 실행되는 트랜잭션들이 서로 영향을 미치지 않도록 격리 해야한다.
    • 지속성 (Durability) : 트랜잭션을 성공적으로 마치면 그 결과가 항상 저장되어야 한다.

    격리성 관련 문제점

    (1) Dirty Read

    한 트랜잭션(T1)이 데이타에 접근하여 값을 'A'에서 'B'로 변경했고 아직 커밋을 하지 않았을때, 다른 트랜잭션(T2)이 해당 데이타를 Read 하면?

    T2가 읽은 데이타는 B가 될 것이다. 하지만 T1이 최종 커밋을 하지 않고 종료된다면, T2가 가진 데이타는 꼬이게 된다.

    (2) Non-Repeatable Read

    한 트랜잭션(T1)이 데이타를 Read 하고 있다. 이때 다른 트랜잭션(T2)가 데이타에 접근하여 값을 변경 또는, 데이타를 삭제하고 커밋을 때려버리면?

    그 후 T1이 다시 해당 데이타를 Read하고자 하면 변경된 데이타 혹은 사라진 데이타를 찾게 된다.

    (3) Phantom Read

    트랜잭션(T1) 중에 특정 조건으로 데이타를 검색하여 결과를 얻었다. 이때 다른 트랜잭션(T2)가 접근해 해당 조건의 데이타 일부를 삭제 또는 추가 했을때, 아직 끝나지 않은 T1이 다시 한번 해당 조건으로 데이타를 조회 하면 T2에서 추가/삭제된 데이타가 함께 조회/누락 된다. 그리고 T2가 롤백을 하면? 데이타가 꼬인다

    트랜잭션 격리수준

    위와 같은 문제들 때문에, ANSI표준에서 트랜잭션의 격리성과 동시 처리 성능 사이의 Trade-off를 두고 4단계 격리수준을 나누었다. 내려갈수록 격리 수준이 높아져서 언급된 이슈는 적게 발생하지만 동시 처리 성능은 떨어진다.

    참고로, 트랜잭션이 발생하면 락(Lock)이 걸리는데, SELECT 시에는 공유 락, CREATE/INSERT/DELETE 시에는 배타적 락이 걸린다

    (1) Read Uncommitted

    한 트랜잭션에서 커밋하지 않은 데이타에 다른 트랜잭션이 접근 가능하다. 즉, 커밋하지 않은 데이타를 읽을 수 있다.

    이 수준은 당연히 위에서 언급한 모든 문제에 대해 발생가능성이 존재한다. 대신, 동시 처리 성능은 가장 높다.

    • 발생 문제점 : Dirty Read, Non-Repeatable Read, Phantom Read

    (2) Read Committed

    커밋이 완료된 데이타만 읽을 수 있다.

    Dirty Read가 발생할 여지는 없으나, Read Uncommitted 수준보다 동시 처리 성능은 떨어진다.  대신 Non-Repeatable Read 및 Phantom Read는 발생 가능하다.데이타베이스들은 보통 Read Committed를 디폴트 수준으로 지정한다.

    • 발생 문제점 : Non-Repeatable Read, Phantom Read

    (3) Repeatable Read

    트랜잭션 내에서 한번 조회한 데이타를 반복해서 조회해도 같은 데이타가 조회 된다

    이는 개별 데이타 이슈인 Dirty Read나 Non-Repeatable Read는 발생하지 않지만, 결과 집합 자체가 달라지는 Phantom Read는 발생가능하다.

    • 발생 문제점 : Phantom Read

    (4) Serializable

    가장 엄격한 격리 수준

    위 3가지 문제점을 모두 커버 가능하다. 하지만 동시 처리 성능은 급격히 떨어질 수 있다.