2022-04-22

  • Stream
    • 컬렉션 내부에서 요소들을 반복시키고, 개발자는 요소당 처리해야 할 코드만 제공하는 코드 패턴
    • 다량의 데이터를 순차적으로 혹은 병렬적으로 처리하기 위해 추가되었다.
    • 데이터를 추상화하여 다루기 때문에 다양한 형태로 저장된 데이터를 위한 공통된 방법을 제공한다
    • 특징
      • 외부 반복을 통해 작업하는 컬렉션과는 다르게 내부 반복을 통해 작업을 수행
      • 단 한 번만 사용 가능 (재사용 불가능)
      • 원본 데이터를 변경하지 않음
      • 병렬 처리 지원

2022-04-20

  • Abstract class vs Interface
    • Java8에서 추상 클래스와 같이 concrete method(body, 즉 구현부를 갖는 method)를 가질 수 있어 추상 클래스와 인터페이스의 경계가 모호해진 듯 하지만, 차이가 존재한다.
    • 공통점
      • 인스턴스화가 불가능하여 abstract class를 상속 받은 자식 클래스로 객체를 생성하거나 interface를 구현한 클래스로 객체를 생성해야 한다
      • 추상 메서드를 가질 수 있다.
      • 상속을 받은 자식 클래스 또는 구현한 클래스를 상위 타입으로 묶을 수 있다.
    • 차이점
      • interface의 목적은 full abstraction(전부 추상화)의 제공이고, abstract class의 목적은 partial abstraction(일부 추상화)의 제공이라는 점은 아직 유효하다. interface의 default method는 이미 interface를 구현하고 있는 class에 영향을 끼치지 않고 추가하는 데에 의의가 있다.
      • interface는 생성자가 없으나, abstract class는 생성자를 가질 수 있어 상속한 concrete class의 객체가 생성될 때, super인 abstract class의 생성자가 호출될 수 있다.
      • abstract class는 멤버 변수를 가질 수 있으나, interface는 상수(public static final)만 가질 수 있다. (interface의 상수는 anti-pattern이라고 한다 - https://dwenn.tistory.com/107)
      • abastract class는 이용, 확장하는데에 있고, interface는 인터페이스 함수의 구현을 강제하여 객체의 같은 동작을 보장하는데에 있다.
    • https://nathanh.tistory.com/126

2022-04-17

  • static method의 추가
    public interface Vehicle {
      void run();
      static String producer() {
        return "XXX Vehicles";
      }
    }	
    
    String producer = Vehicle.producer();
    
    • why static method?
      • 인터페이스를 구현하는 클래스에서 override를 할 수 없다는 점 이외에는 default method와 비슷하다

2022-04-16

  • 인터페이스의 모든 메서드는 default로 public이면서 abstract였는데, java8(JDK8) 부터 default와 static method가 추가되었다.
  • Java8(JDK8) - Interface에 default method 추가
    public interface Vehicle {
      void run();
      default String producer() {
        return "XXX Vehicles";
      }
    }
    
    Vehicle vehicle = new VehicleImpl();
    String producer = vehicle.producer();
    
    • why default method?
      • 기존에 이미 존재하는 인터페이스에 abstract method를 추가하고 싶은데, 해당 인터페이스를 구현하는 클래스들이 너무 많아 그 많은 클래스 모두에 method를 구현하는 것은 어렵다.
      • 인터페이스에 default method를 추가해도 구현 강제가 없기 때문에 컴파일 오류가 발생하지 않는다.
    • WebMvcConfigureAdapter vs WebMvcConfigurer
      • WebMvcConfigureAdapter는 추상 클래스이고, WebMvcConfigurer는 interface이다.
      • 기존에 있던 WebMvcConfigureAdapter는 deprecated 되고 WebMvcConfigurer가 추가되었다.
      • Java8 default method의 추가로 interface가 WebMvcConfigureAdapter의 기능 커버할 수 있게 되었던 것이다.
        // before
        public class WebMvcConfig extends WebMvcConfigurerAdapter
              
        // after
        public class WebMvcConfig implements WebMvcConfigurer
        
      • 참고 : https://stackoverflow.com/a/47553586

2022-04-15

  • HikariCP dead lock
    • 하나의 Task에서 동시에 요구되는 connection 개수가 2개였다
      • transaction에서 수행되는 connection 하나와 MySQL에서 @GeneratedValue(strategy = GenerationType.AUTO) 을 수행하기 위한 connection 하나
      • MySQL은 Sequence를 지원하지 않기 때문에 hibernate_sequence라는 테이블에 단일 Row를 사용하여 ID값을 생성하게 된다. 이때, hibernate_sequence 테이블을 조회, update를 하면서 Sub Transaction을 생성하여 실행하게 될 때 connection이 필요했다
    • 데드락을 피할 수 있는 최소한의 $pool size = Tn \times (Cm - 1) + 1$
      • $Tn$ : 전체 thread 수
      • $Cm$ : 하나의 Task에서 동시에 필요한 Connection 수
    • https://techblog.woowahan.com/2663/

2022-04-13

  • linux에서 파일명 일괄 변경
    # 현재 directory에서 png 확장자를 가진 파일명에서 'before'를 'after'로 일괄 변경
    find . -type f -name '*png' | while read FILE ; do
      newfile="$(echo ${FILE} |sed -e 's/\ before/\ after/')" ;
      mv "${FILE}" "${newfile}" ;
    done
    

2022-04-12

  • 가상 메모리 구조는 논리적인 선형 어드레스를 물리적인 물리 어드레스로 변환하는 것 (가상 메모리 ≠ 스왑)
  • 스왑은 가상 메모리를 응용한 기능 중 하나로, 물리 메로리가 부족할 때 2차 기억장치(주로 디스크)를 메모리로 간주해서 외형상의 메모리 부족을 해소하는 원리이다.
  • 가상 메모리 구조가 존재하는 가장 큰 이유는 물리적인 하드웨어를 OS에서 추상화하기 위해서다.

2022-04-11

  • AP서버는 CPU부하만 걸리므로 분산이 간단하다.
  • I/O 부하에는 문제가 있다. DB를 추가로 놓는다고 하면 여러 DB 간의 데이터 sync 문제가 발생한다. 쓰기는 간단히 분산할 수 없다.
  • CPU에 부하를 주는 프로그램을 CPU 바운드한 프로그램, AP 서버는 DB로부터 얻은 데이터를 가공해서 클라이언트로 전달하는 처리를 수행하기 때문에, 대규모 I/O를 발생시키는 일이 드물다. 따라서 많은 경우에 AP 서버는 CPU 바운드한 서버 이다.
  • DB 서버는 데이터를 디스크로부터 검색하는 것이 주된 일로, I/O에 대한 영향이 커지는 I/O 바운드한 서버이다.

2022-04-07

  • 메모리와 디스크의 탐색 속도 차는 $10^5(10만) \sim 10^6(100만)$배 차이 난다
  • 메모리와 디스크의 (버스를 통한) CPU까지의 전송 속도 차는 100배 이상이다

2022-03-29

  • Map의 value로 List를 갖는 경우, map의 원소 추가 및 원소인 list의 원소 추가 방법
    String userId = "hoon";
    String friendId = "woong";
    Map<String, List<String>> friendListMap = new HashMap<>();
    List<String> listPerUserId = friendListMap.computeIfAbsent(userId, k -> new ArrayList<>());
    listPerUserId.add(friendId);
    
  • int[]를 List로 변환하는 방법
    int[] arr = new int[N];
    List<Integer> list = Arrays.stream(arr).boxed()
                               .collect(Collectors.toList());
    
  • List를 Set으로 변경하는 방법
    List<String> list = new ArrayList<>();
    ...
      
    Set<String> set1 = new HashSet<>(list);
    
    Set<String> set2 = new HashSet<>(map.keySet()); // map의 keySet도 가능
    
  • Set을 List로 변경하는 방법
    Set<String> set = new HashSet<>();
    ...
      
    // 1
    List<String> list1 = new ArrayList<>(set);
      
    // 2
    List<String> list2 = new ArrayList<>();
    list2.addAll(set);
    
  • Map의 key를 List로 변경하는 방법
    Map<String, List<String>> map = new HashMap<>();
    ...
      
    // 1
    List<String> keyList = new ArrayList<>(map.keySet());
      
    // 2
    List<String> keyList2 = new ArrayList<>();
    keyList2.addAll(map.keySet());
    
  • Map의 value들을 List로 변경하는 방법
    Map<String, List<String>> map = new HashMap<>();
    ...
      
    List<String> valuesList1 = new ArrayList<>(map.values());
    
  • Map의 entrySet()
    Map<String, List<String>> map = new HashMap<>();
    ...
      
    for (Map.Entry<String, List<String>> entry : map.entrySet()) {
      System.out.println("key : " + entry.getKey());
      System.out.print("values : ");
      for (String element : entry.getValue()) {
        System.out.print(entry.getKey() + " ");
      }
    }
    

2022-03-27

  • CPU bound vs I/O bound
    • 프로그램의 처리 속도가 CPU의 계산 속도에 의존하는, CPU에 부하를 주는 프로그램을 CPU 바운드한 프로그램이라고 한다.
      • 일반적으로, AP(Application) Server가 해당된다
    • 프로그램의 처리 속도가 디스크 입출력(Input/Output, I/O) 속도에 의존하는, I/O에 부하를 주는 프로그램을 I/O 바운드한 프로그램이라고 한다.
      • DB 서버는 데이터가 대규모가 될수록 CPU에서의 계산 시간보다도 I/O에 대한 영향을 많이 받게 된다.

2022-03-17

  • GC Root는 말그대로 GC의 root로, heap 외부에서 접근할 수 있는 변수나 오브젝트를 뜻한다. GC Root에서 시작해 이 Root가 참조하는 모든 오브젝트, 또 그 오브젝트들이 참조하는 다른 오브젝트들을 탐색해 내려가며 마크(Mark)한다. 이게 바로 가비지 컬렉션의 첫번째 단계인 Mark단계이다.

2022-03-16

  • 자바 메모리 누수가 발생하는 패턴
    • Autoboxing과 같이 Integer, Long 틍의 Wrapper 클래스를 이용하여 무의미한 객체를 생성하는 경우
    • map을 활용하여 cache로 쓸 데이터를 저장하고 제거하지 않는 경우
      • map에 계속 저장되어 참조되어 있는 상황이기 때문에 필요없으면 제거해주어야 한다
    • 자원(resources)을 사용하고 반납(close)하지 않은 경우
      • finally에 항상 자원을 반납(close)할 수 있도록 하거나 try with resources(try()) 구문을 사용한다
    • map, set 등의 key로 custom 객체를 사용하면서 equals(), hashCode()를 재정의하지 않아 동일하다고 생각됐던 데이터가 계속 쌓이게 되는 경우
    • map, set 등의 key로 custom 객체를 사용하면서 equlas(), hashCode()를 재정의 했지만, key값이 불변(Immutable) 데이터가 아니라서 데이터 비교시 계속 변하게 되는 경우
    • 자료구조를 생성하여 사용하면서, 구현 오류로 인해 메모리를 해제하지 않는 경우
    • 참고 : https://dzone.com/articles/memory-leak-andjava-code

2022-03-12

  • Redis의 원자성을 제공하는 대표적인 method, setIfAbsent()
    boolean isNewRequest = redisTemplate.opsForValue()
                             .setIfAbsent(name, "true", 1, TimeUnit.MINUTES);
    
    if (!isNewRequest) {
      log.info("동일 요청이 여러 번 발생");
      return;
    }
    

2022-03-11

2022-03-09

  • enum class의 abstract method
    public enum RewardCalculationType {
      FIXED_RATE {
        public BigDecimal calculate(BigDecimal value, BigDecimal amount) {
          return amount.multiply(value);
        }
      },
      FIXED_AMOUNT {
        public BigDecimal calculate(BigDecimal value, BigDecimal amount) {
          return value;
        }
      };
    
      abstract public BigDecimal calculate(BigDecimal value, BigDecimal amount);
    }
    

2022-03-08

  • JPQL에서 parameter로 객체를 받았을 때 field 적용 방법과 JPQL에서 enum 사용 방법
    @Query("select p from Policy p "
         + "where p.transactionType = :#{#trx.type} and "
         + "      p.minimumAmount <= :#{#trx.amount} and "
         + "      p.state = me.hoonti06.rewardsystem.enums.PolicyState.RUNNING")
    List<Policy> getSuitablePolicies(@Param(value = "trx") TransactionDto transactionDto);
    
  • enum 관련 기술 블로그

2022-03-07

  • convert char[] to String
    char[] chars = {'H', 'E', 'L', 'L', 'O'};
    String str = new String(chars);
    
  • @InjectMocks는 interface에 사용할 수 없다

2022-03-05

  • 영속성 컨텍스트
    • persist를 하면 1차 캐시에 저장된다
    • 1차 캐시는 하나의 트랜잭션과 생명 주기를 같이 한다 -> 짧다
    • 1차 캐시에 먼저 조회를 하고 없으면 DB 조회를 한다

2022-03-03

  • JPQL
    • 객체지향 쿼리
    • 경로 표현식
      • .(dot)을 통해 객체 그래프를 탐색
      • 연관 필드는 묵시적 내부 조인 발생(명시적 조인 사용을 권장)
    • 서브 쿼리
      • 한계
        • JPA는 WHERE, HAVING 절에서만 서브 쿼리 사용 가능
        • SELECT절도 서브 쿼리 가능(하이버네이트에서 지원)
        • FROM절의 서브 쿼리는 현재 JPQL에서 불가능
    • fetch join
    • JPQL에서 성능 최적화를 위해 제공하는 기능(SQL 조인 종류 X)
    • 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능
    • 엔티티에 직접 적용하는 global 로딩(eager, lazy) 전략보다 우선
    • ‘join fetch’ 명령어 사용
      • N+1 문제 해결

2022-03-02

2022-02-27

  • container registry인 treescale이 현재 운영이 안 되고 있고, 대체할 무료 플랫폼인 jfrog를 발견하게 되었다

2022-02-24

2022-02-17

  • 현업에서는 생각하는 것보다 log를 더 많이 남긴다고 한다
  • 다음과 같이 파일(특히, log)의 중간 라인을 출력할 수 있다
      sed -n '77358p,77370p' debug-2022-02-09 # 77358 ~ 77370 line 출력
    

2022-02-16

2022-02-15

  • 간단한 String 더하기(concat) 코드는 compiler에서 최적화를 해준다
  • JDK 8에서 String의 concat을 StringBuilder의 append()로 치환해주지만, 그것 또한 문제가 발생한다
      // 실제 코드
      String result = "";
      for (int i = 0; i < 1e6; i++) 
          result += "some data";
    		
      // JDK8에서 최적화된 코드
      String result = "";
      for (int i = 0; i < 1e6; i++) {
          StringBuilder tmp = new StringBuilder();
          tmp.append(result);
          tmp.append("some data");
          result = tmp.toString();
      }
    

2022-02-14

  • controller와 service 의존성을 끊어내야 한다
    • controller에서 request DTO의 경우 외부와 연결되어 있는 것으로, 해당 스펙은 바뀔 수 있다. 해당 스펙이 service에 영향을 줘선 안 된다.

2022-02-12

  • 여러 모듈(컴포넌트)로 나뉜 MSA 환경에서 분산 트랜잭션의 패턴이 2가지 있는데, 2-phase commit pattern과 saga pattern이다.
  • 2-phase commit(2PC)
    • 여러 모듈의 로컬 트랜잭션이 모두 준비(prepare)되면 그때 commit을 진행한다.
    • prepare와 commit, 2단계로 이뤄져 2-phase라고 불린다.
  • saga pattern
    • 트랜잭션 관리의 주체가 DB 서버가 아닌 여러 모듈로 나뉜 각각의 애플리케이션에 있으며, 각 애플리케이션 하위에 존재하는 DB는 자신의 트랜잭션만 처리하는 구조
    • Choreography-Based Saga, Orchestration-Based Saga, 이렇게 2가지 종류가 있다.
    • Choreography-Based Saga
      • 중앙 제어 서비스 없이 각 서비스마다 로컬 트랜잭션을 처리하며 이벤트 혹은 메시지를 통해 통합 트랜잭션 처리를 통해 데이터 정합성을 처리
      • 간단하게 구현할 수 있다
      • 서비스들이 어떤 메시지나 이벤트를 수신 대기하는 지 등 트랜잭션의 진행 상황을 알기 어렵다
    • Orchestration-Based Saga
      • 중앙 오케스트레이터가 각 서비스들의 통합 트랜잭션을 관리 하는 것으로, 트랜잭션 및 수행할 상태를 관리하여 통합 트랜잭션을 처리
      • Orchestrator 서비스가 필요하고 시스템이 복잡해질 수 있다

2022-02-11

  • OSIV 설정하면서 발생한 이슈
    • 설정해놓은 transaction 범위의 바깥에서 연관 관계의 entity 객체를 lazy loading하는 기존 상황에서, OSIV 설정 OFF를 하게 되면서 LazyInitializationException 발생
      • fetch join 적용을 통해 lazy loading하지 않도록 함
      • N+1 문제도 해결

2022-02-10

  • OSIV (Open-Session-In-View)
    • 영속성 컨텍스트가 트랜잭션 범위를 넘어선 레이어까지 살아있다
      • filter, interceptor, controller, view (영속 상태, 수정 불가능)
    • false일 경우 트랜잭션을 종료할 때 영속성 컨텍스트 또한 닫힌다
    • 영속성 컨텍스트를 유지한다는 건, DB Connection 또한 계속 가지고 있다는 뜻
      • DB connection 부족이 일어날 수 있다
    • 영속성 컨텍스트가 transaction 범위 바깥에서 Lazy loading을 시도하면 org.hibernate.LazyInitializationException: could not initialize proxy [kr.gracelove.osivdemo.domain.Team#1] - no Session 이 발생한다
      • 영속성 컨텍스트가 닫혀 있으면 lazy loading을 할 수 없다

2022-02-09

  • JPA N+1 문제
    • 연관 관계가 설정된 엔티티를 조회할 경우, 조회된 엔티티 수(N) 만큼 연관 관계에 대한 조회 쿼리가 추가로 수행되는 문제
    • FetchType이 EAGER 이든 LAZY 이든 시기만 다를 뿐, N회의 쿼리가 수행되는 건 동일하다

2022-02-08

  • -Dcom.zaxxer.hikari.housekeeping.periodMs=10000를 설정하면 10초를 주기로 DB connection pool의 상태가 debug level의 log로 출력된다(default : 30s).

2022-02-07

  • JPA 1차, 2차 캐시
    • 1차 캐시
      • 영속성 컨텍스트 내부의 엔티티를 보관하는 저장소를 1차 캐시라고 한다
      • 영속성 컨텍스트 범위의 캐시(트랜잭션 범위의 캐시, OSIV를 적용하면 요청 범위의 캐시)
      • 끄고 켤수 있는 옵션이 아님(영속성 컨텍스트 자체가 사실상 1차 캐시)
    • 2차 캐시
      • 애플리케이션에서 공유하는 캐시를 JPA는 공유 캐시(shared cache)라 하는데 일반적으로 2차 캐시(second level cache, L2 cache)라 부른다
      • 애플리케이션 범위의 공유 캐시
      • 애플리케이션이 종료될 때까지 유지

2022-02-04

  • 경과 시간 측정할 때 사용할 수 있는 method로 System.currentTimeMillis()System.nanoTime()가 있다

2022-02-03

2022-02-02

2022-01-30

  • 데이터가 오직 DB에서 온다면 java code로 filtering하는 것보다 query가 더 빠르다
  • CPU는 scale out이 쉽고, DB는 scale out이 어렵다
    • 복잡한 query보다는 단순한 query를 통해 DB 사용 시간을 줄이고, filtering 등의 작업은 java code(CPU)로 하는 것이 더 효율적일 수 있다.

2022-01-27

  • 비관적 lock vs 낙관적 lock
    • 비관적 lock
      • 충돌이 많이 발생할 것으로 예상하고 미리 공유 자원에 대해 선점을 하는 방식
      • 선점 과정 등으로 인해 속도가 느릴 수 있다
      • DB의 Query 중 SELECT FOR UPDATE로 구현할 수 있다
    • 낙관적 lock
      • 충돌이 거의 일어나지 않을 것으로 예상하고 충돌 발생 시 추가적인 조치를 취하는 방식
      • 특별한 과정이 추가되지 않기 때문에 비관적 lock에 비해 속도가 빠르다
      • Version을 통해 구현할 수 있다
    • https://jinhanchoi1.medium.com/비관적-lock-낙관적-lock-이해하기-1986a399a54

2022-01-26

  • @Transacional이 설정된 method는 외부에서 호출되어야 한다.
    • proxy 기반이기 때문에 내부 method에서 @Transactional method를 호출하게 되면 proxy 객체의 AOP가 설정된 method가 호출되지 못해서 원하는 동작을 수행할 수 없다.

2022-01-25

2022-01-24

  • @Transactional 을 method에 적용 시 해당 method는 private이면 안 되고 overridable 해야 한다.
    • Proxy로 작용하기 때문이다.
    • class에 @Transactional 을 적용하면 method가 private이어도 된다.

2022-01-23

2022-01-22

  • [[programmers-67258]]

2022-01-21

  • caching 관련 redis 활용에 대한 글
  • local cache vs global cache
    • local cache
      • 서버마다 cache가 존재
      • 각 서버의 resource를 사용
      • 속도가 빠름
      • 데이터 변경 시 모든 서버의 cache에 변경 사항이 전달되어야 함
      • 읽기 전용 데이터에 적합
    • global cache
      • 여러 서버에서 하나의 cache 서버에 접근
      • 서버 간의 데이터 공유가 쉽다
      • 네트워크를 거치기 때문에 local cache보다 속도가 느림

2022-01-20

2022-01-19

  • Redis transaction은 rollback이 없다. 대신, EXEC 실행 전 DISCARD할 수 있다

2022-01-18

  • Redis sentinel은 설정이 다르기 때문에 configuration 재 작성해야 함
      @Configuration
      @RequiredArgsConstructor
      public class RedisConfig {
    
          private final RedisProperties redisProperties;
    
          @Bean
              public RedisConnectionFactory redisConnectionFactory() {
                  RedisSentinelConfiguration redisSentinelConfiguration = 
                    new RedisSentinelConfiguration()
                        .master("192.168.219.15")
                        .sentinel("192.168.219.16", 7000)
                        .sentinel("192.168.219.17", 7000);
                  return new LettuceConnectionFactory(redisSentinelConfiguration);
              }
    
          @Bean
              public RedisTemplate<?, ?> redisTemplate() {
                  RedisTemplate<?, ?> redisTemplate = new RedisTemplate<>();
                  redisTemplate.setConnectionFactory(redisConnectionFactory());
                  return redisTemplate;
              }
      }
    

2022-01-17

2022-01-16

2022-01-15

  • [[programmers-17683]]

2022-01-14

  • @Transactional readOnly가 있다
  • Service단이 단순 Repository의 Wrapping이라면 Controller에서 Repository를 바로 쓰는 것도 고려해볼 수 있다

2022-01-13

  • redis 메모리 측정
    • SET 자료구조에 SADD로 10개 데이터 추가 ⇒ 0.19KB 사용
    • 0.19KB(10개 데이터가 포함된 set 하나) x 유저 50,000,000명 x 정책 50개 = 475,000,000KB = 452.995GB
  • 팀원과의 소소한 공유 내용
    • git commit 히스토리 보기
      git log  --all  --decorate --oneline --graph  # a dog
      
    • global gitignore 적용
      git config --global core.excludesFile ~/.gitignore
      

2022-01-12

  • https://techblog.woowahan.com/2709/에서 비동기를 적용해도 괜찮은 이유
    • 재고사용량 증가 방식을 동기 방식으로 처리함으로써, 절대 재고가 더 팔리는일은 발생하지 않는다.
      • 늦게 처리된다 하더라도 최악의 문제가 안 생긴다

2022-01-11

  • spring boot 2.5.8 적용했는데, logback 1.2.9 버전으로 되어 있음
    • logback 1.2.9 버전은 취약점 해결한 버전인 것 같음(참고 링크)

2022-01-10

  • 일정 짜는데 시간이 많이 소요되었다. 내가 얼마나 시간을 잘 활용하는지에 대한 대략적인 감이 필요하다

2022-01-08

  • [[programmers-17676]]

2022-01-07

  • 백엔드는 비주얼적으로 보여주기 어렵지만, 성능 테스트 등으로 보여줄 수 있다
  • history의 경우 변경은 하지 않는다. 말 그대로 history이기 때문에 실패하면 실패한대로, 성공하면 성공한대로 기록한다
  • 하나의 데이터 타입으로 표현하지 못하는 경우 column을 여러 개 생성하는 것도 하나의 방법이 될 수 있다

2022-01-06

  • 아키텍처 설계 시 sequence 다이어그램 그리는 것을 추천 받음
  • 돈을 다루기 위해서는 소수점 정확도가 높은 BigDecimal을 사용하는 것이 좋다
  • 공유 자원의 동시성 제어를 위한 비관적 lock

2022-01-05

  • 동일한 과제 요구사항에 대해 서로의 이해가 많이 다를 수 있다는 것을 또 한 번 깨달을 수 있었음

2022-01-04

  • 카프카
    • 재처리로 유명한 메시지 큐
    • offset만 알면 offset으로 접근 가능
    • 디스크에도 남음