VueJs

2018-03-12 00:00:00 +0000

vuejs and nuxtjs

React Native

2018-03-12 00:00:00 +0000

https://reactforbeginners.com/

react native, redux, rxjs

Deep Learning and Python

2018-03-12 00:00:00 +0000

Deep Learning A-Z™: Hands-On Artificial Neural Networks

https://www.udemy.com/deeplearning/

Laravel

2018-03-12 00:00:00 +0000

still subscribed to https://laracasts.com/

Elasticsearch

2018-03-12 00:00:00 +0000

새로 이직한 소개팅 앱 회사에서 지난 2개월동안 Laravel 5.5 와 Elasticsearch 6.01 을 가지고 회원과 회원간의 추천용 Microservice API 를 만들면서 경험한 내용들을 간단히 정리합니다. 올 1월만 하더라도, Elasticsearch Reference 6.x 설명서를 제외하고, Elasticsearch - The Definitive Guide [2.x] 에 나오는 설명도 5.x 에 맞춰져 있어서, 예제가 실행되지 않는 혼란을 겪었었는데, 두달이 지난 지금 상위버젼으로 트랜지션이 무난하고 빠르게 진행되고 있는 듯 합니다. 다른 여타의 IT 기술들의 변화 속도가 그렇듯…

지오해쉬 (GeoHash)

지오해쉬는 지구를 grid 시스템으로 자르고 alphanumeric 문자로 대응시켜서 지리적 위치를 표현하는 방식이다. 지오해쉬 설명 에서 볼 수 있듯이 lat/lng 값을 geohash 로 변환할 수 있으며, geohash 는 다시 lat/lng 값으로 변환할 수 있다. 지오해쉬는 12 단계의 precision 이 존재하는데, 아래 grid 사이즈 도표를 참고하면 geohash 한개 셀의 크기가 얼마인지 알 수 있다.

precision grid 상의 1개 셀 width x height 크기
1 ≤ 5,000km × 5,000km
2 ≤ 1,250km × 625km
3 ≤ 156km × 156km
4 ≤ 39.1km × 19.5km
5 ≤ 4.89km × 4.89km
6 ≤ 1.22km × 0.61km
7 ≤ 153m × 153m
8 ≤ 38.2m × 19.1m
9 ≤ 4.77m × 4.77m
10 ≤ 1.19m × 0.596m
11 ≤ 149mm × 149mm
12 ≤ 37.2mm × 18.6mm

지오해쉬 값은 precision 에 따라서 alphanumeric 문자가 한개씩 더 붙는다. 예를 들어 서울의 어떤 위치를 wydk0fqksulk 이라 지정하면 굉장히 작은 점으로 특정하게 되는 것이고, wydk0fq 라고 표현하면 약 153 미터 x 153 미터 크기의 바운더리를 표현하는 것이다. wyd 라고 표현하면 엄청 큰 바운더리가 되겠다.

lat/lng 값은 지리적 위치 좌표를 표현하는 방법이고, geohash 는 위에서 설명한 규칙에 맞게끔 자른 1개 cell 의 바운더리를 지정하는 방법이라서, geohash 를 lat/lng 으로 변환하게되면 해당 cell 의 좌상단 lat/lng 값과 우하단 lat/lng 값으로 변환된다. (지오 바운더리) 이해를 돕기위해, 서울을 포함하는 geohash 1단계 w 지역, 2단계 wy 지역, 3단계 wyd 지역을 지도에 표시해보면 아래 이미지들과 같다.

샘플 지오코드

인접한 셀들도 계산으로 구할 수 있으며, 이를 이용하면 가령 wy 는 너무 크다고 판단되고, wyd 는 너무 작다고 판단되는 경우, wyc 의 좌상단 lat/lng값과 wy7 의 우하단 lat/lng값을 구하여 바운더리를 조정가능하다.

Elasticsearch 의 geo_point 타입

Elasticsearch 에서 위치정보는 geo_point 라는 타입으로 매핑이 되어야 하는데, 이 location 데이터는 일반적인 lat/lng 형태로 지정할 수 도 있고, 위에서 설명한 geohash 형태로도 지정할 수 있다. 국내에서 위치정보 사업자 인허를 위해서는 사용자의 위치정보를 AES128 이상 으로 인크립트시켜서 저장하던지, 지오해쉬로 저장하면 된다고 한다. 물론, 지오해쉬값 자체는 인크립트된 정보가 아니라 위/경도 값으로 변경이 가능하지만, 현재 관련 정부부서의 기준은 숫자로된 위/경도 정보를 저장하지 않으면 된다는 입장인 듯 싶다. 이러한 연유로 이번 프로젝트에서 Elasticsearch 에 사용자 위치정보를 저장할 때는 geohash 값으로 저장하였다. (37.5647689, 126.7093711 대신, wydk0fqksu 식으로 저장.)

Elasticsearch 에 lat/lng 조합값이던, geohash 값이던 간에 geo_point 데이터가 존재한다면, geo_location 필터를 적용하여 필터링이 가능하다. geo_location 필터링은 기본적으로 시간이 오래걸리는 operation 이고 geo_distance 필터와 geo_bounding_box 라는 필터를 사용할 수 있는데, 전자는, 지정 위치로 부터 몇 Km 내의 또는 몇 m 내의 데이터를 모두 필터링하여 리턴하라는 query 를 사용할 수 있는 방법이고, 후자는, 좌상단 lat/lng 값과 우하단 lat/lng 값으로 지정한 지오 바운더리 안에 들어가 있는 데이터만 필터링하여 리턴하기 위한 query 를 사용하는 방법이다. 속도는 후자가 10%-20% 정도 빠르다. (사각형이 아니라, radius 계산이 들어가면 느려짐.)

Elasticsearch 에서는 aggregations 기능을 이용할때는 geohash 의 precision 을 지정할 수 있는데, aggregations 을 사용하지 않고 geo_location 필터링을 하는 경우 precision 을 특정하는 것이 없다. aggregations 기능을 이용하면, 사용자의 위치에 인접한 사용자들의 숫자를 precision 별로 가져올 수 있는데, 이 기능을 잘 활용하면, 사용자 pool 이 적은 지역의 경우, 후보군을 가져오는 geohash precision 을 동적으로 조절하여 원하는 숫자 이상의 사용자 pool 을 추출할 수 있을 것 같다. 예를 들어, 아래와 같은 aggregations 결과를 받았을때, wyd1 지역은 doc_count (회원수) 가 9922 명이니까 충분한 pool 이 되지만, wyki 지역은 doc_count 가 11 밖에 안되므로, 해당 지역에 위치한 사용자에게 후보군을 추천할때는 wyki 가 아니라 wyk 에 위치한 사용자들을 불러와서 매칭을 추천할 수 있겠다.

"aggregations": {
 "buckets": [
   {
     "key": "wyd1", "doc_count": 9922
   },
   {
     "key": "wyex", "doc_count": 4663
   },
   {
     "key": "wy6u", "doc_count": 492
   },
   {
     "key": "wys4", "doc_count": 165
   },
   {
     "key": "wy7o", "doc_count": 143
   },
   {
     "key": "wyki", "doc_count": 11
   }
 ]
}

사용자 pool 에서, 특정 아이디를 갖는 사용자 필터링

현재 구현된 소개팅 앱의 특성상 모든 사용자 정보를 기록한 사용자 pool 이 있고, 그 중 어떤 relationship 관련 정보에 의하여 검색에서 제외되어야만 하는 필터링을 구현할 필요가 있었다. 이러한 요구사항을 갖는 경우, 사용자 정보를 기록한 인덱스에서 검색에서 제외해야하는 아이디들까지 관리하는 것은 상당한 속도 저하를 가져올 수 있다. 다시말해, 특정 데이터 집합에서 데이터를 검색할때 다량의 아이디들을 그 검색 결과에서 제외해야만 하는 시나리오의 경우, 필터링을 할 아이디들을 기록하는 별도의 인덱스를 관리하도록 모델링을 하는 것이 정석이며, 이는 Elasticsearch 레퍼런스 terms query 의 twitter 예 에서 제시된 예와 같은 형태로 구현하는 것이 best practice 라 할 수 있겠다

샘플 매핑코드

모든 퀴리는 long 데이터 타입을 사용하는 것이 keyword 데이터 타입 보다 더 빠르므로, 가급적 long 형 데이터로 변환하는 것이 유리하다. 제외할 아이디들을 기록하는 인덱스의 setting 값에는 auto_expand_replicas 를 0-all 로 셋팅하여 모든 node 에 대해 replica 가 생기도록 한다. (read throughput 증가.)

Elasticsearch 의 디폴트 shards 갯수는 5 이지만, use case 마다 그 값을 변경할 필요가 있다. 인덱스의 크기에 따라 운영자가 판단하여 shard 갯수를 정하면 되는데

https://www.elastic.co/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster

이번 프로젝트에서는 현재 제일 큰 index 의 크기가 3GB 도 안되기 때문에, Best Practice 를 따라 15G 보다 적은 데이터를 운영중에는, 1개 샤드를 유지하도록 결정했다.

Elasticsearch 는 그 하단에 루씬엔진을 사용하는데, 멀티 샤드를 유지할때는 ES 가 여러 샤드에서 독립적으로 돌아가는 루씬에게 신호를 보내, 쿼리 결과를 달라고 분산처리를 한다. 대장인 Elasticsearch 는 그 결과를 취합하여 최종 결과를 사용자에게 리턴하는데, 데이터 크기가 크지 않지 않은 상태에서, 분산처리를 하는 것 보다, 하나의 샤드에서 처리하는 것이 효율적이라고 판단할 수 있다.

또한 users 라고 인덱스를 만들지 않고 users_v1 이라는 이름으로 인덱스를 만든 다음, 이를 users 라고 alias 설정한 이유는 아래의 레퍼런스에서 언급한 것처럼

https://bonsai.io/2016/01/11/ideal-elasticsearch-cluster

운영중에 샤드 갯수 변경이나 또는 스키마 변경이 생길 경우를 무중단 업데이트를 하기 위한 Best Practice 를 따른 것이다.

사용자 인덱스 매핑 샘플

PUT /users_v1
{
  "mappings": {
    "doc": {
      "properties": {
        "id": { "type": "long" },
        "name": { "type": "keyword" },
        "gender": { "type": "keyword" },
        "location": { "type": "geo_point" },
        "birthday": { "type": "date", "format": "yyyy-MM-dd" },
        "rating_score": { "type": "float" },
        "language": { "type": "long" },
        "religion": { "type": "long" },
        "region": { "type": "long" },
        "active": { "type": "boolean" },
        "tags": { "type": "keyword" }
      }
    }
  }
}

제외할 아이디들 관리 인덱스 매핑 샘플

PUT /blocks_v1

{
  "settings": {
    "auto_expand_replicas": "0-all"
  },
  "mappings": {
    "doc": {
      "properties": {
        "member_id": { "type": "long" },
        "exclude_ids": { "type": "long" }
      }
    }
  }
}

Laravel Eloquent model events 를 이용

모델이벤트와 연결된 각각의 리스너(이벤트 핸들러)를 이용하여, MySQL 과 Elasticsearch 간의 데이터를 동기화 시킨다. 샘플 이벤트는 아래와 같다.

이벤트 이름 발생 조건 모델이름 모델이벤트 리스너가 하는일
UserLastUpdatedAt users 테이블의 last_updated_at 이 업데이트 되는 순간 발생 User updated Elasticsearch 의 users_v1 인덱스의 boolean 형 필드가 false 인 경우 true 로 수정

Docker and Kubernetes

2018-03-12 00:00:00 +0000

docker 와 kubernetes

https://hackernoon.com/the-best-architecture-with-docker-and-kubernetes-myth-or-reality-77b4f8f3804d