📚FOS Study
홈카테고리
홈카테고리
📚FOS Study

개발 학습 기록을 정리하는 블로그입니다.

바로가기

  • 홈
  • 카테고리

소셜

  • GitHub
  • Source Repository

© 2025 FOS Study. Built with Next.js & Tailwind CSS

목록으로 돌아가기
💼interview

nhn gameenvil console backend 직무 인터뷰 준비

약 4분
GitHub에서 보기

nhn gameenvil console backend 직무 인터뷰 준비

자기소개

  • 안녕하세요 김병태입니다.
  • 소셜카지노 슬롯게임, 블록체인을 활용한 메타버스 게임 등 다양한 도메인을 경험해 왔습니다.
  • NHN에서는 슬롯 백엔드 개발을 담당하여, 슬롯 게임 제작 및 아키텍처 고도화, 테스트 가능한 환경을 만들었으며 품질과 구조 개선에 강점을 갖고 있습니다.
  • 새로운 기술과 개발 프로세스 최적화에도 관심이 많아, AI기반 슬롯게임개발 TF에도 참여하여 생산성을 향상하기 위해 노력했습니다.
  • 고성능 게임엔진 백엔드 환경과 분산 시스템을 경험해보고자 지원하게 되었습니다.

RabbitMQ 혹은 Message Queue에 대해서 경험이 있는가?

  • 슬롯 게임 데이터의 변경을 관리하기 위해 RabbitMQ를 사용해본 경험이 있습니다.
  • 게임 데이터는 자주 변하지 않는 데이터로 애플리케이션 메모리에 올려두고 사용했는데요, 이 때 DB 변경시점과 여러 서버에서 캐쉬 동기화 시점을 맞추기 위해 RabbitMQ를 사용한 경험이 있습니다.
  • 이 문제를 해결하기 위해 Hibernate Post-Commit Listener를 활용해 데이터 변경을 감지했고, 변경된 엔티티 ID를 RabbitMQ를 통해 fanout 방식 브로드캐스트로 전달하도록하는 구조를 사용했습니다.
  • 각 서버는 메시지를 수신하면, 자체적으로 유지하던 캐시 메모리를 갱신하도록 처리하여, DB와 메모리 캐시간 정합성을 유지하도록 했습니다.

Hibernate Post-Commit Listener는 어떻게 동작하나요?

  • Hibernate는 flush 단계에서 Dirty Checking을 수행합니다.
  • 트랜잭션 커밋 직전, Hibernate Event System이 해당 엔티티 변경 이벤트를 기록합니다.
  • DB 커밋이 실제로 성공하면, 그 때 이벤트 리스너가 실행됩니다.
  • 리스너 내부에서는 변경된 엔티티 정보, 변경된 필드, 엔티티 ID등을 받아 후처리 로직을 자유롭게 실행할 수 있습니다.

캐시 갱신 시 데이터 불일치와 동기화 문제는 어떻게 해결했나요?

  • 게임 서버 특성상 캐시에 대한 Read 요청이 많고, Write는 메시지 수신 시에만 간헐적으로 발생했습니다.
  • 이런 read-heavy 환경에서는 일반적인 ReentrantReadWriteLock보다 StampedLock이 optimistic read와 write lock이 훨씬 효율적입니다.
  • 평소에는 Optimistic Read Lock으로 캐시를 읽습니다.
    • 대부분 Read 요청이기 때문에 lock이 거의 필요 없고, 스레드 경합도 없어서 매우 빠르게 응답 가능합니다.
  • 캐시 갱신이 필요한 타이밍에만 Write Lock을 획득했습니다
    • Write Lock은 단일 스레드가 캐시 값을 변경할 때 잠깐만 획득됩니다.
  • Read 요청은 Write 요청을 막지 않고, Write는 Read를 대기 시킵니다.
  • 짧은 불일치에 대해서는 허용하는 수준으로 동작하게 했습니다.
    • read는 잠깐 이전 캐시를 볼 수 있지만, 슬롯 게임 특성상 게임의 정보가 변경되는 일은 빈도가 낮은 편이었습니다.

StampedLock vs ReentrantReadWriteLock은 어떻게 다른가요?

  • 둘 다 읽기-쓰기를 분리하는 락이라는 점은 같습니다.
  • 읽기 동시성은 높이고, 쓰기 구간에서는 배타적으로 보호하는 목적을 갖고 있습니다.
  • ReentrantReadWriteLock은 이름 그대로 재진입성을 갖고 있어, 같은 스레드가 여러번 같은 락을 획득할 수 있습니다.
    • 다만 읽기/쓰기는 내부적으로 락 상태를 갱신해야 하기 때문에, 읽기가 많은 상황에서는 오버헤드가 커질 수 있습니다.
  • StampedLock은 낙관적 읽기를 제공하며 쓰기가 드문 시나리오에서는 거의 락 오버헤드가 없는 것과 비슷한 성능을 낼 수 있습니다.
    • 또, 락을 잡을 떄 long 타입의 stamp를 반환하고, stamp를 넘겨줘야 unlock이 됩니다.
    • 이 떄문에 특정 스레드에 종속되지 않는 대신, 코드를 잘못짜면 unlock 누락 등으로 이어지기 쉽습니다.

Kafka 경험도 있나요?

  • 슬롯 게임에서 Spin 요청에서 즉시 응답과 비동기 후 처리를 분리하기 위해, 스핀 과정에서 발생하는 미션 달성, 로그 처리 등은 ApplicationEvent로 발행하여 트랜잭션 이후에 처리되도록 했습니다.
  • 트랜잭션 커밋된 이후 Kafka에 메시지를 발행하고, 발행 시 오류가 발생하면 Outbox 패턴을 통해, 일단 DB에 적재한 후 별도 재처리 배치 작업으로 카프카로 재전송하는 구조를 사용했습니다.
  • 이렇게 설계함으로써 Spin API의 응답 속도는 유지하면서도 후처리 작업의 신뢰성과 재시도 가능성을 확보했습니다.

왜 RabbitMQ가 아닌 Kafka를 사용했나요?

  • Spin 후처리 데이터는, 로그 기반 스토리지와 재처리가 가능한 Kafka를 사용했다고 생각됩니다.
  • RabbitMQ는 실시간의 단기 메시징 중심으로 동작되지만, Kafka를 사용함으로 써 Consumer에 의해 Commit되지 않으면 메시지가 계속 남아 있어, 필요하면 과거 메시지를 재처리할 수 있도록 하기 때문입니다.
  • 장기 보관 / 재처리 / 리플레이 구조에 더 강한 Kafka를 사용함으로써, 결과적으로 일관성을 유지할 수 있는 구조를 만들기 위해 Kafka를 사용한 것으로 판단됩니다.
  • 또한 userId 기반으로 파티션 키를 user account uuid로 사용함으로 써, 해당 메시지에 대한 순서가 보장되도록 처리할 수 있습니다.

Kafka에서 메시지는 어떤 모델을 사용했나요? (At Most Once, At Least Once, Exactly Once)

  • 메시지는 반드시 한 번 publish 되도록 처리
  • Outbox 패턴을 사용하여, 전송 실패 시 재전송 할 수 있도록 처리했습니다.
  • Consumer에대해서는 중복 발생 가능성이 열려있는 상태인 것 같은데, 이는 매 스핀마다 spinId를 부여했고, 해당 id를 통해서 중복된 메시지인지 구분할 수 있도록은 처리해두었습니다.

데이터베이스 샤딩을 해본적이 있나요?

  • 유저 ID기반, mod 방식 샤딩을 적용해본 경험이 있습니다.
  • 공통 DB에서 account 테이블을 관리하고, 각 유저에 대한 shard 번호를 DB에 관리하도록 했습니다.
  • 이 때, 트랜잭션을 사용하기전에 ThreadLocal 스토리지를 활용하여 shard 번호를 설정하도록 하도록하여 동작하도록 했습니다.

그럼 Spring 트랜잭션에서 DataSource를 선택하는 흐름은 어떻게 되나요?

  • 메서드를 호출하면
  • AOP Proxy에서 @Transactional를 감지하게 되고
  • PlatformTransactionManager.begin()을 호출하게 됩니다.
  • 이 때 DataSourceTransactionManager.getConnection()을 호출하여 DataSource를 가져오게 됩니다.
  • DataSource(=RoutingDataSource)에서 determineCurrentLookupKey()를 호출하게 되고
  • 이 때 ThreadLocal에 저장된 shardKey를 활용하여 데이터 소스를 선택할 수 있도록 했습니다.

그럼 샤딩 라우팅을 어떻게 하면 최적화 할 수 있을까요?

  • 샤드 내부 트랜잭션만 허용하고, 샤드 간 정합성은 이벤트 기반 Eventually Consistent 패턴을 활용하여 결과적으로 정합성을 유지하는 방식을 택할 것 같습니다.
interview 카테고리의 다른 글 보기수정 제안하기
목차
  • nhn gameenvil console backend 직무 인터뷰 준비
  • 자기소개
  • RabbitMQ 혹은 Message Queue에 대해서 경험이 있는가?
  • Hibernate Post-Commit Listener는 어떻게 동작하나요?
  • 캐시 갱신 시 데이터 불일치와 동기화 문제는 어떻게 해결했나요?
  • StampedLock vs ReentrantReadWriteLock은 어떻게 다른가요?
  • Kafka 경험도 있나요?
  • 왜 RabbitMQ가 아닌 Kafka를 사용했나요?
  • Kafka에서 메시지는 어떤 모델을 사용했나요? (At Most Once, At Least Once, Exactly Once)
  • 데이터베이스 샤딩을 해본적이 있나요?
  • 그럼 Spring 트랜잭션에서 DataSource를 선택하는 흐름은 어떻게 되나요?
  • 그럼 샤딩 라우팅을 어떻게 하면 최적화 할 수 있을까요?