한경민

한경민님의 아티클

한경민

한경민

Inception (개인) 프로젝트 소개

기간 : 2022.11. ~ 2022.12.

기술 스택 : Linux (Debian & Alpine), Docker, Shell Script

개요 : Docker 컨테이너로 서비스 실행 환경 구축 자동화.

  • Linux Virtual Machine (UTM/VirtualBox) 에 Docker CLI 설치
  • Dockerfile 로 각 서비스 이미지 build 설정
  • 각 서비스 설정 파일 작성
  • docker-compose 로 여러 서비스 실행 자동화
  • docker network 설정
  • run dependency 설정 (service health check etc.)
  • DB 데이터 & Wordpress Script bind mount 설정

결과물 : github.com/42ghan/inception

3
0
한경민

한경민

BrilliantServer 문제 해결 사례 - writev 사용하여 불필요한 복사 방지 및 시스템콜 호출 감소

0 문제 정의

  • HTTP 응답을 준비할 때, 파싱을 편리하게 하고, 불필요한 메모리 재할당 및 복사를 최소화하기 위해 header 버퍼와 body 버퍼를 별도로 저장하기로 결정.
  • send 시스템콜로 응답을 보내려면, header 와 body 를 모두 담을 수 있는 버퍼를 할당해서 복사하는 과정을 거치거나 send 를 두번 호출해야 한다.
  • 시스템콜 호출과 할당 및 복사 모두 서버의 성능을 저하시킬 수 있는 요인으로 판단.

1 해결책

  • writev 시스템콜 사용하여 한번의 시스템콜로 각기 다른 두 버퍼에 있는 header 와 body 순서대로 클라이언트에게 전송.
#include <sys/uio.h>
ssize_t writev(int fildes, const struct iovec *iov, int iovcnt);
struct iovec {
   char   *iov_base;  /* Base address. */
   size_t iov_len;    /* Length. */
};
  • 함수 두번째 인자 struct iovec *iov 에 각 버퍼의 주소값과 길이가 순서대로 담긴 iovec 구조체 배열의 주소값을 넣어주고 호출하면, 한번의 시스템콜로 여러개의 버퍼에 담겨 있는 데이터 전송 가능.

2 검증

  • send 결과

  • writev 결과

  • 동일한 양의 데이터 전송 시 writev 를 사용했을 때, 더 나은 성능, 더 적은 시스템콜 호출 횟수 확인.

💡위 비교는 각기 다른 버전의 프로그램으로 테스트한 것이기 때문에 정확한 비교라고 할 수는 없다. 하지만 writev 버전이 상위 버전이고, 총 시스템콜 횟수 증가에서도 볼 수 있듯 timeout 처리 등 추가적인 기능들이 더해졌는데도 더 나은 성능을 보였기 때문에 유의미한 결과라고 볼 수 있다.

3
0
한경민

한경민

BrilliantServer (3인팀) 프로젝트 소개

RFC 9110, 9112 에 정의된 규격을 따르는 HTTP/1.1 버전 origin server 의 구현

개발 기간 : 2022.09. ~ 2022.11.

0 기술 스택

  • C++98
  • CMake
  • GoogleTest

1 기능

  • kqueue 활용한 event loop 기반 non-blocking I/O multiplexing
  • static 파일에 대한 GET/POST/DELETE HTTP Request 처리
  • HTTP 규격에 맞는 응답 생성
  • CGI 실행, name-based virtual hosting, directory listing 지원
  • Google Test & CMake 로 TDD & 유닛 테스팅
  • Connection request header field 의 값에 따라 해당 요청 응답 후, 클라이언트와 네트워크 연결 유지 (keep-alive) / 연결 해제 (close) 결정

2 설계

  • 객체 책임/역할 정의

  • HTTP 요청/응답 flowchart

3 구현

4 성능 테스트

  • siege http load tester 로 성능 테스트
  • 작은 파일 (48.7 KB) GET 요청 10,000 번

  • 큰 파일 (2.5MB image) GET 요청 10,000 번


2
0
한경민

한경민

우리42벤트 문제 해결 사례 - crontab 으로 공휴일 데이터 fetch 자동화 & redis 캐싱

0 배경

  • 공휴일 정보의 경우 어떤 클라이언트가 요청하던지 변하지 않는 데이터인데, 캘린더 표시 월이 바뀔 때마다 공공데이터포탈 Open API 로 데이터를 fetch 해야한다.

1 문제 정의

  • 서비스가 외부 API 서버의 상태에 의존적이다.
  • 공휴일 데이터 fetch 가 오래 걸려서 요청 응답, 캘린더 렌더링이 느려진다.
  • API 서버가 안정적이지 않아서 정확하지 않은 정보가 캘린더에 표시될 때가 있다.
  • 불필요한 네트워크 overhead. 변하지 않는 데이터에 대해 반복적으로 외부 요청을 보내야한다.
  • 클라이언트의 상태에 영향을 받지 않는 데이터를 반복적으로 외부 서비스에 요청해야한다.

2 해결책

  • 서비스 배포 전 +-12개월 공휴일 데이터를 fetch 받아서 각 월별 m_{year}{month} : stringified 공휴일 날짜 array 형태로 redis 에 SET 하여 캐싱.
  • 공휴일 데이터 API 서버가 안정적이지 않아서 24개월치 공휴일을 한꺼번에 요청할 때 실패하는 연월들이 생긴다.
  • Promise.allSettled 로 비동기 요청들을 보내고, 성공 실패 여부를 각 연월별로 ‘fulfilled’ / ‘rejected’ 여부로 STDOUT 으로 로깅.
  • 이미 캐싱에 성공한 연월은 redis EXISTS 로 확인하여 다음 시도에서 제외하고, 24개월 데이터가 모두 캐싱될 때까지 script 를 재실행한다. 보통 두번 하면 다 성공.
  • crontab 으로 매월 첫째날 3AM 에 다음 해 해당 월 공휴일 데이터 fetch.
0 3 1 * * [node executable path] [script path] >> ~/log/our42vent/updateHolidays.log 2>&1
  • eviction policy - 캐시 값들을 영구적으로 쌓을 수 없기 때문에 캐싱된 달의 1일로부터 +12개월 후에 캐시 값 지워지도록 TTL 설정.
  • 결과적으로 항상 +-12개월 공휴일 데이터 캐싱. 그 외의 범위는 공휴일을 표시하지 않는다.
2
0
한경민

한경민

우리42벤트 (4인팀) 프로젝트 소개

슬랙에 마구잡이로 올라오는 42 이벤트들을 한 눈에 볼 수 있는 캘린더 웹서비스

Github 레포지토리 : github.com/innovationacademy-kr/our42vent

대상 : 42 Seoul 커뮤니티

개발 기간 : 2022.01. ~ 2022.02.

0 서비스 배포

배포 기간 : 2022.02 ~ 2022.12

1 기술 스택

2 목적

현재 42 서울 이벤트들은 슬랙 random 채널을 통해 공지. 이러한 커뮤니케이션 방식에서 아래와 같은 문제 발견:

  • 글이 쌓여서 필요한 정보를 못 보는 사람들이 생긴다.
  • 관심있는 이벤트의 일정을 개인적으로 저장해야 한다.
  • 인트라의 agenda 캘린더는 공식 이벤트 정보만 볼 수 있고, 사용이 불편하다.
  • 학생들은 인트라 캘린더에 이벤트를 등록할 수 없다.

42의 모든 구성원들이 이벤트를 등록할 수 있고, 이벤트들을 한눈에 볼 수 있는 캘린더 웹사이트 구현.

3 기능

👤 로그인

  • 42 API OAuth 로 일차 인증 후, access token & refresh token 발급하여 사용자를 인증합니다.

✅ 이벤트 일정 확인

  • 캘린더 뷰에서 다양한 42 이벤트들의 일정을 확인할 수 있습니다.
  • 이벤트들은 종류별 카테고리로 분류되어 있고, 각 카테고리에 해당하는 이벤트들만 선택하여 볼 수 있습니다.

✨ 이벤트 생성

  • 특강, 외부 컨퍼런스, 개인이 여는 이벤트등을 캘린더 이벤트로 생성할 수 있습니다.
  • 본인이 생성한 이벤트 정보를 수정하거나 삭제할 수 있습니다.

🔔 이벤트 구독

  • 이벤트를 구독하여 나만의 캘린더로 관리할 수 있습니다.
  • 알림설정을 통하여 브라우저 알림을 (ServiceWorker API & WebPush API) 받을 수 있습니다.

📣 이벤트 홍보

  • 이벤트 생성 후 자동으로 홍보글이 만들어져서 손쉽게 홍보할 수 있습니다.

4 아키텍쳐

  • AWS lightsail 인스턴스.
  • NGINX 로 HTTPS 설정 및 reverse proxy.
  • Express application server.
  • MariaDB.
  • Redis Cache Storage.

5 역할 및 기여

기여도

  • 커밋 : 46% (139/300 commits without merges)
  • 이슈 : 46% (59/127 assigned issues)
  • PR : 38% (33/86 merged PRs)

역할

  • 캘린더 CSR view & API 구현
  • Service Worker API & Push API 로 사용자가 등록한 이벤트 push 알림 기능 & 알림 설정 UX 구현
  • 공공데이터포탈 Open API 로 공휴일 데이터 요청 & 표시
  • ejs 로 여러 view 에서 공통으로 활용되는 SSR layout 설정



2
0
한경민

한경민

기절초퐁 문제 해결 사례 3 - 유저 접속 상태 변경 알림 최적화

0 문제 정의

  • 기존 구현에서 모든 유저의 접속 상태 변화가 모든 접속된 유저에게 하나의 userActivity WebSocket 이벤트로 공지된다.
  • 프론트엔드에서 유저가 접속 상태가 변화된 타 유저의 정보를 보고 있던 보고 있지 않던 이벤트를 받으면 확인하는 과정이 요구된다. 유저가 상태 변화가 일어난 타 유저의 정보를 보고 있지 않다면 불필요한 연산 소모.

1 해결책

  • 관련 Pull Request : ✨ feat : 유저 컴포넌트를 보고 있는 유저에게만 activity 변경 정보 전달 #185
  • 서버에서 GET /user/:userId/info 와 요청자가 머무르고 있는 UI 정보를 바탕으로 :userId 로특정되는 유저 정보를 보고 있는 유저들의 목록 in-memory 로 저장하고, 변화 시 업데이트.
  • socket.io 의 room 기능 활용하여 특정 유저의 접속 상태가 변경될 때, 해당 유저의 정보를 보고 있는 유저들을 activity-${userId} 라는 room 에 넣어두고, 해당 room 에만 userActivity 이벤트 전송.

2 리뷰

  • 장점
  • 불필요한 네트워크 전송 감소. 서버는 각 클라이언트가 필요로 하는 유저들의 접속 상태 변화만 이벤트로 공지한다.
  • 단점
  • 유저 접속, 접속 해제, UI 변경 시 서버 연산량 증가.
  • socket.io room 개수 증가 및 서버가 in-memory 로 저장해야할 데이터량 증가.


1
0
한경민

한경민

기절초퐁 문제 해결 사례 2 - 게임 엔진 코드 클라이언트에서 서버로 이전

0 문제 정의

  • 플레이어 및 관전자의 클라이언트 코드에서 게임 엔진을 동시에 각각 실행하고, paddle 위치 업데이트만 서버를 통해 게임에 접속 중인 다른 유저들에게 WebSocket 이벤트로 알려주던 기존 설계.
  • 기존 설계대로 구현했을 때, 반응형 UI 적용 시, browser 윈도우 크기가 변할 때마다 해당 클라이언트에서 실행되던 게임 엔진 재실행. 결과적으로 한 게임에 대한 각 클라이언트의 게임 엔진 동기화가 실패 문제 발생.

1 해결책

  • 관련 Pull Request : ✨ feat : 게임 엔진 백엔드로 이전 및 반응형 구현 #214
  • 설계 상의 문제라고 판단하여 게임 엔진 서버로 이전.
  • 서버에서 각 게임 상태 (공 위치, paddle 위치, 점수) in-memory 에 저장.
  • 한 게임 당 서버에서 하나의 게임 엔진 실행, 플레이어 및 관전자들에게 10 ms 마다 게임 데이터 계산해서 전송.
  • 플레이어들은 paddle 위치 변경할 때마다 서버에게 WebSocket 이벤트로 paddle 위치 변경 알림.

2 리뷰

  • 장점
  • 여러 유저에게 안정적으로 동기화된 게임 데이터 전송.
  • 서버에서 게임 규격을 0~1 사이의 비율로 저장하고 계산하여 클라이언트에게 전송. 클라이언트의 browser 윈도우 크기 곱하여 반응형 손쉽게 구현.
  • 보다 간결해진 코드 (PR git diff +306, -511)
  • 단점
  • 서버 연산량 & 네트워크 데이터 송수신 과부화. 끊김 없는 게임 UX 를 위해 10ms 마다 서버는 게임 데이터 연산을 수행해야하고, 모든 플레이어 & 관전자들에게 데이터를 보내줘야한다. 게임이 많아질수록 서버 성능이 저하될 수 있다.
  • 개선안
  • 게임 데이터 연산 및 전송 역할 서버 분리. 서버의 API 핸들링 성능에 영향을 주지 않도록 게임 데이터 연산 및 전송 역할만을 담당하는 별도 서버 구축. 하지만 유저 인증이 복잡해질 수 있다.
1
0
한경민

한경민

기절초퐁 문제 해결 사례 1 - 유저-유저 & 유저-채팅방 관계 in-memory 캐싱

0 문제 정의

  • 위와 같은 Entity Relationship 구조에서 아래와 같은 성능 저하 요인들이 있을 것으로 예상.
  • 유저-유저 관계 조회에서 대부분의 UI 에서 잦게 요청된다. 매번 해당 유저가 특정 관계에서 능동적인 역할인지 혹은 수동적인 역할인지 두 경우 모두 확인이 필요하다.
  • 친구 관계일 경우 해당 유저가 친구 요청 수신자인지 송신자인지 확인이 필요.
  • 차단 관계일 경우 차단한 유저인지 차단 당한 유저인지 확인이 필요.
  • 유저-채팅방 관계 정보 SELECT 시 JOIN 이 매번 일어난다.
  • 유저가 참여한 채팅방 리스트와 정보를 조회하기 위해 channels & channel_members 테이블
SELECT 
    cm.member_id,
    cm.channel_id,
    c.access_mode,
    c.name, c.dm_peer_id,
    (
        SELECT COUNT(*)
        FROM messages msg
        WHERE msg.channel_id = cm.channel_id
        AND msg.created_at > (
            SELECT viewed_at 
            FROM channel_members 
            WHERE member_id = my_id AND channel_id = cm.channel_id
        )
    ) AS unseen_count,
    (
        SELECT COUNT(*)
        FROM channel_members cm2
        WHERE cm2.channel_id = cm.channel_id
    ) AS member_count
FROM channel_members cm
LEFT JOIN channels c ON c.channel_id = cm.channel_id
WHERE cm.member_id = my_id;
  • 유저가 채팅방에 입장하여 멤버들의 정보를 조회하기 위해 channels & channel_members 테이블
SELECT
	cm.member_id,
  cm.is_admin,
  c.owner_id
FROM channel_members cm
LEFT JOIN channels c ON c.channel_id=cm.channel_id;

1 해결책

  • 비즈니스 로직을 수행하기 위해 관계 정보를 조회해야하는 Service 객체들과 DB 사이에 관계 정보를 in-memory 에 캐싱하여 들고 있는 Storage 객체 구현.
  • 유저-유저 관계 정보를 캐싱하는 UserRelationshipStorage 객체.
  • 모든 유저의 정보를 항상 들고 있는 건 너무 큰 overhead 로 이어질 수 있기 때문에, 유저의 접속 상태로 캐싱 life-cycle 설정. 유저 접속 시 load & 접속 해제 시 unload.
  • 유저-채팅방 관계 정보를 캐싱하는 ChannelStorage 객체.
  • Service 객체는 관계 정보 CRUD 시 반드시 Storage 객체를 거친다.

💡 Dependency Injection 패턴의 NestJs 에서 주입된 의존 객체가 Singleton 으로 유지되기 때문에 가능한 해결책이었다.

2 검증

  • 관련 Pull Request : ✅ test : 관계 정보 캐싱 성능 테스트 #347
  • 위 문제 정의에 정의한 성능 저하가 예상되는 관계 정보 조회 메소드들을 기존 캐싱을 활용할 때와 매번 query 하는 방식으로 비교. 아래와 같이 뚜렷한 성능 개선 확인.

  • 약 300 명의 유저들의 캐시 로드 시간 평균값이 2.10ms 정도로 측정되었다. 접속 시에만 발생하는 지연이고, 캐싱이 가져온 성능 향상에 비해 무시할 정도의 지연으로 판단.
  • 메소드 별 분석

3 부작용

4 리뷰

💡 TL;DR 요청이 많을수록 유리, 유저가 많을수록 불리


2
0
한경민

한경민

기절초퐁 (4인팀) 프로젝트 소개

친구와 함께하는 채팅과 멀티플레이 Pong 게임 웹서비스

Github 레포지토리 : github.com/brilliantshell/ft_transcendence

개발 기간 : 2022.12. ~ 2023.03.


0 기술 스택

1 기능

🗝️ 로그인

  • 42 API OAuth 로 1차 인증 후, access token & refresh token 발급하여 유저 인증
  • refresh token 의 재사용 감지 기능을 추가하여 보안 강화
  • Nodemailer 를 활용하여 이메일 인증코드 2차 인증

🧒 유저 프로필

  • 닉네임, 프로필 사진 수정 및 2차 인증 설정
  • 유저 접속 상태(🟢 online / 🟠 in-game / 🔴 offline) 실시간 업데이트
  • Pong 게임 레벨, 전적, 업적

🕹️ 멀티플레이 Pong 게임

  • 실시간 관전 가능한 2D 반응형 1 vs 1 Pong 게임
  • 네트워크 지연 및 재연결 시에도 게임 session 유지
  • 다양한 게임 모드 지원 (공 사이즈 2x, 짧은 paddle)
  • 플레이어 랭킹 보드

💬 채팅

  • 실시간 메시지 전송 및 수신
  • 유저 간 DM 기능
  • 채팅 방 소유자(owner)
  • 채팅 방 모드 및 비밀번호 설정, 참여자에게 채널 권한을 부여하거나 삭제하는 등 모든 권한
  • 채팅 방 관리자(admin)
  • 다른 유저를 채널에서 추방하거나 일정 시간 동안 음소거 할 수 있는 권한
  • 유저를 검색하고 채팅 방에 초대
  • 전체 채팅 방 리스트에서 생성 또는 삭제된 채팅방, 읽지 않은 메시지 개수, 접근 권한 변경 등을 실시간 반영

👯 유저 관계

  • 친구 리스트에서 받거나 보낸 친구 요청과 내 친구들을 간편하게 확인
  • 차단 시 일부 상호 작용 제한 (채팅 숨김, 친구 요청 금지, 게임 초대 금지, etc.)

2 설계 문서

3 역할 및 기여

3.0 기여도

  • 커밋 : 41% (234/575 commits without merges)
  • 이슈 : 48% (95/200 assigned issues)
  • PR : 45% (67/149 merged PRs)

3.1 역할

Backend

  • 성능 개선을 위한 유저-to-유저, 유저-to-채팅방 관계 데이터 in-memory 캐싱
  • 멀티플레이 Pong 게임 플레이 및 관전 API 담당하는 GameModule 구현
  • 유저 관계 및 접속 상태 CRUD & 실시간 업데이트 API 담당하는 UserModule 구현
  • 플레이어 랭킹 보드 데이터 가공 및 실시간 업데이트 API 담당하는 RanksModule 구현
  • Jest 로 각 service 객체 유닛 테스트 및 supertest 로 controller & gateway e2e 테스트

Frontend

  • 게임 대기방 & 게임 플레이 반응형 UI/UX
2
0
한경민

한경민

👨🏻‍💻 저는 이런 Problem Solver 입니다

  • 주도적으로 문제를 정의하고 해결합니다.
  • 문제의 본질을 파악하고, 정확한 문제 정의를 하기 위해 질문합니다.
  • 복잡한 문제를 작고 단순한 단계로 나누어 접근합니다.
  • 동료와의 협력, 관계를 중요시합니다. (매우!)
  • 보다 효율적으로 소통하기 위해 clean code 를 지향합니다.
  • 과정과 결과를 검증하고, 더 나은 방안을 찾기 위해 테스트합니다.
  • frontend 개발 경험이 있어 클라이언트 사이드의 문제를 이해하고 서버를 개발합니다.
2
0