미분류

랭킹을 위한 맵 앤 리듀스 뷰 사용하기

6 분 읽기

Starting from version 2.0, Couchbase server offers a powerful way of creating indexes for JSON documents through the concept of views.

Using views, it is possible to define primary indexes, composite indexes and aggregations allowing to:

. query documents on different JSON properties

. create statistics and aggregates

Views generate materialized indexes so provide a fast and efficient way for executing pre-defined queries.

This blog provides a simple example of how a view using map and reduce can be created to index a JSON document attribute but also to determine document ranking based on that attribute.

Using map and reduce is a very fast efficient way to determine ranking and can scale across million of users and provide very fast ranking lookup. Thanks Aaron for teaching me this!

This can be used for instance for ranking users based on score or experience.

This blog illustrates that concept for a user document with 2 attributes: name and experience, index this document based on experience and allow determining ranking based on the experience attribute.


We will first write some Java code allowing connecting to Couchbase server and creating User documents. The following Java code is self-contained and will create the users.

It is leveraging Gson Google code libraries for creating JSON objects.

 

package blog;

import com.couchbase.client.CouchbaseClient;

import com.couchbase.client.CouchbaseConnectionFactoryBuilder;

import com.google.gson.Gson;

import java.io.IOException;

import java.io.UnsupportedEncodingException;

import java.net.URI;

import java.util.ArrayList;

class UserDoc {

   String name;

   long experience;

   UserDoc(String name, long experience) {

       this.name = name;

       this.experience = experience;

   }

}

/**

*

* @author alexis

*/

public class RankView {

   private static CouchbaseClient client;

   public static void main(String[] args) throws UnsupportedEncodingException, IOException {

       ArrayList nodes = new ArrayList();

       // Add one or more nodes of your cluster (exchange the IP with yours)

       nodes.add(URI.create("https://127.0.0.1:8091/pools"));

       // Try to connect to the client

       CouchbaseConnectionFactoryBuilder cfb = new CouchbaseConnectionFactoryBuilder();

       cfb.setOpTimeout(10000);

       cfb.setReadBufferSize(1024);

       cfb.setShouldOptimize(true);

       cfb.setTimeoutExceptionThreshold(100);

       try {

           client = new CouchbaseClient(cfb.buildCouchbaseConnection(nodes, "default", ""));

       } catch (Exception e) {

           System.err.println("Error connecting to Couchbase: " + e.getMessage());

           System.exit(1);

       }

       UserDoc user = null;

       // Creates users

       for (int i = 0; i

           user = new UserDoc("User" + i, Math.round(Math.random()*1000));

           Gson json = new Gson();

           String jsonString = json.toJson(user);

           client.set(user.name, 0, jsonString);

       }

       client.shutdown();

   }

}

After running this program (please change the URL or bucket name as appropriate) you should now have 10 users in your bucket.

 

The next step is to a User design document with a Rank view.

The first step is create a simple Map for the Rank view which will emit the experience attribute:

function (doc, meta) {

 if (doc.experience)

  emit(doc.experience, null);

}

Doing so will create an index based on experience attribute but will not allow to determine the ranking.

This is where adding a Reduce fits in. We will add simple built-in _count reduce.

The full view should look like this:

The Reduce function allows to aggregate the number of User documents with a known experience value (if doc.experience).

By not specifying any query parameter, it will output 10 which is the number of documents that we have created.

In order to look up the ranking of a specific User what we need to do is first look up the ranking of a given user (User5 in this example):

       // Look up a specific user

       String jsonString = (String) client.get(“User5”);

       user = json.fromJson(jsonString, UserDoc.class);

From there, we can create a query which will filter this count with a range query with a descending order which will start by max value to capture all the users which have a greater experiencethe experience and end with value for the user:

       View view = client.getView(“User”, “Rank”);

       Query query = new Query();

       query.setIncludeDocs(true).setLimit(10000);

       query.setRangeStart(Long.toString(Long.MAX_VALUE));

       query.setRangeEnd(Long.toString(user.experience));

       query.setDescending(true);

       query.setReduce(true);

As such the reduce will output the number of users which have a greater experience than the experience for that user. The ranking is simply that number + 1.

       ViewResponse response = client.query(view, query);

       Iterator itr = response.iterator();

       while (itr.hasNext()) {

           ViewRow row = itr.next();

           System.out.println(“Rank: ” + Long.parseLong(row.getValue()) + 1 );

       }

This will output the Rank based on the experience for that user such as:

Rank: 7

The full Java code (again self contained) is:

package blog;

import com.couchbase.client.CouchbaseClient;

import com.couchbase.client.CouchbaseConnectionFactoryBuilder;

import com.couchbase.client.protocol.views.Query;

import com.couchbase.client.protocol.views.View;

import com.couchbase.client.protocol.views.ViewResponse;

import com.couchbase.client.protocol.views.ViewRow;

import com.google.gson.Gson;

import java.io.IOException;

import java.io.UnsupportedEncodingException;

import java.net.URI;

import java.util.ArrayList;

import java.util.Iterator;

class UserDoc {

   String name;

   long experience;

   UserDoc(String name, long experience) {

       this.name = name;

       this.experience = experience;

   }

}

/**

*

* @author alexis

*/

public class RankView {

   private static CouchbaseClient client;

   public static void main(String[] args) throws UnsupportedEncodingException, IOException {

       ArrayList nodes = new ArrayList();

       // Add one or more nodes of your cluster (exchange the IP with yours)

       nodes.add(URI.create(“https://127.0.0.1:8091/pools”));

       // Try to connect to the client

       CouchbaseConnectionFactoryBuilder cfb = new CouchbaseConnectionFactoryBuilder();

       cfb.setOpTimeout(10000);

       cfb.setReadBufferSize(1024);

       cfb.setShouldOptimize(true);

       cfb.setTimeoutExceptionThreshold(100);

       try {

           client = new CouchbaseClient(cfb.buildCouchbaseConnection(nodes, “default”, “”));

       } catch (Exception e) {

           System.err.println(“Error connecting to Couchbase: ” + e.getMessage());

           System.exit(1);

       }

       UserDoc user = null;

       Gson json = null;

       // Creates users

       for (int i = 0; i

           user = new UserDoc(“User” + i, Math.round(Math.random() * 1000));

           json = new Gson();

           String jsonString = json.toJson(user);

           client.set(user.name, 0, jsonString);

       }

       // Look up a specific user

       String jsonString = (String) client.get(“User5”);

       user = json.fromJson(jsonString, UserDoc.class);

       View view = client.getView(“User”, “Rank”);

       Query query = new Query();

       query.setIncludeDocs(true).setLimit(10000);

       query.setRangeStart(Long.toString(Long.MAX_VALUE));

       query.setRangeEnd(Long.toString(user.experience));

       query.setDescending(true);

       query.setReduce(true);

       ViewResponse response = client.query(view, query);

       Iterator itr = response.iterator();

       while (itr.hasNext()) {

           ViewRow row = itr.next();

           System.out.println(“Rank: ” + Long.parseLong(row.getValue()) + 1 );

       }

       client.shutdown();

   }

}

Using a map and reduce view for ranking allows to quickly and very efficiently look up a rank without having to do additional processing on the client side.

To learn more about views and queries in Couchbase, read: https://www.couchbase.com/docs/couchbase-devguide-2.1.0/indexing-querying-data.html

For questions and comments: alexis@couchbase.com or @alexisroos on Twitter.

이 기사 공유하기

작가

알렉시스 루스(Alexis Roos)는 세일즈포스의 시니어 엔지니어링 매니저입니다. 알렉시스는 20년 이상의 소프트웨어 엔지니어링 경력을 가지고 있으며, 최근 5년 동안은 유럽의 SI 업체, 썬 마이크로시스템즈/오라클, 그리고 Radius Intelligence, Concurrent, Couchbase를 포함한 여러 스타트업에서 대규모 데이터 과학 및 엔지니어링에 집중해 왔습니다.

4개의 응답

  1. Guest 아바타
    Guest

    I wonder the syntax in C Library matched for ‘setIncludeDocs’ in java..

  2. Patrick 아바타
    Patrick

    A overview of storing game data in couchbase: https://www.couchbase.com/work...

  3. Guest 아바타
    Guest

    If multiple people are on the same rank say rank 1,2 and 3 have a score of 100 then it will always return their rank as 3rd. Not sure how to fix this.

    1. Stu Powell 아바타
      Stu Powell

      To return players with matching user.experience values as the highest rank rather than lowest, I used the workaround of querying on (user.experience+1), then adding 1 to the derived rank. Seemed to do the trick.

댓글 남기기

카우치베이스 카펠라를 시작할 준비가 되셨나요?

개발 시작하기

NoSQL을 탐색하고, 리소스를 찾아보고, 튜토리얼을 시작하려면 개발자 포털을 확인하세요.

카펠라 프리 사용하기

단 몇 번의 클릭으로 카우치베이스(Couchbase)를 직접 체험해 보세요. Capella DBaaS는 시작하기 가장 쉽고 빠른 방법입니다.

연락해

Couchbase 제품군에 대해 더 알고 싶으신가요? 저희가 도와드리겠습니다.