C# .NET CORE 기반 Web-Crawler

최근에 블로그 글이 뜸했는데, 이에는 여러가지 이유가 있었다.

첫번째로는 이직 후 적응도 하고 있었지만, 기본적으로는 Dev Toy를 많이 했다.

그 중 몇개를 운용해보고, 어느정도 버그가 잡힌 뒤에 Github 공개 프로젝트로 돌렸고, 이에 대한 얘기를 블로그에 써보고자 한다.

우선 내가 하루에 꽤 많은 시간을 쏟는 것이, News 찾아보기, 핫딜 게시판 보기였다.

가는 사이트, 열어보는 게시판이 여럿이다보니까 이 polling 만 해도 어마어마한 시간을 쏟게 됐다.

특히 hotdeal의 경우 놓치는 일이 많아서, 이를 극복(?)하고자 웹 크롤링을 시도하게 됐다.

처음에는 python을 썼었는데, python의 경우 디버깅이나 비동기 처리에 아쉬움이 있어, C# .NET CORE로 개발하게 됐다.

elky84/web-crawler: web-crawling (use Abot2), feed-crawling (use FeedReader)

web-crawler

web-crawler

abot2라는 C# .NET CORE 기반의 크롤러를 이용했으며, 페이징 처리를 했다.

sjdirect/abot: Cross Platform C# web crawler framework built for speed and flexibility. Please star this project! +1.

또한, Feedly를 통한 RSS구독을 해오긴했으나, 알림을 한 곳에 모아보고자하는 니즈가 있었는데, 이를 위해서 Feed (RSS) Crawling 기능도 포함하게 됐다.

codehollow/FeedReader: C# RSS and ATOM Feed reader library. Supports RSS 0.91, 0.92, 1.0, 2.0 and ATOM. Tested with multiple languages and feeds.

나와 동일한 니즈가 있는 분들께 참고가 되거나, C# 기반 웹 크롤링에 관심있으신 분들께 도움이 되길 바래본다.

C# .NET CORE 기반 Lol-Crawler

Web-Crawler와 마찬가지로, 자동화의 일환으로 개발했다.

elky84/lol-crawler: Notification from LOL friend game start & end.

lol-crawler

지인 분들과 취미로 롤을 종종하는데, 직접 시간을 맞추지 않고선 지인들이 플레이 하는 중인 것을 확인하기 어려워 개발하게 됐다.

MingweiSamuel/Camille: C# Riot API Library. Thread safe, automatic retries, autogenerated nightly releases.

Riot API를 직접 구현할수도 있었겠지만, 이미 구현된 API Wrapper를 이용해서 구현했다.

원래는 전적 사이트나 데이터 분석용도도 고민했으나, 롤을 띄엄 띄엄 하기도하고, 다른 Dev Toy에 시간을 쏟고 있는 중이라, 친구의 플레이 시작/종료(결과 포함) 알림 기능까지만 구현되어있다.

C# .NET CORE 기반의 Riot API 연동이 궁금하신 분께 도움이 되면 좋겠다는 생각에 Github로 공개한다.

게임 개발 에서의 git vs svn

요즘에야 다들 대학생분들도 git과 github를 자연스럽게 사용하지만, 나는 부끄럽지만 학창 시절에 버전별로 압축해서 usb에 백업하고 개발했었다.

실무에서, 내가 처음 사용한 버전 관리 시스템은 CVS다.

툴도 불편했지만, 수동으로 파일 백업하던 시기보다 장점이 많았다.

그 다음으로 쓰게 된 버전 관리 시스템이 svn이다. 아직도 많이 선택되고 있는 간편하고, 직관적인 버전관리 시스템이다.

아직도 게임 개발팀에선 svn을 사용하는 경우가 비일비재 하다. 이는 몇가지 이유가 있다.

  1. svn보다 git이 상대적으로 학습 코스트가 든다는 점, 게임 개발이 훨씬 더 많은 코드를 생산해낸다는 점,
  2. 태그, 브랜치를 적극적으로 쓰지 않는다는 점.
    • 머지를 크게 크게 하고, 태그, 브랜치 갯수를 적게 유지하려 한다.
      • 어찌보면 svn에 개발 문화를 맞춘 느낌도 있다.
  3. 폴더별로 check out / commit / update 하고 싶다.
  4. local change를 다양하게 유지해서, 하나의 check out에서 다양한 작업을 동시에 하고 싶다.
  5. 익숙하다.

애초에 git과 svn의 치명적인 차이점은 다음과 같다.

git_vs_svn_1

git은 각기 다른 저장소를 보유하고 있으며, 협력을 통해 각 rebase/merge 등의 액션을 통해 이를 조절해줘야 한다.

또한 충돌처리 과정이 상대적으로 까다롭기도 하다.

git_vs_svn_2

이 그림을 보면 조금 더 잘 이해가 되실텐데, 모든 작업을 원격에서 하는 것과 로컬에서 하는 것이 가장 큰 차이라고 볼 수 있다.

여기서 계속 논란이 되는 것은 충돌 발생의 기준과, 충돌 해결에 대한 과정이 확실히 git이 더 어렵다.

커밋만 해두면 모든 데이터가 살아 있을 수 있다지만, 비 프로그래머 직군의 사용법을 본다면 사실 Commit은 무거운 행위이자 위험한 행동으로 간주하고, local changes를 유지한채 각종 액션을 시도하는 습관을 쉽게 바꾸지 못했다.

왜 그런 간단한 습관도 못바꿔요?

라고 치부하기엔 git의 학습 코스트가 높고 비직관적이기 때문이라고도 말할 수 있겠다.

git은 모두가 같은 상태에 놓이는 것을 지향하는데, svn은 그렇지 않고, 사용자 다수가 그런 환경에 익숙하지 않다면 이는 자연스레 오류 발생률의 증가로 이어지는 것이기 때문이다.

물론 적응 한 뒤에는 로컬 저장소에 대한 장점이 커질 수 있으나, 이를 활용하기 보다 단점이 더 두드러지는 현상이 벌어진거라고도 볼 수 있겠다.


다시한번 강조하자면 git의 장점은 로컬 저장소 개념과 브랜치의 적극 활용에 있다. svn의 장점은 직관성이다.

사실 게임 개발에서는 비 프로그래머 직군이 많기 때문에, 이런 상대적 비직관성은 큰 단점이 되는 경우가 많다.

특히, 왜 local changes를 두지 않아야 하는지, 적절한 .gitignore와 같은 파일을 만들고 관리해줘야 하는지를 납득시키기가 쉽지 않다.

이에 대한 학습 코스트를 들이고 노력하는 것이 합리적이지 않은 측면도 분명히 있다.


이러한 이유들로 게임 개발에서는 svn이 여전히 git보다 많이, 자주 사용된다.

개인적인 결론은 git+svn이 맞지 않나 싶은 생각이 있다. 리소스 관리 측면에서 보았을때는 확실히 svn이 메리트가 있는 만큼 리소스는 svn, 코드는 git이 맞지 않을까?

온라인 코드 리뷰에 대해서

코드 리뷰.

말만 들어도 시간도 많이 쓰고, 리뷰 중에 삼천포로 빠지기 쉬운 그런 절차. 졸려서 반쯤 잠든 상태에서 동의 하는척하고, 나중에 이 코드 누가짰냐고 리뷰때 건너뛴 코드 아니냐고 말하기 쉬운 절차. 그래서 몇번씩 오프라인 코드 리뷰를 하다가, 바빠지면 건너뛰고 그러다보면 자연스레 흐지부지되곤 하는 바로 그 절차다.

또한 눈에 보이지 않는 효과를 동반하기에, 우선 순위에서도 밀리기 쉽상이다.

이런 심리적 저항들을 이겨낸 뒤 의지를 가지고 시작하지만, 오래 가지 않아 다른 회의에 밀리고, 휴가자 많다고 취소하고, 리뷰 할만한 내용도 없었다고 취소된다.


프로그래머들 사이에서도 여러가지 합의가 필요하다고 생각하는데, 나는 계획(=설계)와 디테일에 대한 장단점에서의 기술 결정, 기획적인 결정에 대한 해석과 같은 것들이다.

그리고 실제 구현된 결과물에 대한 검수라고 할 수 있다.

이 과정들 대부분 온라인으로 인터럽트를 최소화 할 수 있는 방법이 있는데, 그 중에서도 검토 과정인 코드리뷰에 대해서 말해보고자 한다.


내 개인적인 경험상으로만 비춰보자면 웹 개발보다 게임 개발의 경우, 작업량이 좀 더 많았다. 정확히는 업무량의 의미보다도 코드량의 의미에 더 가깝다고도 볼 수 있겠다.

특히 프로젝트 빌드업 단계에서는 코드 변화량이 압도적으로 많았고, 구조에 변경을 가하거나, 대 규모 컨텐츠 작업, 연관성이 깊은 컨텐츠 작업의 개별 진행과 같은 상황들로 인해 더 많기도 했다.

또한 버전 관리 시스템에 추가되는 부산물도 좀 더 많았다.

예를 들어, 마스터데이터, 설정 데이터, 메타 데이터 등으로 불리우는 기획팀의 테이블 데이터라거나, 유니티의 meta 파일, prefab 파일 등이 그렇다.

참고로 당시 회사에서는 gitea를 사용하고있는데, 코드 리뷰 대상에 text 기반 부산물과 같은 파일에는 diff 대상으로 포함되어 코드만 선별해야되는 불편함이 있다.

슬픈 현실이지만, 업무량이 많다보니, 코드리뷰가 강제 될 경우 업무 속도 지연에 대한 불만이 나오곤했다.

또한 많은 부분의 업무가 격리되어 진행되기보다는, 유사한 부분의 코드를 접근해야 하는 일이 많았는데, 브랜치를 강제화 할 경우 다수의 코드 conflrict를 해소하는 일이 쉽지 않다보니, 메인 branch에 잦은 merge를 통한 conflict 최소화를 해야 되는 경우도 많았다.

애초에 git이던 svn이던 같은 문제를 갖고 있는 것이긴 하지만, 많은 충돌을 처리해야 될 만큼 변화량이 많을 경우에는 통합 비용과 사이드 이펙트가 작지 않은데, 이 변화량이 게임이 훨씬 더 많기 때문이라고 볼 수 있다.


github에서는 pull request, gitlab에서는 merge request라 부르는 이 과정은, 메인 브랜치에 허용되지 않은 커밋을 방지함으로써 코드의 안정성을 확보하는 절차라 볼 수 있다. 또한 메인 브랜치를 protect를 거는 것이 좋다고 생각하지만, 변화가 잦은 조직일수록 그렇게 운용하긴 어려운 것 같다.

코드 리뷰를 강제화 한다면 모든 변화를 감지하고, 검수할 수만 있다면 장애나 오류도 미연에 방지할 확률이 높아진다.

또한 큰 기능적으로는 이슈가 없지만, 기존 규칙을 위배한다거나, 오타, 개선이 필요한 작업들을 체크하고, 수정 작업이 완료된 상태에서 들어올 수 있어서, 메인 브랜치가 자칫 깨진 창문이 되는 일을 방지해주기도 한다.


기본적으로 코드 리뷰가 시점마다 다른 피드백이 가게 되어있다.

프로젝트 빌드업 단계

  • 큰 틀을 이해 시키려는 피드백
  • 빠른 구현을 위해 기술 부채를 일부 용인하는 검수

프로젝트 출시 막바지 단계

  • 기능을 수정하는 데에 포커스 맞춘 피드백
  • 만기일을 맞추기 위한 기술 부채를 용인하는 검수

프로젝트 라이브 단계

  • 안정성을 최우선으로 한 피드백
  • 기존 규칙을 깨지 않는 것을 우선으로 하는 피드백
  • 기술 부채를 가급적 줄이려는 검수
  • 기술 부채를 없애려는 일감 진행이라면, 정말 기술 부채가 줄어드는지에 대한 관점에서의 피드백. (과한 코스트 소모 혹은 더 나빠지지 않는지 등)

코드 리뷰는 어떤 수준까지, 어떤 접근으로, 어떤 방식으로 작업 해 나가야 하는지에 대한 피드백이 오가는게 맞다.

어떠한 가치와 철학으로 생각을 맞춰 나가야 하는지에 대한 공감대를 이뤄내지 못한채 코드 리뷰에 돌입하게 되면, 큰 의미 없는 리뷰나 개인의 취향이 묻어나는 리뷰로 인해 불필요한 토론으로 시간 낭비와 감정 소모만 이어지기 쉽다는 점도 염두에 두어야 한다.

기본적으로 코드 리뷰에서의 핵심은 실수를 잡아주고, 접근 방식에 대한 질의 응답, 구현 사항이 충실한지에 대한 검토, 개선 사항 제안 같은 것을 체크하는 과정으로 여겨지는 것이 좋다.


온라인 코드 리뷰를 해보니 확실히 부담이 적고, 내가 원하는 때에 봐도 되니까 오프라인 코드 리뷰에 비해 장점이 많았다.

다만 변경 사항이 빠르게 적용되어야 하는 경우에는 오프라인 리뷰와 다를바 없긴했으나, 기록이 남는다는 점에서의 장점은 있었다.

협업 자체도 브랜치에서 공동으로 이루어져야 하는데, 이는 많은 변경량이 브랜치에서 발생할 수도 있다는 의미이다.

그리고 메인 브랜치나 다른 브랜치에서의 변경 사항이 작업 중인 브랜치에 적용되어야 할 경우 매우 복잡한 머지 과정을 겪어야하기에 큰 혼란을 겪을 여지가 있다.

동시 다발적인 작업 진행이 많을 수록 자연스레 겪는 이슈인데, 이에 대한 해결책이나 작업 규칙 준수, 브랜치 작업 방식의 단순화 등은 온라인 코드 리뷰와 별개로 해소되어야 할 이슈이고, 공동 작업 이슈가 해소되고 난 뒤라면 더더욱 온라인 코드 리뷰가 빛을 발하게 된다.

코드 리뷰를 아직 하지 않고 있다면 코드 리뷰 부터, 오프라인 코드 리뷰만 하고 있다면 온라인 코드 리뷰를 시작하고 그 양을 좀 더 늘려 보는게 어떨까?

온라인 코드 리뷰 중 오프라인 질의 응답이 길어진다면 PR에 설명이 부족한건 아닌지 고민해보자.

게임서버에서의 DB 사용 전략 - 1. DB를 사용하는 방식

게임 서버에서 DB를 사용하는 것은 당연히 필요하다.

여러번 언급한대로 게임 서버에서는 정적 데이터만으로 서비스를 구성하는 것이 애초에 거의 불가능 하기 때문에 그렇다.

그렇다면 게임 서버에서 DB를 다루는 방법은 어떻게 나뉠까?

극단적으로 저장소로서만 사용

  • 게임 서버 메모리에 오랜 기간 캐싱하다, 특정 간격, 특정 이벤트 때만 기록
    • 특정 간격: 시간 단위. 5분, 15분, 30분, 1시간, 하루 등
    • 특정 이벤트: 로그아웃 시점, 특정 패킷 처리 전, 채널 변경, 존 이동 등
  • 패킷 처리 과정에서 이미 데이터 무결성을 지켜주게끔 멀티 스레드 구조를 구축해놓았기 때문에, 저장 시점에 일관되게 잘 기록만 해준다면 무결성도 지키면서 성능 극대화가 가능하다.
  • 다만 메모리 보유기간중에 서버 크래시나, 커넥션 유실/재접속시와 같은 상황에서 유실/복사 같은 크리티컬 이슈가 생기기 쉽다.

Stored Procedure(이하 SP)를 활용해 DB 의존형 로직 구성

  • 이 경우에 트랜잭션을 묶는 지점을 SP의 의존하므로, 자연스레 잠김과 병목 지점이 DB로 몰린다.
    • SP 처리 결과를 무시하고 진행할 경우 2차, 3차 감염의 여지가 있으므로 그렇게 할 수도 없기에 모든 처리가 SP로 집중되면서 자연스레 병목이 된다.
  • 또한 상황에 따라 중복 로직이 구성되는 경우가 많다.
  • 많은 양의 데이터를 자주 요청/응답에 사용해야 되는 장단도 있다.
  • DB의 처리 결과를 반영하므로, DB 처리의 성공/실패가 그대로 메모리에 반영되므로 오차가 적다.
    • 운영툴이나 디비 데이터 변경과 같은 처리도 매끄럽게 이어지게 하기 쉽다.

DB를 적극 사용하되 SP는 사용하지 않고, 트랜잭션 관리도 게임 서버가 하는 구조.

  • 게임 서버가 처리한 것은 DB에도 반영되므로 유실/복사에서 자유롭다.
  • 트랜잭션도 게임 서버 구조가 잘 지켜준다면, 영향을 덜 받을 수 있어 트랜잭션을 안 걸 수도 있게 됨.
    • 그럼에도 거래라거나, GM툴에서 발생된 액션이라거나, 다른 컨텐츠와 겹친 DB 접근 같은 상황을 막기 위해서 트랜잭션을 걸게 되는 경우가 많이 발생한다.
      • 이를 서버 구조적으로 소화하게끔 의도하는 것이 필요하다.
  • 보통 DB 저장 속도보다, 게임 서버 처리량이 많게 동작하므로 여전히 DB의 병목 가능성을 내재하게 됨.
    • 그 가능성은 SP를 사용하는 접근보다는 낮다

DB를 적극 사용하는 것처럼 의도하되, 캐시 서버를 두는 구조.

  • 게임의 캐시 서버는 Redis를 웹에서 사용하는 경우와 다르게, DB를 쓰는 것처럼 호환하고 무결성을 지키게 커스텀하게 구현되는 경우가 많다.
    • 내부적으로 Redis를 쓸 수도 있지만, 기본적으로 추상화 레이어와 규칙을 만들어 데이터 무결성을 지키는 호출을 보장하는 커스텀 캐시 서버를 둔다.
  • 성능적으로 극단적인 저장소 사용처럼 DB를 기다리지 않을 수 있다. (저장 시점 제외)
    • 캐시 서버가 DB의 처리량에 앞서 많은 부분을 커버해주고, 임시 처리 해주므로 성능에 덜 영향을 받는다.
  • 트랜잭션 관리를 캐시 서버가 대행하게 함으로써 복잡도를 낮춘다.
    • 겹칠 때만 체크해서 순서를 지켜주거나, blocking을 걸어서 지키게 할 수 있다.
  • 데이터를 캐시 서버에서 관리해주므로, 게임 서버 크래시로 인한 유실/복사 이슈에서도 유연해진다.
  • 레이어가 하나 더 해지는 만큼 관리 이슈나, 캐시 서버의 규칙, 관리 방법, 장애 대응이 추가로 필요해진다.

이 외에도 더 있을 것이다. 기본적인 접근은, DB를 저장소로만 사용하는가 로직 처리소로 사용하는가와 캐시 활용 여부 정도라고 볼 수 있다.

또한 캐시 서버나 미들 웨어의 역할에 따라 각 선택별 단점을 커버하는 기능을 중간에 끼워넣음으로써 해소하는 방법도 있다.

전반적으로 목적은 쓰루풋을 최대로 끌어올리면서 장애 요소를 제거하거나, 한가지 목적에 치우치거나 같은 선택과 집중이 엿보인다.

아무래도 DBMS는 서비스중엔 변경/확장이 쉽지 않고, 추가적인 DBMS 도입도 쉽지 않은 만큼 여러가지 측면에서 고민이 될 것이다.

이 글에서는 주로 RDB = DB라고 칭했는데, NoSQL을 메인 DB로 사용하는 접근과, 보조 DB로 사용하는 접근과 같이 나뉠 수도 있을 것이다.

현재 관점에서 접근의 연장선으로 케이스를 확장하는 관점에서 NoSQL 이야기와 함께 다음 글에서 이어가보도록 하겠다.