갑자기 유투브 관련한 감상

유투브. 너튜브. 너를 통해 나를 알게되

재택기간이 길어지면서 업무 집중에 조금 느슨해지거나 쉬고 싶을 때 가장 많이 애용하는 것이 유투브 시청. 어느 순가 이 마성의 유툽 알고리즘 세계에 빠져서 구독 채널 수가 200개를 넘겼다. 구독 채널도 많고 액티브 채널이 많다보니 하루 하루 새로 쏟아지는 컨텐츠들이 엄청나다. 그래도 시간은 한정적이기에 좀 걸러서 보게되는데, 이것들을 정리하다 보니 내 취향이 찾아진다 ㅋ

취향 찾기 위한 내 근래 채널과 키워드 상황

요새 많이 보는 구독채널

  • TWICE : 무조건 매일 기본 뮤비 감상 (Daily Batch임)
  • 덤덤_스튜디오 : 예리한 방에 나연이가 나와서 ㅋ
  • 맛있는_녀석들 : 먹방때문에 보다가 요새는 서브 컨텐츠인 운동뚱이랑 잡룡이십끼 때문에 많이 봄 ㅋ
  • 이거해조_원희형 : 조원희 처음에는 슛포러브랑 여기저기 나오다 자기채널 열었는데, 응원해주고 싶은 사람. 가야대 가야대 ㅋ. 세계 최초 은퇴형 축구 선수 (은퇴 후 몸이 더 좋아짐 ㅋ)
  • 피지컬갤러리 : 김계란 말이 필요없음. 재미있고 컨텐츠 들이 유익함.
  • 딩고_뮤직 : 킬링벌스 컨텐츠가 너무 좋다. 요새 노동요 채널. 음악 자체로 접근되는 컨텐츠들이 많아서 진짜 좋다.
  • 놀면_뭐하니? : 라섹때부터 구독하다가 요새 다시 싹쓰리 때문에 ㅋ
  • 비보이의_세계일주 : 비보이인 브루스리의 채널. 오늘 세계여행 마지막회 했는데, 정보가 부족한 국가들의 모습들을 보는 재미. 나도 젊었으면 해보고 싶은 혼자여행 대리만족용 ㅋ. 요새는 방구석 춤선생 컨텐츠가 좋다
  • 오분순삭 : 무도의 향수 ㅋㅋ
  • 노홍철 : 아직 영상이 3개 뿐이고 구독하지 말라니까 구독하게됨 ㅋ. 이미 10만 구독자 넘김. 유툽 열심히하는 정준하의 소머리국밥은 아직 5만인데 ㅋㅋ 정준하 애도.
  • 정육왕 : 고기고기고기 침줄줄흘리는 채널. 고기 굽기 스킬도 구경하고 고기 고르는 법도 배우고 고기로 시작과 끝.
  • 단희TV : 부동산 재태크 관련 정보. 차분하게 설명해주는 팁들이 귀에 쏙쏙!!
  • 김한용의_MOCAR : 김한용기자의 차분하면서 엉뚱한 멘트들이 재미있다. 리뷰 자체는 좀 자본주의 편향적인 느낌이지만 그래도 유익한 점이 많음

요새 자주 찾는 키워드

  • 트와이스 - 무조건 ㅋ 이젠 안본 영상이 없지만 다시 봐도 좋음
  • BTS - BTS 뮤비들과 각종 리액션을 보면 멋있고 국뽕 주입됨
  • tesla - 테슬라 관련 리뷰와 정보 수집을 위해. 나도 곧? ㅋ
  • 김하온 - 고등래퍼2부터 예능까지 쭉 봄. 시작은 “트와이스 김하온” 영상 보고 누구지? 하고 찾아봤다가 실력에 ㄷㄷㄷ 그리고 딩고 뮤직에서 킬링벌스 듣고 우와
  • 세정 - 유툽 알고리즙이 이끌어준 키워드. 내 아이들 입덕 시작점 IOI때부터 근래 영상들 보니 역시 내 원픽. 노래들도 너무 좋구만

뜸해진 채널

#영국남자 #졸리 - 한국에 오지를 못하니 컨텐츠가 고갈되가는 듯. 안타깝다. 과거 영상만 가끔 봄. 내 최애 채널이었는데… #Loud_G - 김민아 아나운서 때문에 자주 보다가 요새 공영방송에서 이미지가 바뀌다 보니 아수라백작 처럼 두가지 캐릭이 왔다갔다하니 몰입감이 떨어져서 김민아 위주 컨테츠 방송은 재마가 떨어지고 있는 느낌. 그래도 이미지 바꾸고 온앤오프 정도의 캐릭터 딱 좋음. 라운드쥐는 이제 안녕. #카라큘라 - 우파TV보고 동정의 구독으로 시작했다가 열심히 하는 모습에 꾸준히 봐았음. 근데 컨텐츠가 너무 비슷해지면서 지루해져감… #야생마TV - 입이 거칠고 최강마초 캐릭터. 캐릭터가 특이해서 이상하게 자주들어가서 보다가. 마찬가지로 컨텐츠가 좀 거기서 거기라 잘 안들어가짐. #우파푸른하늘 - 슈퍼카리뷰 위주의 컨텐츠들과 마이너 차량 유투버들과 연계되서 돌고돌면 여기로 왔던 채널. 여기도 컨텐츠가 좀 지루해짐. #동네축구고수_동고 - 코로나 때문에 스페인에서 돌아온 이후에 조금 시들해짐.

내 채널 취향 분석

주로 팬심에 의한 트와이스 관련이 대부분이고, 연관으로 K pop 컨텐츠와 음악 관련 영상들이 많다. 그중 걸그룹 영상이 제일 많지만, 과거 힙찔이였던 관성 때문인지 좋은 힙합음악들도 하나 꽂히면 줄줄이 찾아보는 느낌. 그 다음은 예능 영상들 단순 예능도 많이 보지만 요새는 운동(헬창, 축구)+예능 영상위주로 보고 있다. 뭔가 운동을 직접 내가 해야하는데 대리 만족 하는 느낌..와이프가 싫어하겠다. 운동안하고 남이 운동하는걸 보고만 있다니.. 그래서 대강 순서는 아이돌 > 음악 > 운동예능 > 먹방 > 예능짤 > 자동차 > 재테크 인 듯.

내 홈 큐레이션을 처다보면서 드는 유툽 추천 알고리즘에 대한 잡생각

괜찮은 영상에서 구좋알 영상 이야기하면 아무 생각 없이 누르다 보니 내 구독 리스트가 너무 많아서 요새는 홈에 나오는게 구독채널 신규 영상때문에 나온건지 알고리즘 추천인지 알 수 없는 상황. 그래도 뇌피셜로 비중 나래비를 해보자면. 구독 채널 중 자주 가는 채널의 신규 영상 포함해 보지 않은 영상들이 제일 비중이 크고, 최근 본 영상과 관련된 것들이 그 다음, 그 중에서도 내가 보지 않은 영상들 위주로. 결국 내가 본 영상들은 홈에서 잘 안보이지만 관심사의 영상이 나오기 때문에 어디를 보다가도 다시 홈에 오면 볼게 넘친다. 재시청하고 싶은 컨텐츠는 검색이나 구독, 좋아요 누른 영상 등의 방법으로 접하게 하고 홈은 가능한 안본 영상으로 뿌려주는 것이 유툽에서 도망가지 못하게하는 방법인 듯. 넷플릭스는 아직 중복이나 이미 본게 너무 많이 홈에 나와서 그런 정도는 잘 안되고 인스타 검색 첫 화면도 중복이 많다. 그래서 그런지 넷플릭스는 막 신규로 보는게 별로 없다. 어쩌면 넷플릭스는 호흡이 긴 영상들이다보니, 가능한 내가 보는 영상의 신규 에피소드로만으로도 유지가 되는 듯. 근래에 내가 하는 업무가 컨텐츠 추천과 관련되서 그런지 추천 알고리즘이 관심이 많은데 알고리즘의 결정은 컨텐츠보다 해당 컨텐츠를 소비하는 사용자의 패턴과 회사가 원하는 사용자의 소비 패턴 위주로 만들어져야 승산이 있는 것 같다.

…나도?

갑자기 유투브를 보다가 채널이 많아져 쳐다보다 블로그 까지 남기게 되었는데 결국 뻘소리만 남기다 끝난다. 근데 자꾸 보다보니 blog 말고 vlog를 나도 시대에 맞춰 시작해봐야하는 것 아닐까 하는 생각이 든다. 컨텐츠를 정하고 시작하지 말고 그냥 기록차원의 vlog를 나도 해볼까?

---------------------------------------- "갑자기 유투브 관련한 감상" 끝 ----------------------------------------
학습은 개발자의 숙명.. 그렇다면 해야할 것은?

많다 많아 정말 많아. 가지를 좀 치자

푸념 중의 푸념. 요새 기술이 너무 쏟아진다. 진심으루다가 너무 많다. 근데 막상 현실에서 내가 일하는데 필요한 것들은 몇 가지 없다. 어쩌면 적용해보고자하는 확신이 들 정도로 학습되지 못한게 많기 때문일 수 있다. 학습은 개발자의 숙명이라지만 나이가 들며 학습 시간이 가족과의 시간으로 대체되면서 물리적으로 예전만 못하다. 예전에는 그래서 이것저것 다 기웃 거려볼 수 있었지만, 이제는 좀 선택과 집중을 해야할 시기이다. 그러면 어떤 기준으로 공부해야할까.

과거에 학습은 그냥 유행하는 것, 주변에서 버즈워드 될 만큼 많이 이야기되는 것 위주로 공부를 해왔던 것 같다. 한 마디로 트랜디한 기술들 위주로 공부했던 것 같다. 그러다 보니 정말 나에게 필요한 기술인지에 대한 고민없이 흘러왔다. 물론 주변인들에게 듣는 기술들은 아무래도 같이 업무가 겹치는 분들이라 그런지 또는 얻어 걸린건지 괜찮은 학습결과를 보이기도 했다. 문제는 전혀 쓸일도 없고 하면서 흥미도 가지지 못한 것들이다. 이런 것들은 뺴야한다. 결국 학습 결과가 좋은 경우는 학습하고 현업에 사용하게 되는 경우다. 그래서 내가 살고 있는 서버 개발자 필드안에서 사용될 만한 기술들로 일단 좁혀보자. 여전히 이 범위는 광범위하다. 개인적으로 학습은 미래지향적이 빠지지 않았으면 한다. 어차피 공부하는 것이라면 선수 학습을 해보고 싶은 욕심을 지우고 싶지는 않다. 남들 보다 더 아는 것에 대한 욕심은 나름 학습 동기부여도 되기 때문이다. 자 이제 할일은 대강 그려진다. 학습거리를 선정하기 위해 내가 살고 있는 서버기술 동네의 흐름을 한번 그려보고 그에 따라 내가 앞으로 사용하게될 것들에 대한 예상을 해보고 내 학습 기준을 만들어보자.

이 바닥 구른지 벌써…경험과 뇌피셜로 생각해보자

과거의 경험에서부터 현재의 경험까지를 돌이켜 보자면 예전 서버기술이라함은 웹페이지를 서빙하는 기술인 웹기술들이었는데, 이 시기에는 물리장비 하나에 프론트/백이 모두 포함된 작은 웹서비스들이 많았다. 트래픽이 많은 서버들 역시 장비를 많이 idc에 늘려서 트래픽을 감당했던 걸로 기억한다. 이후 ajax가 퍼지면서 front/back이 나눠지고 이것이 웹개발자를 프론트개발자, 백엔드개발자로 나뉘어지는 계기를 가져왔다. 이때 ajax, jaxb, soap, rest 등등 api 지원하는 기술들을 공부했고 이후 servlet으로 불편했던 여러가지 필요 라이브러리가 묶이며 spring이 나왔었다. 지금까지도 롱런하면 계속 학습하는 Spring만 보더라도 기술은 항상 필요에 의해 만들어졌다. 앞으로의 흐름에서 어떤 것들이 필요할 것 같고 그에 맞춰 나온 기술들을 찾아봐야겠다는 생각이 들었다.

클라우드의 시대로 넘어오고 서버 인스턴스의 관리 기술들이 복잡도를 가지면서,DEVOPS와 SRE란 영역이 서버개발자 영역에서 분리되기 시작했다. 서버기술이 점점 전문성이 강화되는 방향으로 가고 있고 난 어플리케이션 레벨의 서버개발이 좋기 때문에 SRE영역의 학습은 좀 가지를 쳐도 되겠다. 하지만 DEVOPS 영역의 빌드/배포 관련해서는 개발과 떨어지기 어렵기 떄문에 관련 지식은 계속 습득해야한다. 빅데이터 / ML 역시 서버 개발자들에게 하나의 영역으로 다가왔지만 이것 역시 전문영역으로 빠져나가면서 다 쫓아가기에는 무리가 있어 어플리케이션 개발시에 필요에 따라 학습해야겠다는 정도로 생각하고 가지를 치자.

클라우드 덕분에 서버의 복잡도를 늘리기 쉬워지면서 MSA가 나왔다. 이로 인해 복잡한 서버 연결도를 관리하는 기술들이 쏟아지고 MSA에 효율적인 서버 아키텍쳐들과 프로그래밍 기술들이 쏟아지는게 지금으로 느껴진다. MSA로 인해 하나의 서비스에서 관리하는 서버의 양이 늘어나면서 복잡도 뿐 아니라 당연히 늘어나는 것이 비용. 클라우드가 유동적인 리소스 관리를 해주지만 하나의 서버안에서의 효율성 증대는 다른 이야기다. 이로 인해 나오는 프로그래밍 기술이 비동기 프로그래밍. 결국 지금 내가 한동안 껴안고 가야할 학습 영역은 아래 처럼 나눠질 것 같다.

현 시대에 따른 나의 학습 카테고리

  • 숲의 영역 : MSA 아키텍처 관련 기술군
  • 나무의 영역 : Application의 성능 향상을 위한 기술
    • 비동기 프로그래밍 :
      • Reactive : RxXXX를 통한 대부분의 언어를 커버하는 디자인이 가능한 장점
      • Coroutine : Rx보다 러닝커브가 작고 리소스 효율성 측면에서는 더 나을 것 같다, 단 Kotlin만 가능. (Go도 가능..Go 를 공부해야할까..고민)
    • 함수형 프로그래밍 : 비동기 프로그래밍은 결국 Thread Safe이 중요하기 때문에 순수 함수 / Immutable이 중요한 FP가 대세가 될 것 같다
    • 서버 프로그래밍
      • Webflux : Rx, Coroutine 두 가지 다 해야할까. 이것도 선택과 집중이 필요.
    • 서버 개발자 교양
      • GraalVM : Java로 만들어진 Virtual Machine. GraalVM 사용으로 성능이 개선되었다는 글들이 종종 보여. 관심이 간다. Java로 만들어졌으니 코드도 구경하자

그래서..결론은

호기심에 접근했던 다른 것들 보다 일단 위의 내용들부터 내가 만족할만한 수준이 될 때 까지 공부하려고 한다. 그리고 나서 ML이나 React/Flutter 처럼 호기심에 접근하는 것들을 해야겠다. 시간을 효율적으로 사용하기 위해 선택과 집중!. 그래서 요새 계속 보고 있는 Webflux부터 끝내자.

MSA와 비동기 프로그래밍의 시대 뒤에는 뭐가 있을까? 아마 0/1 바이너리 시대를 끝장낼 양자 컴퓨팅 관련 기술들이 나오지 않을까 생각한다. ML을 내가 공부해야할 영역에서 분리해냈지만, 앞으로 대부분의 서비스들이 AI관련 서비스로 변경될 것 같다. 이에따라 서버에서는 실시간 학습, 피드백이 필요해질 것이고 결국 양자컴퓨팅 기술과 거기에 연관된 확률성 프로그래밍의 시대가 올 것 같다. 아마 다음에 다시 학습 리스트를 짠다면 요것들을 포함하게 되지 않을까?

재택근무를 하면서 뭔가 학습이 흐지부지 되는 경향이 있는데 채찍질겸, 블로그 심폐소생겸 혼잣말 쓰기 끝.

---------------------------------------- "학습은 개발자의 숙명.. 그렇다면 해야할 것은?" 끝 ----------------------------------------
Webflux 공부하자 1편

또 Webflux 공부?

지난 글을 쓰고 무려 1년 동안 제대로 보지 못하다가 다시 공부하면서 그 내용을 정리한다. 이전에는 정리 없이 우수수 글만 읽고 써보지를 않아 금방 잊었지만 이번에는 좀 더 부분부분 찾아서 이해하며 정리해보자.

Webflux와 Reactive Streams

Spring Webflux 문서를 보면 Reactive Stream에 대한 이야기가 나온다. 이 둘의 관계가 무엇인지 알아보자면, Spring의 Webflux에서 사용하는 reactive library가 Reactor이고 Reactor가 Reactive Streams의 구현체이다. 그래서 Webflux 문서에 Reactive Streams가 언급되는 것이고 그거와 같이 Reactor가 나오고 주요 객체인 Mono / Flux가 나오는 것이다. 결국 Webflux의 동작 구조를 이해하는 기본에는 Mono / Flux의 이해가 필요하고 그 이해를 위해 Reactive Streams에 대한 이해가 필요하다. 줄줄이 사탕이다.

Reactive Streams?

Reactive Streams 공식 문서에 정의된 문장을 그대로 본다면 Reactive Streams은 논블럭킹 백프레셔(non-blocking backpressure)를 통한 비동기 스트림 처리 표준을 제공하기 위한 명세이다. 이 영어같은 한국어에 대한 내용은 라인 엔지니어링 블로그에 너무나 잘 설명되어있다. 그 중 backpressure가 중요한 개념으로 이해가 꼭 필요하다. 이 철학적이면서 사명감을 가진 비동기 스트림 처리를 위한 명세는 의외로 매우 간단하게 4개로만 정의되어있다.

  • Publisher : 경계가 미확정된 순차적 데이터들을 생성하는 컴포넌트
    • Publisher는 Subscriber의 onNext호출 토탈 횟수는 반드시 Subscription의 request 횟수를 초과해서는 안된다. (#제1번 룰)
      • 이것이 어떻게 지켜지는지에 대해서는 실제로 코딩을 해보면서 확인해야할 것 같다.
  • Subscriber : 순차적 데이터를 받아서 처리하는 컴포넌트
    • onSubscriber -> onNext* -> (onError onComplete)? 순서로 시그널을 받게된다.
  • Subscription : Publisher에 의해 발행되는 구독 정보 컴포넌트
    • request에 의해 backpressure가 가능.
  • Processor : Publisher/Subscriber Stream의 미들웨어. Publisher이면서 Subscriber

기본적인 Observable 패턴과 유사해 이해가 어렵지 않다. 차이가 있다면 Subscription의 request메소드를 통해 backpressure 기능이 탑재되어 있다는 것이다. 탑재라기보다는 backpressure가 가능하게 interface가 있고 우리가 사용하게될 Reactor에서 구현이 되어있다. Reactive Streams 다 봤다. 참쉽죠잉(!?) 막말. 일단 넘어가자 갈길이 멀다.

Reactor

결국 원하는 것은 Webflux의 제대로된 이해를 통해 프로젝트에 적용 필요 기술인지 확인하고, 어떻게 잘 녹여낼 수 있을지를 알아내는 것이다. 그래서 그 코어기술인 Reactor를 들여다 본다. 참고자료에 링크된 튜토리얼 강좌를 따라해보자.

Flux / Mono

Reactor 관련된 여러 글들을 보면 Flux / Mono가 가장 중요한 컴포넌트로 나온다. Reactive Stream은 기본적으로 비동기 프로그래밍 표준을 목표로하고 있기 때문에 그 구현체인 Reactor는 비동기 지원을 위해 대부분 함수형 프로그래밍 형태로 구현이 되어있다. Mono / Flux를 처음 만났을때 FP의 Functor와 비슷하다고 생각들었다. 그래서 Reactor에서 FP의 향기가 진하게 난다.

  • Flux
    • 0..N 의 데이터를 만들어내는 Publisher, 생성데이터가 0개 또는 1개로 명확하게 구분되는 것은 Mono로 사용되어야한다.
    • Data의 흐름 단위, Complete / Error가 되기전까지는 무한 생성 가능한 Stream으로 생각해야함.
    • javadoc에 보면 모든 함수 동작들이 그림으로 설명되어있어 더욱 이해가 쉽다.
    • static 메소드는 소스 생성에 관련된 것들이고, instance 메소드들은 비동기 파이프라인 처리를 위한 것들로 구분되어있다.
    • 메소드가 너무 많기 때문에 나에게 필요한 것을 찾기위한 문서도 존재. 개발하면서 method 찾기 entry로 javadoc에서도 이걸로 접근하길 추천함
    • 주의할 점 : 기존 동기 코드에서는 애러처리가 try/catch 등으로 간단히 처리되었지만 비동기 코드에서의 애러처리는 Error event를 받아서 처리해야한다. 이게 데이터 흐름 중 애러가 발생하면 해당 Flux는 종료되는 것이기 때문에 이전에 받은 데이터들에 대한 처리등을 생각해야하기에 처음 설계시 애러처리도 정확히 고민되어야한다.
  • Mono
    • 0..1 의 데이터를 만들어내는 Publisher
    • 1개의 데이터를 처리하는 Flux를 Mono로 변경이 가능하고 Mono 역시 1개 초과로 처리시 Flux로 변경이 가능하다.

StepVerifier

StepVerifierreact-test에 포함된 테스트용 클래스다. 이것을 이용해서 Publisher가 우리가 원하는 형태로 동작하는지 테스트 가능하다. 지원되는 기능이 매우 다양한데 그 중에 가장 마음에 드는 것은 VirtualTimeScheduler 기능인데 이것을 이용하면 긴시간의 주기로 동작하는 Publisher를 주기변경해서 테스트 가능하다. 다시말해 하루에 한번 배치동작하는 것을 설정을 통해 시간을 무시하고 동작하게 해서 테스트 가능하다. 과거에 테스트를 위해 실제 코드에 주기 변경하는 코드를 넣고 테스트를 했었는데 그럴 필요가 없다!

Transform / Merge

개발자들이 하는 것은 결국 데이터를 입력 받고 원하는 형태로 전달하고 가공하는 것이다. 여기서 전달을 위한 입출력 pipeline은 구축이 되었고 이제 가공하는 것들이 필요하다. fp에서 자주 접하게되는 map/flatMap가 Mono/Flux에서 제공이 되고 merge, concat등의 merge operation도 제공이 된다.

Request

서두에 이야기했던 Reactor가 단순 pub/sub과 다른 점. 바로 요 backpressure 기능이다. Subscriber는 Publisher에게 현재 처리할 수 있는 data 갯수를 알려줘서 해당하는 만큼만 받아올 수 있다. 이것은 Subscriber의 부하가 몰리는 것을 방지할 수 있다.

Error

Reactor에서 Error 역시 Event로 Signal을 받아 처리가능하다. onErrorReturn, onErrorResume등의 메소드로 Error Signal을 받았을 때 처리하거나 propagate를 사용해 stream 내부 map에서 처리하던 것을 상위로 전달 할 수 도 있다.

techio 후기

아주 간단한 code snippet들을 짜보면서 기능들을 익히는 것에 도움이 되었고, 일부러 javadoc을 찾아보게 해주는 점이 좋았다. 하지만 방대한 api 중에 일부만 다루다보니 결국 Reactor적응을 위해서는 작업하면서 다양한 상황을 경험하며 함수들을 써봐야할 것 같다. 그리고 Flux, Mono javadoc에 나온 그림 기호들에 빨리 익숙해지는 것이 나중에 메소드 찾기에 도움이 될 것 같다. 이제 Reactor Tutorial이 끝났으니 Webflux로 돌아가보자.

참고자료

---------------------------------------- "Webflux 공부하자 1편" 끝 ----------------------------------------
OpenTracing? OpenTracing!

OpenTracing? 넌 누구니. 왜 필요하니.

이 글은 팀에 OpenTracing을 소개하기 위해 작성했던 글로 일부 내용 수정해서 블로그로 옮겨왔습니다. 

이제는 대부분 서비스들에게 분산 인프라 환경이 보편화되면서 복잡도를 가지게된 인프라 환경의 모니터링과 운영 이슈 처리에 관심을 많이 가진다. 그래서 분산 환경에서의 디버깅용 로깅을 어떻게 하면 좋을까 찾다가 알게된 OpenTracing에 대해 정리해보았다.

OpenTracing이란

이 공유에서 언급될 OpenTracingCNCF 산하의 프로젝트로 단어 그자체 처럼 하나의 흐름을 공개적으로 추적하기 위한 기능을 표준화하는 프로젝트입니다. 아직 공식적인 OpenTracing의 표준은 존재하지 않고 CNCF가 가장 큰 기구이기 떄문에 Cloud환경의 입김(question)이 쎄서 주목받는 비공인 표준 입니다. 현재 해당 OpenTracing spec을 기준으로 만들어지는 Tracer들로는 Zipkin, Jaeger, Lightstep등 다양하게 존재합니다. (CNCF사이트 supported tracer목록에 zipkin이 빠져있는건 왜 그런지 궁금하네요)

이제는 서비스를 운영하는 대부분의 서버 개발자들에게 MSA가 보편화되고 여러 효율성 관점에서 도입되고 있을 때, 이런 분산 환경의 로깅이 주목을 받으며 OpenTracing이 그 방안으로 나오고 있습니다. 사실 OpenTracing은 분산 환경의 로깅을 목적으로 만들어진 것이 아니고 하나의 request에서 response를 반환 할 때까지 거치는 서버의 연결 점들을 추적하기 위한 시스템으로 시작된 것이었습니다. 이 서버 연결을 추적하다보니 자연스럽게 해당 서버들의 req/res latency를 확인할 수 있게되어 bottleneck check가 가능하게 되고 그 서버 안의 로그까지 합쳐져 코드레벨의 디버깅도 할 수 있는 기능들도 들어가게되었습니다.

표준화를 시도하는 이유는 OpenTracing Big Piceces(위 그림)에도 설명되어있지만 OpenTracing이 표준화된다면 우리 서비스의 분산 로깅만 가능한 것이 아니라 우리 서비스에서 외부서비스로 연결시 최초 요청의 trace id로 연결되어 모든 연결 구조의 추적이 가능하게 됩니다. 예를 들어 빌링 서비스도 우리가와 모두 같은 tracer 기준으로 opentracing 을 설정한다면 장애발생시 좀 더 빠르게 확인이 가능하곘죠? :-)

왜 OpenTracing을 갑자기?

현재 (회사)팀의 환경에서 운영중인 서버가 단일 서버군을 참조하는 것이 아니고 요청에 따라 복수개의 서버군을 거치게 됩니다. 이럴 때 서버간 로그가 통합관리되는 것이 아니라 문제에 대한 디버깅시 로그를 각자 찾거나 bottleneck을 찾기하는 활동을 할때 각 서버의 로그들을 따로 뒤져봐야하는 불편함이 있습니다. 거기에다 각 서버별 로그를 연결된 reqeust/response를 찾아서 보는 것이 정말 쉽지 않은 일입니다. 앞으로 팀에서 관리하는 서버군의 복잡도가 올라가고 분산 환경이 더 커지게 될 수록 디버깅 작업의 난이도가 올라가고 개발자의 피로도가 올라갈 것이 예상되기 때문에 이러한 작업이 진행되면 차후 편안한 디버깅 생활을 할 수 있을 것 같아 도입검토가 필요합니다. 물론 디버깅할 일이 없게 코딩을 잘하면 좋겠지만 현실은….ㅋ. 최근 팀에서 검토중인 istio가 팀에 녹아들거나 한다면 더욱 편하게 OpenTracing환경을 도입할 수 있게됩니다.

OpenTracing Overview

OpenTracing spec에 대해서 간단하게 오버뷰를 해봅니다. 우선 OpenTracing에서 사용하는 Data Model들을 먼저 이해하면 관련 문서를 참조시 도움이 될거라 간단하게 짚어봅니다.

  • Span
    • OpenTracing Data Model의 기초 단위로 시작과 끝을 가지는 Timeline block입니다. 다시 말해 시간을 측정하게되는 모든 단위를 뜻 합니다. 시간을 측정한다는 것은 측정 시작 지점과 종료 지점이 있으니 그것이 하나의 Span이 됩니다. Span은 Parent / Child 구조를 가지고 있는데 이것은 우리가 흔히알고있는 Tree Node의 Parent Child와는 조금 다르게 하나의 Span을 세부로 나누었을 때의 전체가 Parent, 하부에 포함되어있는 Span이 Child Span이 됩니다. 예를 들어 하나의 api reqeust/response가 하나의 Span이라면 그 reqeust를 처리하는 내부 코드 method call 하나하나를 Span으로 생성시 method Span은 Child Span이 됩니다.
    • Span은 하위의 attribute들을 들고 다닙니다.
      • Operation Name
      • start / finish timestamp
      • Tags
        • key/value 구조의 사용자정의 필터 값들
      • Logs
        • key/value 구조의 정보 메세지
      • SpanContext
        • 각 Tracing 구현체에 따라 달라지는 내용. 기본적으로 trace id와 span id등이 포함됩니다.
  • Tracer
    • 위 Span들에게 특정 id를 부여하여 발송하고 저장하는 시스템을 말합니다. Zipkin, Jaeger 와 같은 것들을 지칭합니다.

동작 원리

동작 원리는 의외로 간단합니다. 위에서 설명한 Span단위의 정보들을 특정 Storage서버에 전달하면 해당 시스템은 그 Span들을 Trace ID, Span ID별로 Timestamp 정렬하게 됩니다. Tracer 별로 SpanContext 구조나 형태가 다를 수 있어 Tracer별로 서로 Span을 공유하지는 못하고 컨버터들이 존재는 합니다

Zipkin Example Flow - https://zipkin.io/pages/architecture.html

OepnTracing을 통해 우리팀이 얻을 수 있는 것

  • 위에서도 언급하였지만 결국 빠른 이슈 원인 파악이 최고의 장점입니다. OpenTracing은 단순히 reqeust/response network 연결에 대한 것만 추적이 가능한 것이 아니라 Redis / MySql 과 같은 외부 캐싱서비스나 데이터베이스의 연결 까지도 추적할 수 있어 문제가 발생한 호툴이 어디까지 진입하였고 어디에서 문제가 발생하였는지 빠르게 확인 가능하게 됩니다. 장애 발생시 로그 수집 서버에 Trace ID만 찍어주면 불편하게 키바나에서 이리저리 쿼리 날리면서 로그 찾기 안해도 됩니다.
  • 팀에서 OpenTracing의 성숙도를 올리고 다른 팀에 전파가 가능하다면 타팀과의 api 협업시 이슈 해결에 더더욱 효과적이 될 것입니다. 이슈에 대한 해결 능력이 좋아지면 서로서로 윈윈이겠죠?

OpenTracing을 통해 해야할 일

이렇게 좋은 것을 왜 이제야! 그리고 왜 다른 곳은 안하지? 에 대해 고민해보면 답은 사실 간단합니다. 인프라 구조가 복잡도 + 디버깅의 어려움 vs 환경 구성에 대한 불편함 의 내적 대결 구조가 있기 때문입니다. OpenTracing 환경 구성을 위해서는 처음 설정에 노오오오력이 들어갑니다. 세상에 쉬운일은 없습니다. 하지만 조금 노오오오오력 해서 구성하면 후세에 편안함이 올거라 생각됩니다. 그래서 할일을 나열해보면…

  • OpenTracing 기술 스택 선정 - Tracer 선정
  • Tracer Server 설치
  • 각 서버군별 적합한 Tracer Library 설치 / 설정 - Spring인 경우 Sleuth + Sl4j로 떙이지만… play+jdk7, ruby..는..;;; 좀 더 찾아봐야함.

---------------------------------------- "OpenTracing? OpenTracing!" 끝 ----------------------------------------
CoroutineScope, CoroutineText, Job

CoroutineScope, CoroutineText, Job 공부해보자

사내 프로젝트를 진행하면서 cache 갱신 부분을 coroutine을 통한 비동기 동시 처리를 하고 싶었는데 coroutine의 동작이 생각했던 방향과 차이가 좀 있어서 이유를 확인하려고보니 CoroutineContext와 Scope에 대한 이해가 좀 부족한 것 같아 이부분에 대한 이해가 좀 더 명쾌해지도록 공부하고 기록합니다.

학습 목표

  • CoroutineContext, CoroutineScope, Job 간의 관계에 대한 이해
  • Coroutine의 내부 동작에 대한 이해
  • 서버사이드 Coroutine 사용시 주의해야 할 것들에 대한 정리
  • 실제 kotlinx.coroutines github repo의 코드 Tour

학습 자료

Coroutine Context and Scope

  • 뭐든 만든사람의 이야기와 동작 구조 이해하는게 우선
  • “비슷한 형태의 것들에게 사용 의도가 다르다면 그 다름을 강조하기위해 명명을 달리준다.”
  • CoroutineContext
    • 모든 Coroutine은 context를 가지게되고 해당 context는 CoroutineContext의 구현체다.
    • context는 엘리먼트들의 세트고 coroutineContext property를 통해 현재 coroutine context에 접근 가능하다.
    • coroutine context는 immutable이다.
    • 하지만 plus op를 통해서 엘리먼트들을 추가가 가능하고 이로인해 새로운 context 객체가 생성된다.
  • Job
    • 우리가 말하는 coroutine은 Job으로 표현된다.
    • coroutine의 생명 주기, 취소, 부모자식관계 등을 책임진다.
    • 현재 Job은 현재 coroutine context에서 접근가능하다.
  • CoroutineScope
    • coroutineContext property 하나만 가지고 있다.
    • context를 제외하고는 아무것도 가진것이 없다. - 여기서 문제. 왜 CoroutineContext와 CoroutineScope 명칭을 달리 존재하는 걸까!?
    • context와 scope는 의도하는 목적(intented purpose)이 다르다

  • coroutine은 launch함수를 통해 생성이 되는데 launch함수는 context를 가진 CoroutineScope의 extension 함수로 제공된다. 그리고 launch는 CoroutineContext를 parameter로 넘겨 받는다 결국 launch함수를 통해 두개의 CoroutineContext를 핸들링하게 되는데 그 이유는 새로 생성되는 Job(=coroutine)에 child context를 전달해주기 위함이다.
    • 내가 그래서 context와 scope의 이해하기로는 scope은 본인이 가지고 있는 하나의 context가 영향을 주는 범위를 한정하기 위해 정의된 것으로 보인다. launch함수 호출시 새로운 context 첨부하지 않으면 EmptyCoroutineContext가 기본으로 설정되는데 그말은 새로운 CoroutineContext추가 없이 현재 launch함수의 scope에서 해당 coroutine을 수행하겠다는 뜻이된다.
    • context가 plus되어 부모/자식 관계가 생기는 시점에 context가 합쳐지는 것은 context가 가진 엘리먼트들이 합쳐진다는 것이다. 이때 scope context의 엘리먼트보다 parameter context의 엘리먼트가 우선시된다.고 써있다.(It merges them using plus operator, producing a set-union of their elements, so that the elements in context parameter are taking precedence over the elements from the scope.)
    • 그리고 scope context에 포함된 엘리먼트들은 parent로서 새로 생성되는 child context에 상속되어 사용할 수 있게된다.
    • 결국 scope는 context영역의 한정과 새로운 context에게 상위 엘리먼트의 상속을 가능하게 하기위한 의도(intended purpose)로 만들어진 것이다.
    • 이것을 보고 이해가 되기 시작했는데 궁금점이 생긴다. 그럼 CoroutineContext가 가지고 있는 엘리먼트들?은 과연 어떤 것들일까?(나중에 코드 까보면서 확인해보자)
  • 함수 반환 이후에도 백그라운드에서 동작하는 coroutine을 수행하고 싶다면 CoroutineScope에 확장함수를 만들어서 수행하거나 CoroutineScope을 넘겨 받아 수행하도록 하면 된다. 해당 함수는 suspend로 만들어선 안된다.
fun CoroutineScope.doThis() {
    launch { println("I'm fine") }
}

fun doThatIn(scope: CoroutineScope) {
    scope.launch { println("I'm fine, too") }
}

The reason to avoid GlobalScope

  • GlobalScope가 백그라운드 작업 수행을 위해 서버사이드에서는 간편한 기능으로 쓰고 싶은데, 담당 개발자가 떡하니 이런 글을 써놓았으니 읽어봐야겠다.
  • GlobalScope의 Dispatcher와 launch 기본 Dispatcher는 Dispatchers.Default로 동일 해서 마치 두개의 launch 함수 동작은 동일 할 것 같지만 실제로 runBlocking 하위에서 동작해보면 둘의 동작이 다르다.
  • 결국 Scope의 lifetime 관리를 쉽게할 수 있느냐의 여부. GlobalScope를 사용하면 이전 scope 연계를 무시하고 바로 global coroutine에서 동작하기 때문에 해당 coroutine으로 생성된 job으로 lifetime 관리를 해주지 않으면 원하는 동작결과를 얻을 수 없다.
  • 하지만!! 굳이 job control이 필요 없다면 쓸 수 있지 않을까?
    • App 개발자인 경우 Coroutine에서 UI resource release등의 작업을 처리하는 일이 있어 해당 작업이 leakage를 일으킬 가능성 때문에 중요하지만 서버사이드 작업에 resource release같은 작업이 없다면 문제 없을(question) 것 같다.
    • 테스트를 좀 해보자.

코드 Tour

  • CoroutineScope.launch - (Builders.common.kt)
    • 로만 블로그에서 설명하는 Scope, Context 구분 통합의 중요한 함수 코드
    • CoroutineScope.newCoroutineContext - (jvm/src/CoroutineContext.kt)
      • 실제로 context를 합치는 동작을 하는 곳
      • multi platform 지원을 위해 js/jvm/native 로 분리 구현되어있다. 일단 jvm만 먼저 보자.
    • CoroutineContext.plus operator - (stdlib/CoroutineContext.kt)
      • 실제 Context 통합 코드. Element interface도 가지고 있음.
      • CoroutineContext iterface 파일은 stdlib에 들어있고 op를 제외한 CoroutineContext의 실제 구현 함수들은 모두 kotlinx.coroutine에 들어있다. 라이브러리 의존성 이슈 때문에 이렇게 해놓은 것 같다. 신기한 패턴이다. 참고
      • 그래서 CoroutineContext.kt 가 kotlinx에도 있는데 이건 actual 구현만 있음.
  • interface Element
    • context가 들고 있는 것들.
    • Element도 CoroutineContext interface를 상속했음.
    • 코드를 찾아보니 아래의 것들이 Element를 상속해서 만들어짐
      • ThreadContextElement
      • AbstractCoroutineContextElement

사용시 주의 사항

  • 아직 확인 못함 ㅋ (TBD)

결론

원래 하고자 했던 작업은 stale cache의 refresh를 서버 request/response latency loss 없이 백그라운드에서 처리하는 거였다. GlobalScope는 kotlin에서 predefined된 Scope여서 나 말고도 여기저기서 쓸 수 있다는 가능성이 있어보여 결국 Cache 서비스만의 Scope를 object로 생성해서 사용하는 방법을 채택해 테스트 해보려고 한다. 하지만 위에서 살펴본 것처럼 해당 Scope로 만들어지는 coroutine들에 대해서 job 처리를 따로 해줘야하는데 refresh에 대한 것이 fail over 로직도 따로 있고 loss가 있는 부분이 없어보여 job처리는 skip한다. 아직 필요성을 못찾음.

---------------------------------------- "CoroutineScope, CoroutineText, Job" 끝 ----------------------------------------