paust-team / paust-db

GNU General Public License v3.0
6 stars 5 forks source link

Design client query interface #162

Open dragon0170 opened 5 years ago

dragon0170 commented 5 years ago

Reference

160

multi query, fetch 시나리오에 적합한 Query Interface를 설계하려고 합니다.

유저 시나리오

google maps에서 검색하는 시나리오와 비슷한 방식으로 timeseries query 시나리오 생성(https://github.com/paust-team/paust-db/issues/145#issuecomment-475202619)

  1. 지도 화면을 여의도로 두고 검색창에 "식당"을 검색하면 여러 개의 검색 결과 리스트가 출력됨. 이때의 결과 리스트는 해당하는 식당의 모든 정보를 가져오는 것이 아니라 상호명, 위치 등 일부만 가져와서 사용자에게 보여줌.
  2. 검색 결과 리스트에서 한 두개의 검색결과를 상세보기를 눌러서 실제 상세한 식당 정보를 확인.
  3. 몇몇 식당의 실제 정보를 보니 원하는 검색 결과가 아니라는 것을 사용자가 알게되면 "한식식당"과 같이 새로운 키워드로 검색을 함.
  4. 새로운 리스트에서 몇몇 식당의 상세 정보를 살펴보고 원하는 정보면 해당 검색 결과를 사용자가 사용함.

위 시나리오와 비슷한 느낌의 timeseries 데이터에 대한 multi query, fetch 시나리오는 다음과 같습니다.

  1. start, end timestamp와 qualifier로 구성된 Query를 사용자가 paust-db로 보내면 iterator를 return 받음.
  2. iterator의 Next operation을 통해 operation이 실행될 때마다 실제 데이터를 하나하나씩 fetch해서 가져올 수 있음.
  3. 초반 몇 개의 데이터를 보고 원하는 데이터가 아니면 기존의 Query를 수정해서 새로운 Query를 paust-db에 보냄. 예를 들어 start 시간을 1주일 더 과거로 변경.
  4. iterator의 Next operation을 통해 fetch한 몇개의 데이터를 확인하고 원하는 데이터면 나머지 데이터를 모두 iteration해서 가져옴.

    Client Query Interface 및 REPL console 사용 example(Go language)

    • Initialization
      db, err := pdb.GetPaustDB("localhost:26657")
    • Put
      err := db.Qualifier('key1','value1').Qualifier('key2','value2').Data('data').Put()
      // console example
      > db, err := pdb.GetPaustDB("localhost:26657")
      > db.Put(~~~)
      nil / Error - ~~~
    • Query
      var it PDBIterator
      err := db.From(timestamp1).To(timestamp2).Has('key', 'value').Query(&it)
      
      // console example
      > db, err := pdb.GetPaustDB("localhost:26657")
      > var it PDBIterator
      > db.~~~.Query(&it)
      nil / Error - ~~~
      > it.Next()
      "First Data"
      > it.Next()
      "Second Data"
      // 유저가 원하는 데이터라고 판단되어서 다 가져옴
      // console 상에서는 iterator가 있으면 자동을 iterate하여서 모든 데이터 출력하도록 함
      > it
      "Third Data"
      "Fourth Data"
      "Fifth Data"
      "Last Data"
      // 새로운 데이터를 Query하기 위해 from, to, has 등의 field를 clear
      > db.Clear()
      nil / Error - ~~~
      > db.~~~.Query(&it)
      nil / Error - ~~~
      > it
      ~~~
      ~~~

// multi query, fetch

db, err := pdb.GetPaustDB("localhost:26657") var it PDBIterator db.~~~.Query(&it) nil / Error - ~~~ it.Next() "First Data" it.Next() "Second Data" // 유저가 원하는 데이터가 아니다고 판단. From timestamp 변경해서 새롭게 Query db.From(another_timestamp).Query(&it) nil / Error - ~~~ it.Next() "Another First Data" it.Next() "Another Second Data" // 원하는 데이터라고 판단되어서 다 가져옴 it "Another Third Data" "Another Fourth Data" "Another Last Data"


#### 추후 생각해볼만한 기능
* streaming interface
* pagination interface
elon0823 commented 5 years ago

다른곳에서도 it 하면 iterator 의 처음부터 다 가져오는게아니라 current cursor 부터 끝까지의 데이터를 가져오게끔되나요?

dragon0170 commented 5 years ago

@elon0823 네 iterator의 현재 위치부터 for문을 돌면서 iterator가 끝날때까지 돌아가는 방식입니다. 위 방식은 Apache TinkerPop의 Gremlin Console에서 traversal 결과의 iterator 처리를 참고해서 만들었습니다.

1dennispark commented 5 years ago

음... 너무 REPL에 의존성을 두고 설계하는 것은 아닐까 싶네요. 제가 말한 interactive는 query들이 같은 context를 갖도록 하는 것이었습니다.

예를 들면, db.QueryContext(_).query().query().fetch().query().close() 이런 형태로 QueryContext로 context를 시작하고 마지막에 close로 context가 끝났다는 것을 명시해줄 수 있도록 할 수 있어야합니다.

사실 REPL은 직접 라이브러리를 사용하는 어플리케이션의 입장에서는 사용하기 힘들고 사용자의 interactive 만을 고려한 방식입니다.

실제 API로써 쓸 수 있는 프로그래밍 가능한 인터페이스는 아니라는 것이죠. 그러면 Application에서 사용할 수 있도록 만들려면 context가 존재해야할 것입니다.

dragon0170 commented 5 years ago

@co1god go의 context 기본 패키지를 이용해 context 개념을 도입해보았습니다.(https://golang.org/pkg/context/)

// API 사용 example

db := pdb.GetPaustDB(endpoint)
ctx := db.QueryContext()
var data pdb.Data
var allData []pdb.Data

ctx.Query(From(timestamp1),To(timestamp2),Has('key1','value1'))
ctx.Fetch(&data)
ctx.Fetch(&data)
ctx.Query(Has('key2','value2'))
ctx.Fetch(&data)
ctx.FetchAll(allData)
ctx.Close()

// interface

type QueryContext struct {
  endpoint string
  it PDBIterator
  ...
  context context.Context
}

// interface implementation

func (ctx *QueryContext) Query(opts ...QueryOption) *QueryContext {
  ...
  // query option을 모두 QueryContext내의 context에 WithValue 함수를 통해 저장

  timeoutCtx, cancel := context.WithTimeout(ctx.Context(), time.Second * 5)
  defer cancel()

  response := make(chan QueryResponse)
  defer close(response)

  go func() {
        // paust-db에 실제로 query를 보내고 response를 받는 부분
        response <- queryPDB(timeoutCtx)
  }

  select {
        case result := <-response:
            // query response가 돌아옴
            // paust-db에 query한 결과를 QueryContext내에 iterator로 담기
        case <-timeoutCtx.Done():
            // response가 오기전에 timeout 발생
  }
}

func (ctx *QueryContext) Fetch(*Data) *QueryContext {
  // iterator fetch한 데이터를 Data 변수에 담아서 주기
  // iterator에서 Next operation 역할
}

func (ctx *QueryContext) FetchAll([]Data) *QueryContext {
  // iterator가 끝날때까지 돌면서 fetch한 데이터를 Data slice로 담아서 주기
}
dragon0170 commented 5 years ago

@code-to-gold 피드백 결과, 여러 range에 대한 query와 같이 다양한 유저 시나리오에 대해 구체화하고 이런 시나리오들을 충족할 수 있는 paust-db client의 context를 설계할 예정입니다.

dragon0170 commented 5 years ago

timeseries data streaming 시나리오의 데이터 flow 관점에서 생각해보았습니다. 간단히 생각해본 timeseries data streaming에 대한 예시는 다음과 같은 것들이 있습니다.

이런 예시들의 공통점은 특정 streaming source로부터의 데이터를 실시간으로 그리고 지속적으로 관찰/사용한다는 것입니다. 이 포인트에서 데이터의 flow를 살펴보겠습니다.

데이터를 put 하는 과정(생산자)

dragon0170 commented 5 years ago

@co1god @code-to-gold @elon0823 피드백 해주시기 바랍니다

Put(저장) 시나리오

Query(검색/스트리밍) 시나리오

code-to-gold commented 5 years ago

Query와 Fetch를 한번에 쓰신것 같습니다.

제가 생각하기에 스트리밍의 경우 Put 과 Fetch는 Client와 Server간에 채널이 생성되어 지속적으로 데이터를 저장, 혹은 획득하는 기능이 될 것 같아요. 그리고 Query는 지속적으로 데이터를 획득할 source를 검색하고, 해당 정보를 저장해 놓는 기능이 될 것 같습니다. 이러한 관점에서 위에 적어 놓은 '필터'로 grouping하신 기능들이 query와 fetch중 어디에 필요한 기능인가, 그리고 정말 필요한 기능인가 다시 한번 생각해보셔야 할 것 같습니다. split 필터만 봐도 어떻게 데이터를 획득할 지에 대한 설정으로 보입니다. split이란 이름도 좀 이상하네요..ㅋㅋ

Query와 Fetch를 분리해서 다시 한번 작성 부탁드립니다.

dragon0170 commented 5 years ago

데이터 획득 flow

dragon0170 commented 5 years ago

hbase async scanner 정리