개발자 준의 기술블로그


  • 홈

  • 아카이브

  • 태그

  • 검색

[Django Rest Framework] class-based views - Introduction

작성일 2019-12-06 | In Backend

이 글은 본인이 공부를 목적으로 공식문서를 읽으며 정리/번역한 글로서 오역이 있을 수 있을 수 있습니다. 또한 본인이 이미 알고 있거나 불필요하다 느끼는 내용들은 누락될 수 있습니다.

Class-based view는 view를 함수가 아닌 파이썬 객체로 구현할 수 있는 하나의 방법이다. Class-based view는 function-based view를 대체하지는 않지만 그와 비교하여 차이점과 장점이 있다.

- HTTP method(GET, POST, etc)에 따른 코드를 conditional branching(IF)를 하지않고 별도의 메소드로 분리 할 수 있다.

- 객체 지향 테크닉들이 사용될 수 있다.

Using class-based views

1
2
3
4
5
6
from django.http import HttpResponse

def my_view(request):
    if request.method == 'GET':
        # <view logic>
        return HttpResponse('result')

가 아래로 대체될 수 있다.

1
2
3
4
5
6
7
from django.http import HttpResponse
from django.views import View

class MyView(View):
    def get(self, request):
        # <view logic>
        return HttpResponse('result')

장고의 URL resolver는 request와 인자들을 클래스가 아닌 호출가능한 함수에 보내기 때문에 class-based view는 as_view()라는 클래스 메소드를 사용하여 request를 받는다. 이 함수는 클래스 인스턴스를 생성하고 setup()을 호출하여 attribute들을 initialize하고 dispatch()를 호출한다. dispatch()는 request를 확인하여 GET,POST 등 해당하는 메소드에 전달하여 준다.

더 읽어보기 »

[Django Rest Framework] viewset and router

작성일 2019-12-06 | In Backend

Viewset을 사용하면 관련된 view들을 하나의 클래스로 묶을 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class UserViewSet(viewsets.ViewSet):
    """
    A simple ViewSet for listing or retrieving users.
    """
    def list(self, request):
        queryset = User.objects.all()
        serializer = UserSerializer(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None):
        queryset = User.objects.all()
        user = get_object_or_404(queryset, pk=pk)
        serializer = UserSerializer(user)
        return Response(serializer.data)
1
2
3
4
5
6
from rest_framework import routers

router = routers.SimpleRouter()
router.register(r'users', UserViewSet)
router.register(r'accounts', AccountViewSet)
urlpatterns = router.urls

위의 예시는 아래의 URL 패턴을 만든다

  • URL pattern: ^users/$ Name: 'user-list'
  • URL pattern: ^users/{pk}/$ Name: 'user-detail'
  • URL pattern: ^accounts/$ Name: 'account-list'
  • URL pattern: ^accounts/{pk}/$ Name: 'account-detail'

Viewset에서 사용하는 self.action은 router가 자동으로 변환한 것이다.

URL Style HTTP Method Action URL Name
{prefix}/ GET list {basename}-list
POST create    
{prefix}/{url_path}/ GET, or as specified by `methods` argument `@action(detail=False)` decorated method {basename}-{url_name}
{prefix}/{lookup}/ GET retrieve {basename}-detail
PUT update    
PATCH partial_update    
DELETE destroy    
{prefix}/{lookup}/{url_path}/ GET, or as specified by `methods` argument `@action(detail=True)` decorated method {basename}-{url_name}

위에 표는 URL Style + HTTP Method가 어떻게 Action으로 변환되는지를 보여준다.
만약 POST users/라고 쿼리를 보내면 {prefix}/ + POST로 action은 list가 된다.
만약 GET users/123라고 쿼리를 보내면 {prefix}/{lookup}/ + GET으로 action은 retrieve가 된다.

perform_create()는 create()가 호출된 과정에서 serializer를 사용하여 serializer.save()를 하기 위해 호출된다.
perform_create()와 create()를 구분해서 사용하는법
추가1
추가2

serializer.save()는 객체가 이미 존재하면 update를, 없다면 create를 해준다

더 읽어보기 »

[Django Rest Framework] create() vs perform_create()

작성일 2019-12-06 | In Backend

Django REST Framework의 ModelViewSet과 create() 관련 찾아보고 공부한 내용을 정리해보려고 한다.

기본적으로 ModelViewSet은 GenericAPIView라는 클래스를 상속받기 때문에 .list(), .create()  .list(), .create() 등을 기본으로 내장하고 있다. 따라서 이 중에서 기본 내장되어 있는 메소드 중 동작을 변경하고 싶은 것들만 overriding해서 수정하면 된다.

그리고 ModelViewSet은 GenericAPIView를 상속받고 있기 때문에 최소한 queryset와 serializer_class는 세팅해주어야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
from rest_framework import status
from rest_framework.response import Response


def create(self, request, *args, **kwargs):
    serializer = self.get_serializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    self.perform_create(serializer)
    headers = self.get_success_headers(serializer.data)
    return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

def perform_create(self, serializer):
    serializer.save()

위의 코드를 보면 이해가 조금 더 쉬울 것 같다. perform_create()은 create()의 동작 중 일부분을 overriding한다고 생각하면 되는데, serializer.save()가 호출될 때 perform_create()가 호출된다고 생각하면 된다. 위의 경우 명시적으로 perform_create()를 호출했지만, django에서는 개발자의 짐을 덜어주기위해 mixin으로 앞에서 설명한 list(), create()등을 제공하는 데, 이때 자동으로 save()대신 perform_create()를 호출하는 것이다. 

더 읽어보기 »

[Django] Django Channels - Introduction

작성일 2019-12-06 | In Backend

Scope란 connection의 정보를 뜻한다. remote IP, username, lifetime of connection등의 정보를 가지고 있다. Application은 한번의 scope당 한번씩 instantiate된다. HTTP에서는 각 request마다, socket에서는 각 WebSocket connection마다이다.

Consumer 각각의 프로토콜은 각자 다른 event들이 발생하며 이 event들은 메소드로 표현된다. 개발자는 각각의 event들을 어떻게 처리할지 코드만 작성하면 Django의 Channel이 스케쥴링과 병렬적으로 실행시켜준다.

class LogConsumer(WebsocketConsumer): def connect(self, message): Log.objects.create( type=”connected”, client=self.scope[“client”], )

Channel은 asynchronous event loop을 기반으로 돌아간다. 하지만 위처럼 코드를 작성하면 synchronous thread에서 실행이 된다. 따라서 위의 코드처럼 blocking operation도 문제없이 잘 작동한다.

class PingConsumer(AsyncConsumer): async def websocket_connect(self, message): await self.send({ “type”: “websocket.accept”, }) async def websocket_receive(self, message): await asyncio.sleep(1) await self.send({ “type”: “websocket.send”, “text”: “pong”, })

위처럼 작성하면 asynchronous하게 작동한다.

Cross-Process Communication Application내에서 통신하는 것은 각 application바다 존재하는 application instance를 통해서 하면 되지만, application끼리 통신하는 것은 이야기가 달라진다. 데이터베이스 polling을 통해서 할 수도 있지만 Channels는 channel layer라는 개념을 도입했다. 각각의 application instance는 unique channel name을 가지고 있으며, group에 join할 수 있다.

In a consumer self.channel_layer.send( “myproject.thumbnail_notifications”, { “type”: “thumbnail.generate”, “id”: 90902949, }, )

위의 코드처럼 다른 process에게 broadcasting해줄 수 있다.

더 읽어보기 »

[Django] Django Channels - consumers

작성일 2019-12-06 | In Backend

Consumer하는 일 중 몇가지: - 이벤트가 발생했을 때 사용할 함수를 작성하기만 하면 된다. event loop전체를 다 쓸 필요가 없다.

1
- synchronous / asynchronous 코드를 작성하기만 하면 threading을 포함한 나머지를 다 처리해준다.

Consumer Events from channels.consumer import SyncConsumer class EchoConsumer(SyncConsumer): def websocket_connect(self, event): self.send({ “type”: “websocket.accept”, }) def websocket_receive(self, event): self.send({ “type”: “websocket.send”, “text”: event[“text”], })

Consumer는 type과 매칭되는 몇가지의 메소드들로 구성되어 있다. type에서 .를 _로 바꾼것이 메소드 이름이다. 즉 위의 예시에서

“type”: “websocket.accpet” 는 websocket_accept()를 호출하고

“type”: “websocket.send”는 websocket_send()를 호출한다.

그렇다면 위의 예시에서 websocket_receive(self, event)는 event[“text”]를 접근하고 있는데 websocket_receive의 event안에 text가 있다는 것은 어떻게 알았을까? 이는 ASGI에서 정해진 규약을 따른다.

AsyncConsumer vs SyncConsumer AsyncConsumer와 SyncConsumer의 사용처에 대해서는 쉽게 정리하자면 asynchronous 코드들만 실행될 경우에는 AsyncConsumer를, ORM처럼 Synchronous 코드는 SyncConsumer에서 호출하도록 한다.

Closing Consumers Socket이나 Connection이 끊기면 보통은 서버에 event(http.disconnect or websocket.disconnect)가 보내진다. disconnect에 관련된 작업을 마치면 channels.exceptions.StopConsumer를 발생시켜 ASGI application을 정상적으로 중지시켜야된다. 따로 중지시키지 않는다면, server는 timeout시간이 되면 자동종료시키고 warning을 띄운다.

Channel Layers from channels.consumer import SyncConsumer class EchoConsumer(SyncConsumer): channel_layer_alias = “echo_alias”

위의 코드처럼 channel_layer_alias를 별도로 명시해주지 않는다면 Consumer는 default channel layer를 사용한다.

더 읽어보기 »

[NodeJS] NodeJS 내부 원리 - Reactor pattern

작성일 2019-12-06 | In Backend

본문은 Node.js Design Patterns - Mario Casciaro 를 번역/정리한 글입니다.

Blocking I/O

- 기존의 Blocking I/O를 웹서버에 사용하게 되면 동시에 여러 접속을 처리하기 위해 각각의 커넥션을 별도의 쓰레드로 분리를 하여야한다. 이렇게 각가의 쓰레드가 하나의 커넥션만을 처리하게 되면 실제로 일을 하는 시간보다 기다리는 시간이 훨씬 많아지게 된다. 그리고 쓰레드를 여러개 사용하는것은 메모리를 많이 잡아먹고 Context Switch를 발생시키므로 효율적이지 않다.

Non Blocking I/O

- Non-blocking I/O에서는 system call이 데이터를 읽고 쓰는것을 기다리지 않고 만약 그시점에 처리할 것이 없다면 바로 반환해버린다.

Busy Waiting

- Non-blocking I/O에서는 여러 커넥션을 병렬적으로 처리하기 위한 방법 중 가장 간단한 방법으로 리소스를 polling(수시로 체크)하는 busy-waiting이라는 방법이 있다. 아래의 pseudo 코드 방식으로 작동한다:

1
2
3
4
5
6
7
8
9
10
11
12
13
resources = [socketA, socketB, socketC];
while(!resources.isEmpty()) {
	for(i=0;i < resources.length; i++){
    	resource = resources[i]
        var data = resource.read();
        if (data == NO_DATA_AVAILABLE)
        	continue;
        if (data == RESOURCE_CLOSED)
        	resources.remove(i);
        else
        	consumeData(data)
    }
}

이런방식을 사용하면 동시에 여러개의 접속을 하나의 쓰레드에서 처리할 수 있다. 하지만 이러한 Polling 알고리즘들은 CPU를 너무 낭비한다.

Synchronous Event Demultiplexer

- 이를 효율적으로 해결한 방법이 Synchronous Event Demultiplexer 또는 Event Notification Interface이다.

1
2
3
4
5
6
7
8
9
10
11
12
socketA, pipeB;
watchedList.add(socketA, FOR_READ)
watchedList.add(pipeB, FOR_READ)
while( events = demultiplexer.watch(watchedList)){
	foreach(event in events){
		data = event.resource.read()
        if(data == RESOURCE_CLOSED)
        	demultiplexer.unwatch(event.resource)
        else
        	consume(data)
	}
}

1. 여기서 핵심은 demultiplexer.watch(watchedList)인데, demultiplexer가 watchedList를 계속 보면서 다음으로 넘어가지않고 Block하고 있다가 watchedList 중 하나라도 준비가 된다면 다음 foreach문으로 넘어간다.

2. 이 foreach문이 실행될때는 각각의 event들이 준비가 되었다는것이 보장이 되어 처리될때 block이 되지않는다.

3. 모든 event가 처리되고 나면 다시 demultiplexer가 block하며 새로운 event가 발생할때까지 기다린다.

이 일련의 과정을 event loop라고 한다.

따라서 정리하자면 각각의 쓰레드에서 커넥션을 1개씩 잡고 있는게 아니라 event demultiplexer가 여러개의 커넥션을 보고있다가 event가 발생하면 이를 모아서 전달해주는데, 이 모여진 event들은 blocking되지 않고 바로 처리할 수 있기 때문에 1개의 쓰레드로 처리가 가능하게 된다.

이 방식을 차용하면 쓰레드가 기다리는 idle time이 획기적으로 줄게되고 single thread로 운용이 가능해지기 때문에 race condition이나 multi-thread synchronization같은 문제를 겪지않고 더 직관적이게 병렬처리가 가능해진다.

The Reactor Pattern

Reactor 패턴은 앞에서 소개한 알고리즘의 특화된 버전이다. 핵심이 되는 아이디어는 각각의 I/O에 핸들러를 붙이는 것이다(Node.JS에서는 Callback). 그리고 이 핸들러들은 event가 발생하면 호출되어 event loop에 의해 처리된다.

Reactor패턴이 처리하는 방식은 다음과 같다:

1. Application은 Event demultiplexer에게 새로운 요청을 보냄으로서 새로운 I/O를 생성한다. Application은 요청을 보낼때 event가 발생하면 호출될 Handler를 설정하여 보낸다. 이 작업은 Non-Blocking이다.

2. I/O 작업이 끝나면 Event Demultiplexer는 새로운 event를 Event Queue에 삽입한다.

3. 이 때가 되면 Event Loop가 Event Queue를 순회한다.

4. 이벤트가 발생하면, 각각에 적용된 핸들러가 호출된다.

5. 어플리케이션 코드의 일부인 핸들러는 본인의 작업이 완료되면 Event Loop에게 control을 다시 넘겨준다. 

6. Event Queue에 있는 모든 작업이 완료되면 Event Demultiplexer가 다음 사이클을 시작할때까지 block 된다.

Node.js의 Non-Blocking Engine - libuv

I/O operation은 같은 OS안에서도 resource의 종류에 따라 상당히 다르게 처리될 수 있다. 예를 들자면 Unix의 regular filesystem은 non-blocking operation을 지원하지 않는다. 따라서 non-blocking처럼 행동하게 만들기 위해서는 event loop 밖에 별도의 thread를 사용하여 처리하게 된다. 이렇게 다양한 운영체제에서 Event Demultiplexer를 지원하기 위해서는 high-level abstraction이 필요했다. 그래서 메이저 OS들을 지원하기 위해 Node.js core team은 libuv라는 C 라이브러리를 만들게 되었다.

The Callback Pattern

Javascript는 callback을 나타내기 위한 최적의 언어이다. Function은 class object로서 쉽게 변수에 할당될 수 있고, argument로 전달되거나 return으로 반환될 수도 있다. 그리고 closure를 사용하면 function이 생성된 환경을 쉽게 접근할 수 있다. 즉, asynchronouse operation이 생성된 context를 언제 callback이 불러지던 접근할 수 있다.

Callback은 다른 함수에 인자로 전달되는 함수로서 함수의 작업이 끝나면 작업의 결과물과 함께 호출되는 함수이다.

이런 방식은 함수형 프로그래밍에서는 Continuation-Passing style(CPS)라고 불리며 Asynchronous operation에서만 사용되는 개념은 아니다. 단지 이 방식은 작업의 결과물을 caller에게 바로 반환하는 것이아니라 다른 함수에게 전달하는 방식을 뜻할 뿐이다.

더 읽어보기 »

[Elastic Search 공부하기] Mapping Types

작성일 2019-11-14 | In ElasticSearch

출처: https://www.elastic.co/guide/en/elasticsearch/reference/current/full-text-queries.html
출처: https://stackoverflow.com/questions/26001002/elasticsearch-difference-between-term-match-phrase-and-query-string

1. term

하나의 term을 찾는다. 이 term은 analyze 되지 않는다. 따라서 대소문자, 복수형 등이 다 구분된다

1
2
3
4
5
6
7
8
{ "user":"Bennett" }

{
  "query": {
    "term" : { "user" : "bennett" }
  }
}
//아무것도 리턴되지 않는다. (대소문자 구분)

2. match

가장 기본적인 search로 text, number, date, boolean을 사용할 수 있다. text는 검색하기전 analyze된다.

fuzzy matching을 지원한다(정확하게 일치하지않더라도 연관성이 있다고 판단하면 리턴).

1
2
3
4
5
6
7
8
9
10
GET /_search
{
    "query": {
        "match" : {
            "message" : {
                "query" : "this is a test"
            }
        }
    }
}

3. query_string

입력값을 analyze한다. 사용자가 명시적으로 ““로 둘러싸지않는이상 순서는 상관이 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{ "foo":"I just said hello world" }
{ "foo":"Hello world" }
{ "foo":"World Hello" }

{
  "query": {
    "query_string": {
      "query": "hello World"
    }
  }
}
//얘는 3개를 다 리턴한다

{
  "query": {
    "query_string": {
      "query": "\"Hello World\""
    }
  }
}
//얘는 명시적으로 ""로 싸여져 있기 때문에 match_phrase와 동일하게 처음 2개만을 리턴한다.

query_string과 match의 차이점은 AND와 OR같은 연산자를 사용할 수 있다. 

query_string은 syntax를 먼저 parse한 뒤에 split을 하고 analyze를 한다.

1
2
3
4
5
6
7
8
9
GET /_search
{
    "query": {
        "query_string" : {
            "query" : "(new york city) OR (big apple)",
            "default_field" : "content"
        }
    }
}

주의할 점: query_string은 invalid syntax에 대해 에러를 반환하므로 AND와 OR과 같은 query syntax를 지원해야되는 것이 아니라면 match를 사용하거나 좀 더 덜 엄격한 simple_query_string을 사용하는 것이 낫다.

4. Intervals

matching rules를 사용하여 쿼리한다. 여러개의 term들이 원하는 간격안에 존재하는지 쿼리한다.

아래의 쿼리는 “hot”과 “porridge”사이에 10단어 이상 간격이 없으면서 중간에 “salty”가 없어야 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST _search
{
  "query": {
    "intervals" : {
      "my_text" : {
        "match" : {
          "query" : "hot porridge",
          "max_gaps" : 10,
          "filter" : {
            "not_containing" : {
              "match" : {
                "query" : "salty"
              }
            }
          }
        }
      }
    }
  }
}

5. match_phrase

입력값을 analyze한다. 그리고 다음의 조건을 충족하는 document를 찾아서 반환한다.

- 모든 term들이 field에 존재해야한다.

- 모든 term들이 입력된 순서에 맞게 존재해야된다.

1
2
3
4
5
6
7
8
9
10
11
12
{ "foo":"I just said hello world" }
{ "foo":"Hello world" }
{ "foo":"World Hello" }

{
  "query": {
    "match_phrase": {
      "foo": "Hello World"
    }
  }
}
//1번째 2번째만 리턴된다. 3번째는 순서가 틀림

6. match_boolean_prefix

마지막 term을 제외한 모든 term은 term query에 사용된다. 마지막 term은 prefix query에 사용된다.

따라서 아래의 match_bool_prefix는 그 밑의 term과 prefix로 이루어진 query와 동일하다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
GET /_search
{
    "query": {
        "match_bool_prefix" : {
            "message" : "quick brown f"
        }
    }
}

GET /_search
{
    "query": {
        "bool" : {
            "should": [
                { "term": { "message": "quick" }},
                { "term": { "message": "brown" }},
                { "prefix": { "message": "f"}}
            ]
        }
    }
}

match_phrase_prefix와 상당히 유사하지만 match_phrase_prefix는 입력값을 phrase로 보고 쭉 이어진 하나로 검색을 하는 반면, match_boolean_prefix는 term단위로 검색을 하기때문에 서로 떨어져 있어도 가능하고, 순서가 뒤섞여있어도 가능하다.

7. match_phrase_prefix

위에서 언급했던 것처럼 입력값을 하나의 뭉텅이로 검색한다 따라서:

     - quick brown fox (O)

     - two quick brown ferrets (O)

     - the fox is quick and brown (X)

8. multi_match

multi_match는 multi query를 바탕으로 여러 field를 동시에 쿼리할 수 있도록 한다.

1
2
3
4
5
6
7
8
9
GET /_search
{
  "query": {
    "multi_match" : {
      "query" : "this is a test",
      "fields" : [ "subject^3", "*_name" ] 
    }
  }
}

위의 코드처럼 subject 필드에 3배의 가중치를 줄 수 있으며 field이름에 * wildcard를 사용할 수 있다.

만약 field가 명시되어 있지 않다면 index.query.default_field로 설정된 field에서 검색하고, 이 default_field는 따로 명시하지 않았다면, index의 field는 term query를 수행할 수 있는 모든 field를 추출하여 수행한다.

multi_match가 어떻게 작동할지는 type 파라미터에 따라 결정된다.

 - best_fields: 여러개의 field들 중에서 입력값 단어들 중 가장 많은 단어를 가지고 있는 하나의 field의 점수를 사용

 - most_fields: 여러개의 field들의 점수를 산출한뒤 합산하고 field의 개수로 나눈 평균점수를 사용한다

 - phrase / phrase_prefix: best_fields와 똑같지만 match_phrase / match_phrase_prefix를 match대신 사용

 - cross fields: 여러개의 field가 동시에 만족되야할 경우(예: 성과 이름이 다른 field에 들어가있을때). query string을 individual term으로 analyze한 뒤에 마치 하나의 field를 검색하는 것처럼 모든 field를 살펴본다. 

(위의 best_field와 most_field와는 달리 어느 하나의 field에 모든 term이 존재하면 된다)

(주의: cross_fields에는 fuzziness가 사용될 수 없다)

tie_breaker: 점수가 산정되는 방식을 결정한다:

     - 0.0 : 모든 field 중 가장 높은 점수를 가진 field의 점수를 선택

     - 1.0 : 모든 field의 점수를 합함

     - 0.0 < n < 1.0: 가장 높은 field의 점수 + (n * 나머지 matching field 점수)

더 읽어보기 »

[Elastic Search 공부하기] Mapping Types

작성일 2019-11-14 | In ElasticSearch

ElasticSearch를 도입하기전, ElasticSearch에대해 공부하기 위해 레퍼런스를 읽으면서 번역&정리한 글입니다.

문서 전체를 번역한 것이 아니라 개인적으로 정리가 필요하다 싶은 내용만 정리하였습니다.

본문: ElasticSearch 공식레퍼런스

Mapping은 document와 document의 field가 어떻게 저장되고 인덱싱되는지를 정하는 것이다.

Field Datatypes:

- Simple types like text, keyword, date, long, double, boolean, ip

- JSON형식을 띄는 object, nested

- 특수한 경우에 사용되는 geo_point, geo_shape, completion

잘모르는 타입들 정리:

Keyword datatype:

- 주로 이메일주소, 우편번호 등 형식을 갖춘 데이터들을 저장하기 위해 사용된다.

- 주로 필터링, 정렬, 종합을 위해 사용된다.

- 정확한 값으로면 검색이 가능하다

Text datatype:

- text로 지정된 field는 anlayzer를 통해 변환된다.(한글은 anlayzer-nori 플러그인을 별도로 깔아야됨)

- 변환된 후에는 ES가 하나의 단어를 전체 내용중에서 찾을 수 있게 해준다.

Nested datatype:

- nested타입은 object타입의 특수한 형태로써 쉽게 말하자면 object의 list라고 생각하면된다.

- 그리고 ES는 inner object라는 형식을 지원하지 않기때문에 이 list를 flatten하여 처리한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PUT my_index/_doc/1
{
  "group" : "fans",
  "user" : [ 
    {
      "first" : "John",
      "last" :  "Smith"
    },
    {
      "first" : "Alice",
      "last" :  "White"
    }
  ]
}

위의 코드블럭이 처리될때는 아래로 변환되어 처리된다.

1
2
3
4
5
{
  "group" :        "fans",
  "user.first" : [ "alice", "john" ],
  "user.last" :  [ "smith", "white" ]
}
더 읽어보기 »

[Elastic Search 공부하기] Mapping Types

작성일 2019-11-14 | In ElasticSearch

ElasticSearch를 도입하기전, ElasticSearch에대해 공부하기 위해 레퍼런스를 읽으면서 번역&정리한 글입니다.

문서 전체를 번역한 것이 아니라 개인적으로 정리가 필요하다 싶은 내용만 정리하였습니다.

본문: ElasticSearch 공식레퍼런스

1
2
3
4
5
PUT /...customer/_doc/1
{
  "name": "John Doe",
  "characteristics": "he is tall, fat and wearing blue jacket"
}

위의 예시에서

1
PUT /<index>/<mapping type>/<id>

순이다.

그래서 실제로 쿼리를 보내보면:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "_index" : "...customer",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

 이렇게 _type에 _doc이 뜨는 것을 볼 수 있다.

mapping type이 7.x버전부터는 없어졌다고 하는데 무엇인지 알아둘겸 읽어보았다.

-–여기서부터 원문—

처음 ElasticSeach(이하 ES)가 출시했을때부터, 각각의 document는 하나의 index와 하나의 mapping type으로 저장되었다. Mapping Type은 document의 타입을 나타내는데 사용되었는데, 예를 들자면 twitter라는 index는 user 타입과 tweet 타입을 가질 수 있다.

각각의 mapping type은 고유의 field를 가지고 있다. 그래서 user 타입은 full_name, user_name 필드를 가지고 있고, tweet 타입은 content, tweeted_at 필드를 가질 수 있다.

document는 _type 메타 필드를 가지고 있었기 때문에 타입으로 필터링하여 검색을 할 수 있었다.

1
2
3
4
5
6
7
8
GET twitter/user,tweet/_search
{
  "query": {
    "match": {
      "user_name": "kimchy"
    }
  }
}

_type필드는 document의 _id와 합쳐져 _uid필드를 만들어낸다. 따라서 다른 타입에 속하는 document들은 같은 _id를 가질 수 있다.

그렇다면 왜 mapping type을 없애는 걸까?

처음에 ES에서 소개할때 index는 SQL database의 데이터베이스와 유사하고, type은 테이블과 유사하다고 하였다.

하지만 이것은 유저들이 잘못된 방향으로 사용하도록 유도하였다. 

ES의 index에서는 같은 이름을 가졌지만 서로 다른 type에 속하는 field들이 있을때, 2 field는 같은 Lucene field에 속하게 된다. 따라서 A라는 type에 속하는 ABC라는 field과 B라는 type에 속하는 ABC라는 field는 같은 lucene field세 속하고, 따라서 같은 mapping을 가져야한다.

대체방안

1. document의 종류별로 각기 다른 index를 만든다. index들은 상호간에 독립적이기 때문에 위에서 언급한 문제가 없다.

2. Custom type field를 사용한다. 이를 적용하면:

1
2
3
4
5
6
7
8
9
10
11
12
13
PUT twitter/user/kimchy
{
  "name": "Shay Banon",
  "user_name": "kimchy",
  "email": "shay@kimchy.com"
}

PUT twitter/tweet/1
{
  "user_name": "kimchy",
  "tweeted_at": "2017-10-24T09:00:00Z",
  "content": "Types are going away"
}

가 이렇게 바뀐다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PUT twitter/_doc/user-kimchy
{
  "type": "user", 
  "name": "Shay Banon",
  "user_name": "kimchy",
  "email": "shay@kimchy.com"
}

PUT twitter/_doc/tweet-1
{
  "type": "tweet", 
  "user_name": "kimchy",
  "tweeted_at": "2017-10-24T09:00:00Z",
  "content": "Types are going away"
}

매핑도 이를 반영하기위에 좀 바뀌는데

원래는:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PUT twitter
{
  "mappings": {
    "user": {
      "properties": {
        "name": { "type": "text" },
        "user_name": { "type": "keyword" },
        "email": { "type": "keyword" }
      }
    },
    "tweet": {
      "properties": {
        "content": { "type": "text" },
        "user_name": { "type": "keyword" },
        "tweeted_at": { "type": "date" }
      }
    }
  }
}

이후에는:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PUT twitter
{
  "mappings": {
    "_doc": {
      "properties": {
        "type": { "type": "keyword" }, 
        "name": { "type": "text" },
        "user_name": { "type": "keyword" },
        "email": { "type": "keyword" },
        "content": { "type": "text" },
        "tweeted_at": { "type": "date" }
      }
    }
  }
}
더 읽어보기 »

[Elastic Search 공부하기] Information out: search and analyze

작성일 2019-11-14 | In ElasticSearch

ElasticSearch를 도입하기전, ElasticSearch에대해 공부하기 위해 레퍼런스를 읽으면서 번역&정리한 글입니다.

문서 전체를 번역한 것이 아니라 개인적으로 정리가 필요하다 싶은 내용만 정리하였습니다.

본문: ElasticSearch 공식레퍼런스

ElasticSearch(이하 ES)는 클러스터 매니징, 인덱싱, 데이터 검색을 위한 사용이 간단한 REST API를 제공한다. 그리고 테스트 목적으로 Command Line이나 Kibana의 개발자 콘솔에서 직접 request를 보낼 수도 있다.

ES의 REST API는 구조화된 쿼리, 텍스트 쿼리, 두개가 혼합된 쿼리를 지원한다. 만약 gender와 age field를 employee index에서 검색하고 hire_date에 따라 정렬한다면 Full-text 쿼리는 query string과 매치되는 모든 document를 찾고, 연관도에 따라 정렬한다.

더 읽어보기 »
1 … 3 4 5
Junsoo Choi

Junsoo Choi

41 포스트
8 카테고리
46 태그
RSS
Github LinkedIn
© 2021 Junsoo Choi
Contact: cjsjyh@gmail.com