DEVIEW 2023 - Day1

2023-02-27 ~ 28 에 진행했던 deview day01 컨퍼런스 내용을 간단히 정리했다.
내용은 좋았다고 느꼈지만 당장 내 실무에 도움된다기 보단 그냥 시야를 넓히고 이런게 트랜드구나 하는 걸 들었다는거에 의의를 두기로 했다.

네이버 검색은 어떻게 나보다 더 내 의도를 잘 아는가? - AiRSearch 반응형 추천

네이버는 현재 사용자 의도에 적합한 검색 결과를 제공하도록 여러가지 추천 알고리즘이 적용되어있다.

사용자 의도를 이해하는 방법은 크게 두가지가 있는데

  1. 검색어
    • 사용자가 질의하는 검색어(쿼리)를 통해 의도를 예측한다.

    • 검색어 입력 후 탐색과정에서 관심사와 검색의도를 더 구체화할 수 있다.

      ex) 제주도여행 검색 후 결과목록
      Doc1 : 제주도에서 고등어가 맛있는 식당 추천
      Doc2 : 12월 제주도 겨울 여행
      Doc3 : 제주도 가파도 배시간 & 올레길 추천

      여기서 3번째 문서를 클릭했다면 사용자의 관심사는 고등어 보단 가파도에 있다는걸 파악할 수 있다.

    • 사용자의 행동을 바탕으로 의도를 이해하고 반응형으로 컨텐츠를 빠르게 제공하는데 여기서 반응형 추천결과란 선택한 문서를 읽고 다시 뒤로가기로 나오게 되면 관심사와 유사한 다른 문서를 추천하는 방식이다.

반응형 문제를 해결하기

  1. 반응형 문제 정의
    • 언제 / 어디서 / 누구에게? : 네이버 통합검색에서 문서를 소비하고 돌아온 사용자들에게
    • Input : 사용자의 행동 기반 정보들 (검색어, 클릭한 문서, 사용자의 취향 정보 등)
    • Output : 추천 문서
    1. 반응형 문제의 특징으로 검색 사용자의 2가지 탐색 패턴이 있다.
      1. 국내여행 → 제주도 여행 검색 후
        1. Narrow-Down → 가파도
        2. Side-By-Side → 일본여행
    2. 두번째는 검색과 추천의 융합이 필요하다.
      1. 검색어를 통해 단순히 결과를 보여주거나 클릭한 문서 기반의 추천문서만 보여주는게 아니라 검색어와 클릭한 문서를 종합하여 추천문서를 보여주려고 한다.
  2. 반응형 문제를 정의하였다면 이제 이러한 문제를 해결하기 위한 모델을 만들었다.
    • Input : Query, Clicked Document, User
    • Retriever : 각 요소를 기반 검색
    • Ranker : 결과 정렬
    • Output : 추천문서 제공

반응형 문제를 풀기 위한 3가지 방법

  1. Intent Query
    • 클릭한 문서의 제목에서 핵심 의도를 표한하는 키워드 찾기
    • 네이버 언어모델을 기반으로 (Q1,D1)와 적합한 Intent Query의 유사도가 높도록 학습
    • 검색 세션 로그 (Q1,D1) → Q2를 이용해서 학습 데이터를 구축하고 의도를 벗어나는 Q2는 예외처리
  2. Intent Walker
    • 그래프 기반으로 사용자 의도에 맞는 문서 추천하기
      • 클릭로그 활용 탐색과정 그래프 생성
    • 유저가 클릭한 문서를 시작점으로 아래의 방법들을 실행
      • 랜덤 워크 수행
      • Label Propagation 수행
      • (검색어-클릭문서), (세션ID - 클릭문서) 페어 등을 기반으로 GCN 임베딩 학습
  3. User Preference
    • 유저의 취향에 적합한 문서를 추천
      • Q1) 후드집업 + D1) 오버핏 후드집업 양털후리스 + U1) 30대 남성
        • D2) 너무 편한 남자 후리스 추천 모음
      • 추천 문서에 대한 long-term의 세대성별 선호도를 ranker의 feature로 추가
      • 개인의 short-term 관심사 기반한 개인화 랭킹 연구 중

반응형 서비스 개발을 위한 꿀팁

  1. 문제를 작게 정의하기
    • 이런경우 막연하게 풀고 싶은 큰 문제를 구체적이고 해결 가능성이 있는 작은 문제부터 시작한다.
    • ex) 사용자의도가 Narrow-Down vs Side-By-Side ? → Narrow-Down만 있다고 가정
      대상 문서 : 여행 등 일부 카테고리 한정
    • 작은 문제의 장점은 아래와 같은데 애자일 방법론과 유사한거 같다.
      • 빠른 개발
      • 문제 해결 가능성과 효과 검증
      • 사용자의 피드백으로 문제 확장
  2. 학습데이터 잘 구축하기
    • (Q1, D1) → 후보 D2의 적합도 [0,1] or [1,2,3,4]
      • 서비스 출시 전 소량의 Manual 평가를 거친 후
      • 서비스 출시 후 사용자 피드백을 기반으로 생성한다.
        (ex) 클릭 받으면 1, 아니면 0)
        • 단점?어려운점? : 모델 bias(편향), 클릭은 없지만 적합 문서
        • 개선 아이디어로 CTR 역전되면 Negative
          • 검색에는 Rank Bias 존재
          • 하위 노출 문서보다 CTR이 낮은 상위 문서는 매력이 낮을 가능성 있음
          • D1,D2 보다 D3의 CTR이 높은 경우 D1,D2 는 Negative 후보
      • positive set과 negative set을 후보로 모아서 Manual그룹평가와 비교하여 신뢰도를 측정하고 최종 선정된 Top3 학습데이터 weekly 생성한다.
  3. ABTest 활용하기 : ABTest 결과를 바탕으로 최종 검증 모델로 서비스 적용
  4. 지표 모니터링하기 : CTR, 체류시간, LCR(마지막 클릭 비율) 등을 모니터링하여 사용자 만족도 측정 및 모델 개선 참고

눈으로 보며 듣는 음성 기록, 클로바노트 서비스의 웹 기술 톺아보기

클로바노트 2.x 버전에서 지원할 웹기술을 살펴보자

  1. 오프라인 지원
    • Progressive Web Application(PWA) : Next.js +next-pwa + workbox로 구현
    • 오프라인 API 중 R-Get을 제외한 나머지 C-Post U-Put,Patch D-Delete 는 불필요하여 막았고 LocalStorage와 IndexedDb를 활용하였음
    • 가장 큰 골칫거리는 Update 후 Deploy 방식이었는데
      • 주기적인 최신버전 체크, 수동 업데이트 진행, 강제 업데이트 로직 추가, 에러 시 SW 제거 및 해당버전 재설치 방지 등을 통해 처리하였음?
    • PWA의 Service Worker의 XHR cache에 XHR 분기를 추가
      • [API Wrapper - [Fetch, Axios, Socket.io]] → Service Worker
  2. 반응형 지원
  3. ZOOM 서비스 연결
    1. 줌에서 제공하는 API 중 REST API와 WebHook을 이용하였음
    2. 회의 시작, 종료에 맞춰서 훅을 받아서 녹음파일을 업로드하여 노트를 생성하도록 자동화 하였으나 좀 더 직접적인 도움을 주고 싶었음
      1. 실시간으로 가져오도록 구현해보자
      2. ZOOM의 라이브스트림을 통해 다른 플랫폼으로 스트리밍을 제공하는데 이것을 활용 - RTMP(Real Time Message Protocol)
      3. 이를 위해 백엔드 서버가 필요했는데 Node-Media-Server 가 원하는 스펙이 모두 있었고 BFF(Backend for Frontend) 서버를 개발하였음
      4. 이제 회의시작 후 실시간으로 노트생성이 가능해졌다.
    3. 예외처리
      1. 웹훅 API, RTMP 타이밍 문제
      2. 네트워크 등의 오류로 웹훅을 받을 수 없을때
        • 프로세스가 진행되는 동안 주기적으로 REST API 더블체크하도록 처리하였음
  4. 모노레포
    • Multi Repo에서 Mono Repo로 합치게 되면서 팀으로서의 장점이 뚜렷해졌음
    • 기술적 장점
      • 반복적인 환경 셋팅, 코드 중복 최소화
      • 효율적인 의존성 관리
      • 단일 이슈 트래킹
      • 각자의 역할에 집중된 작은 패키지들
      • 린트외의 팀 규칙을 적용하여 신규입사자의 컨텍스트 파악이 용이해지고 적극적인 리뷰문화를 도입할 수 있었다.
    • 모노레포는 최대한 간단하게하고 빌드 캐싱은 Docker 활용
  5. 아토믹 디자인 패턴
    • 컴포넌트를 가장 작은 단위로 설정하여 상위 컴포넌트를 만들어 코드 재사용을 최대화하는 방식
      • 원자(아톰) → 분자 → 유기체 → 템플릿 → 페이지 구조
    • 그대로 사용하기엔 재사용하지 않아도 되는 부분까지 나눠야하는 불편함이 있어서 일반적인 UI 라이브러리에서 제공하는 수준을 아톰으로 정의하여 구조를 잡았음
  6. 리코일
    • Flux / Redux 의 모든 기능이 가능하고 상대적으로 낮은 러닝커브를 가졌음
    • 자체적인 비동기 지원
    • React18 동시성의 장점을 그대로 사용할 수 있는 라이브러리
    • 일반적인 리코일 폴더구조는 리코일에 직접접근이 가능하여 프로젝트가 커질수록 종속성이 복잡해지고 디버깅이 어렵다.
      • 결과적으로 컴포넌트의 복잡함이 리코일로 이동한것에 불과하다.
    • 격리된 도메인별 리코일
      • 아톰, 셀렉터에 직접 접근 없이 훅으로만 연결
      • 리코일 훅은 서로 참조하지 않는다.(인증 제외)
      • 순서가 필요하다면 비지니스 컴포넌트 사용

네이버 스마트블록 개인화 검색

와… 이건 듣는거만으론 이해불가라 포기


네이버 스케일로 카프카 컨슈머 사용하기

  1. Kafka Consumer 동작 원리
    1. Topic : 1개 이사의 Partition으로 분할, 1개 이상의 Replica로 복제된 log 자료 구조
    2. Client
      1. Producer : 쓰고자 하는 Topic Partition의 맨 끝에 record를 추가
      2. Consumer : 읽어오고자 하는 Topic의 Partition에 저장된 record를 순차적으로 읽어 옴
    3. Consumer Group
      • 같은 ‘ group.id’ 설정값을 가진 Consumer들은 하나의 Consumer Group 을 이룬다.
      • 같은 Consumer Group에 속한 Consumer들이 Topic에 속한 Parition들을 나눠서 읽는다.
      • Consumer Group == “논리적인 Consumer”
      • Consumer Group이 제대로 동작하기 위해선 아래 기능이 필요하다.
        1. Parition Assignment 기능
        2. Offset Commit 기능
    4. Consumer Coordination 동작원리
    • Consumer Group Coordinator
      1. Consumer Group에 변경이 생겼는지 탐지
      2. TopicParition에 변경이 생겼는지 탐지
      3. Consumer Group Leader와 나머지 Consumer들간의 communication 중개
    • Consumer Group Leader
      • 현재 구독중인 topic의 파티션들을 consumer들에 할당
    • Broker를 재시작할 필요 없이 더 유연하고 확장 가능한 파티션 할당을 지원하기 위해 좀 복잡한 구조로 만들어짐
    • 관련 설정
      • partition.assignment.strategy
        • List of org.apache.kafka.clients.consumer.ConsumerPartitionAssignor class
        1. Consumer Group에 참여한 모든 Consumer에 공통으로 설정된 Assignor중에서
        2. 우선순위가 가장 높은 것이 파티션 할당 전략으로 선택
  2. Cloud 환경에서 Kafka Consumer 사용하기
    1.
  3. 네이버 스케일로 Kafka Consumer 사용하기

카프카에 대해서 제대로 공부하지 않고 이 내용을 정리하는건 의미가 없다고 판단되서 나중에 다시 정리해보자…


GraphQL 잘 쓰고 계신가요?

  1. GraphQL이란
    • 필요한 것만 요청하고 받아오기
    • 단일 요청으로 많은 데이터 가져오기
    • 가능한 케이스를 타입 시스템으로 표현하기

단순히 Payload를 줄이기 위해서만 사용하는건 아니다. 제대로 사용하지 않는다면 REST API와 크게 다를게 없는 경우도 발생한다.

도입배경

화면 컴포넌트를 구성하는 다양한 데이터를 여러 API를 호출하여 조회하는데 GraphQL BFF 를 통하면 개별 API 스펙을 크게 신경쓰지 않아도 된다.

  1. Schema

    1. 데이터의 모양으로 DB의 스키마는 데이터를 R/W 하는데 최적화를 목적으로 관계와 참조로 중복을 해소한다(정규화)

    2. GraphQL 스키마는 클라이언트를 중심으로 생각하는 출처가 다른 데이터들의 통합 타입 시스템을 의미한다.

    3. Enum을 적극적으로 활용한다

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      type Profile {
      name : String
      thumbnail42 : String
      thumbnail60 : String
      ...
      backgroundImage42: String
      ...
      backgroundImage200: String
      }

      // Enum 사용
      type Profile {
      name : String
      thumbnailImage(width: ImageWidth = W_42): String!
      backgroundImage(width: ImageWidth = W_100): String!
      }

      Enum을 사용함으로 좀 더 명확하게 표현하여 실수를 줄일 수 있다.

    4. 암시적인 의미의 API를 좀 더 명시적으로 한다.

      1. 여기서도 Enum을 사용하는데 empty가 의미를 가질 때 Enum을 사용할 수 있다.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      enum MediaFilter {
      WITH_MEDIA
      WITHOUT_MEIDA
      ALL
      }

      type Query {
      placeReviews(placeId: ID!, mediaFilter: MediaFilter = ALL): [Review!]!
      }

      API 사용자관점으로 명확하게 스키마를 표현한다.

    5. Error Handling

      1. 다양한 에러를 대응하고 메타데이터가 필요한 경우 이러한 상태값을 단순히 타입으로 추가한다면 여러 데이터가 복잡하게 얽히게 된다.

      2. Union 타입을 사용한다.

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        //union Result = Succeed | Error

        type DuplicationError {
        message: String!
        }
        type PwordError {
        workds: [String!]!
        message: String!
        }

        union CheckNickNameOutput = NickNameSucceed | DuplicationError | PwordError

        다만, 유니온 타입만으론 새로운 스펙과 에러가 추가된다면 데이터를 받을 수 없다. 즉, 확장에 닫혀있는 상태이다.

      3. Interface를 적용하여 명시적이고 유연하게 에러 핸들링이 가능하다.

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        interface BaseError {
        message: String!
        }

        type Duplication implements BaseError {
        message: String!
        }
        type PwordError implements BaseError {
        words: [String!]!
        message: String!
        }
        type CountOverError implements BaseError {
        count: Int!
        message: String!
        }

    아래 내용이 더 있으나 GraphQL이 어떻게 사용되는지 대략적인 개요를 한번 들어보고 싶었던거고 뒷부분은 리액트 관련 기술들이랑 좀 더 심화된 내용들이라 굳이 정리하지 않음

    결국 해당 팀에서도 별도의 GraphQL용 BFF 서버를 만들어서 운영중이라고 하니 REST API 처럼 별도의 GraphQL 스펙의 API 서버 쪽 공부를 더 해보도록 하자

Google Tag Manager 간단 정리

웹사이트 또는 모바일 앱에서 태그라고 통칭되는 추적 코드 및 관련 코드 조각을 쉽고 빠르게 업데이트할 수 있는 태그 관리 시스템

여기서 태그는 웹페이지의 HTML 태그를 말하는게 아니고 마케팅 업계에서 자바스크립트로 데이터를 수집해서 웹 분석 및 광고 성과 추적용도로 사용하기 위해 서비스 제공업체(구글,네이버,페이스북 등)로 전달하는 역할을 수행하는 기능을 말한다.

컨테이너 코드 스니펫(컨테이너 생성 시 헤더, 바디에 심으라고 하는 코드)이라는 기본 스크립트를 소스코드에 한번 추가하면 이후 개발자가 관여하지 않더라도 GTM에서 제공하는 UI를 통해 태그를 효율적으로 추가,삭제,변경 등 관리하기 위해 사용한다.

1
2
3
4
5
6
7
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-A123456');</script>
<!-- End Google Tag Manager -->

컨테이너

태그를 관리하는 프로젝트 단위로 일반적으로 도메인별로 생성하며 태그,트리거,변수 3가지 요소를 사용해서 데이터를 추적

변수(How)

특정 값을 담아두는 가상의 메모리 영역으로 어떠한 데이터를 수집할 지 지정하고 여러 이벤트에서 재사용하여 효율증가

ex) 특정 페이지에 접속할때, 클릭할때 데이터를 수집할 경우 사전 정의된 url이라는 변수는 현재 로드중인 페이지의 URL을 변수로 사용할 수 있다.

  • 트리거 : 태그 실행 조건을 지정하는 필터를 정의하는 용도 (ex: url 변수가 ‘example.com/index.html’일 때 페이지뷰 트리거를 실행하는 용도)
  • 태그 : 동적 값을 포착하여 전송하는 데 변수가 사용 (ex: 거래 금액과 구매 제품을 전환추적 태그에 전달하는 용도)

기본 제공하는 변수로 처리할 수 없는 특정 요구사항에 맞추고 싶다면 사용자 정의 변수를 생성할 수 있다.

트리거(When)

태그(명령어)가 실행되는 조건을 정의하여 조건 충족 시 연결된 태그가 실행

ex) 페이지뷰, 클릭할때 등 이벤트 실행 조건

태그(What)

데이터를 추적하여 수집하기 위해 명령하는 명령어의 역할로 트리거의 조건이 충족되면 태그가 인식하여 데이터를 추적하여 Google Analytics 등의 툴에서 데이터를 수집

쉽게 생각해서 트리거가 실행 조건을 담고 있고 태그는 실행 내용을 담고 있다고 생각하면 된다.

데이터 영역(dataLayer)

웹사이트에서 태그 관리자 컨테이너로 정보를 전달할 때 사용되는 자바스크립트 객체

웹사이트 --> dataLayer <–> GTM --> 구글 애널리틱스, 네이버광고 등

구글 애널리틱스 사용 시 전자상거래 구매 데이터, 맞춤 측정기준에서 사용하는 데이터 등은 기본 추적코드만으로는 수집할 수 없어 추가적인 설정이 필요하다.

이때 주로 웹페이지 내 별도의 추적 코드를 소스코드에 삽입하여 이들 정보를 GA서버로 직접 보내는 방식을 사용하는데 GTM에선 이런 정보를 수집할 때 데이터 영역을 주로 사용한다.

코드 스니펫을 header에 추가하고

1
2
3
<script>
window.dataLayer = window.dataLayer || [];
</script>

dataLayer.push() 명령어를 사용하면 데이터 영역에 정보를 추가할 수 있다.

1
2
3
4
5
<a href="#" onclick="dataLayer.push({
'bookCategory': 'fiction',
'bookTitle': 'Cien años de soledad',
'bookAuthor': 'Gabriel García Márquez'
});">도서 세부정보</a>

주의사항으로 GTM은 컨테이너 스니펫 실행 시 자동으로 데이터영역을 생성하며 이미 있는 경우 그 안의 변수를 가져다 사용하기 때문에 데이터영역은 GTM 컨테이너 스니펫보다 앞에 위치해야 한다고 한다.