33.8. Traversal


The Matrix

이것은 우리가 순회할  첫번째 그래프 모습이다.


Figure 33.2. Matrix node space view

examples-matrix.png


친구들  / 친구들의 친구들


private Traverser getFriends(
        final Node person )
{
    TraversalDescription td = graphDb.traversalDescription()
            .breadthFirst()
            .relationships( RelTypes.KNOWS, Direction.OUTGOING )
            .evaluator( Evaluators.excludeStartPosition() );
    return td.traverse( person );
}


 실제 순회를 돌아보고 결과를 찍어보자.

int numberOfFriends = 0;
String output = neoNode.getProperty( "name" ) + "'s friends:\n";
Traverser friendsTraverser = getFriends( neoNode );
for ( Path friendPath : friendsTraverser )
{
    output += "At depth " + friendPath.length() + " => "
              + friendPath.endNode()
                      .getProperty( "name" ) + "\n";
    numberOfFriends++;
}
output += "Number of friends found: " + numberOfFriends + "\n";


결과:

Thomas Anderson's friends:
At depth 1 => Morpheus
At depth 1 => Trinity
At depth 2 => Cypher
At depth 3 => Agent Smith
Number of friends found: 4


누가 매트릭스를 코드했나?


private Traverser findHackers( final Node startNode )
{
    TraversalDescription td = graphDb.traversalDescription()
            .breadthFirst()
            .relationships( RelTypes.CODED_BY, Direction.OUTGOING )
            .relationships( RelTypes.KNOWS, Direction.OUTGOING )
            .evaluator(
                    Evaluators.includeWhereLastRelationshipTypeIs( RelTypes.CODED_BY ) );
    return td.traverse( startNode );
}


:결과 

String output = "Hackers:\n";
int numberOfHackers = 0;
Traverser traverser = findHackers( getNeoNode() );
for ( Path hackerPath : traverser )
{
    output += "At depth " + hackerPath.length() + " => "
              + hackerPath.endNode()
                      .getProperty( "name" ) + "\n";
    numberOfHackers++;
}
output += "Number of hackers found: " + numberOfHackers + "\n";


이제 우리는 알게됬다.

Hackers:
At depth 4 => The Architect
Number of hackers found: 1



순서에 따라서  경로를 걸어보자


토이그래프를 만들자

Node A = db.createNode();
Node B = db.createNode();
Node C = db.createNode();
Node D = db.createNode();

A.createRelationshipTo( C, REL2 );
C.createRelationshipTo( D, REL3 );
A.createRelationshipTo( B, REL1 );
B.createRelationshipTo( C, REL2 );

example-ordered-path.svg


Now, the order of relationships (REL1 → REL2 → REL3) is stored in an ArrayList. Upon traversal, the Evaluator can check against it to ensure that only paths are included and returned that have the predefined order of relationships:


어떻게 경로를 걸어갈지 정의하자.

final ArrayList<RelationshipType> orderedPathContext = new ArrayList<RelationshipType>();
orderedPathContext.add( REL1 );
orderedPathContext.add( withName( "REL2" ) );
orderedPathContext.add( withName( "REL3" ) );
TraversalDescription td = db.traversalDescription()
        .evaluator( new Evaluator()
        {
            @Override
            public Evaluation evaluate( final Path path )
            {
                if ( path.length() == 0 )
                {
                    return Evaluation.EXCLUDE_AND_CONTINUE;
                }
                RelationshipType expectedType = orderedPathContext.get( path.length() - 1 );
                boolean isExpectedType = path.lastRelationship()
                        .isType( expectedType );
                boolean included = path.length() == orderedPathContext.size() && isExpectedType;
                boolean continued = path.length() < orderedPathContext.size() && isExpectedType;
                return Evaluation.of( included, continued );
            }
        } )
        .uniqueness( Uniqueness.NODE_PATH );

Note that we set the uniqueness to Uniqueness.NODE_PATH as we want to be able to revisit the same node dureing the traversal, but not the same path.


순회를 하고 결과를 찍어보자

Traverser traverser = td.traverse( A );
PathPrinter pathPrinter = new PathPrinter( "name" );
for ( Path path : traverser )
{
    output += Paths.pathToString( path, pathPrinter );
}


결과:

(A)--[REL1]-->(B)--[REL2]-->(C)--[REL3]-->(D)


여기서 우리는 패스 아웃풋을 표현하기위해 커스텀 클래스를 사용했다. 

static class PathPrinter implements Paths.PathDescriptor<Path>
{
    private final String nodePropertyKey;

    public PathPrinter( String nodePropertyKey )
    {
        this.nodePropertyKey = nodePropertyKey;
    }

    @Override
    public String nodeRepresentation( Path path, Node node )
    {
        return "(" + node.getProperty( nodePropertyKey, "" ) + ")";
    }

    @Override
    public String relationshipRepresentation( Path path, Node from, Relationship relationship )
    {
        String prefix = "--", suffix = "--";
        if ( from.equals( relationship.getEndNode() ) )
        {
            prefix = "<--";
        }
        else
        {
            suffix = "-->";
        }
        return prefix + "[" + relationship.getType().name() + "]" + suffix;
    }
}


Uniqueness of Paths in traversals


This example is demonstrating the use of node uniqueness. Below an imaginary domain graph with Principals that own pets that are descendant to other pets.

Figure 33.3. Descendants Example Graph

Descendants-Example-Graph-Uniqueness-of-Paths-in-traversals.svg

In order to return all descendants of Pet0 which have the relation owns to Principal1 (Pet1 and Pet3), the Uniqueness of the traversal needs to be set to NODE_PATH rather than the default NODE_GLOBAL so that nodes can be traversed more that once, and paths that have different nodes but can have some nodes in common (like the start and end node) can be returned.

final Node target = data.get().get( "Principal1" );
TraversalDescription td = db.traversalDescription()
        .uniqueness( Uniqueness.NODE_PATH )
        .evaluator( new Evaluator()
{
    @Override
    public Evaluation evaluate( Path path )
    {
        boolean endNodeIsTarget = path.endNode().equals( target );
        return Evaluation.of( endNodeIsTarget, !endNodeIsTarget );
    }
} );

Traverser results = td.traverse( start );

This will return the following paths:

(2)--[descendant,2]-->(3)<--[owns,5]--(4)
(2)--[descendant,0]-->(0)<--[owns,3]--(4)

In the default path.toString() implementation, (1)--[knows,2]-->(4) denotes a node with ID=1 having a relationship with ID 2 or type knows to a node with ID-4.

Let’s create a new TraversalDescription from the old one, having NODE_GLOBAL uniqueness to see the difference.

[Tip]Tip

The TraversalDescription object is immutable, so we have to use the new instance returned with the new uniqueness setting.

TraversalDescription nodeGlobalTd = td.uniqueness( Uniqueness.NODE_GLOBAL );
results = nodeGlobalTd.traverse( start );

Now only one path is returned:

(2)--[descendant,2]-->(3)<--[owns,5]--(4)


Social network

Social networks (know as social graphs out on the web) are natural to model with a graph. This example shows a very simple social model that connects friends and keeps track of status updates.


Simple social model

Figure 33.4. Social network data model

socnet-model.svg

The data model for a social network is pretty simple: Persons with names and StatusUpdates with timestamped text. These entities are then connected by specific relationships.

  • Person

    • friend: relates two distinct Person instances (no self-reference)
    • status: connects to the most recent StatusUpdate
  • StatusUpdate

    • next: points to the next StatusUpdate in the chain, which was posted before the current one

Status graph instance

The StatusUpdate list for a Person is a linked list. The head of the list (the most recent status) is found by following status. Each subsequent StatusUpdate is connected by next.

Here’s an example where Andreas Kollegger micro-blogged his way to work in the morning:

andreas-status-updates.svg

To read the status updates, we can create a traversal, like so:

TraversalDescription traversal = graphDb().traversalDescription()
        .depthFirst()
        .relationships( NEXT );

This gives us a traverser that will start at one StatusUpdate, and will follow the chain of updates until they run out. Traversers are lazy loading, so it’s performant even when dealing with thousands of statuses — they are not loaded until we actually consume them.

Activity stream

Once we have friends, and they have status messages, we might want to read our friends status' messages, in reverse time order — latest first. To do this, we go through these steps:

  1. Gather all friend’s status update iterators in a list — latest date first.
  2. Sort the list.
  3. Return the first item in the list.
  4. If the first iterator is exhausted, remove it from the list. Otherwise, get the next item in that iterator.
  5. Go to step 2 until there are no iterators left in the list.

Animated, the sequence looks like this.

The code looks like:

PositionedIterator<StatusUpdate> first = statuses.get(0);
StatusUpdate returnVal = first.current();

if ( !first.hasNext() )
{
    statuses.remove( 0 );
}
else
{
    first.next();
    sort();
}

return returnVal;




http://neo4j.com/docs/stable/tutorials-java-embedded-traversal.html

'NoSQL' 카테고리의 다른 글

Neo4j - 인덱스 사용하기  (0) 2015.10.30
Neo4j 인사이드 : 파일 스토리지  (0) 2015.10.30
시계열 DB (OpenTSDB , 인플럭스 DB , Graphite ) 정리  (0) 2015.10.22
MongoDB vs Couchbase (2)  (0) 2015.09.03
MongoDB vs Couchbase (1)  (0) 2015.09.03


Neo4j 의 인덱스를 만들고  데이터 넣고, 검색하기 


- 일단 서버부터 시작하자.

GraphDatabaseService graphDb = new GraphDatabaseFactory().newEmbeddedDatabase( DB_PATH );


- 이름으로  유저를 인덱스하기위해 데이타베이스를 설정해야한다. 한번에 완료되야 한다.

IndexDefinition indexDefinition;
try ( Transaction tx = graphDb.beginTx() )
{
    Schema schema = graphDb.schema();
    indexDefinition = schema.indexFor( DynamicLabel.label( "User" ) )
            .on( "username" )
            .create();
    tx.success();
}


-인덱스는 처음 생성될때 비동기적으로 구성된다.  완료되기 기다리길 원할때  코어 API 를 사용할수있다.

try ( Transaction tx = graphDb.beginTx() )
{
    Schema schema = graphDb.schema();
     ....

    schema.awaitIndexOnline( indexDefinition, 10, TimeUnit.SECONDS );
}


- User 를  추가해 보자.

try ( Transaction tx = graphDb.beginTx() )
{
    Label label = DynamicLabel.label( "User" );

    // Create some users
    for ( int id = 0; id < 100; id++ )
    {
        Node userNode = graphDb.createNode( label );
        userNode.setProperty( "username", "user" + id + "@neo4j.org" );
    }
    System.out.println( "Users created" );
    tx.success();
}

- id 로 User 를 찾는 방법.
Label label = DynamicLabel.label( "User" );
int idToFind = 45;
String nameToFind = "user" + idToFind + "@neo4j.org";
try ( Transaction tx = graphDb.beginTx() )
{
    try ( ResourceIterator<Node> users =
            graphDb.findNodes( label, "username", nameToFind ) )
    {
        ArrayList<Node> userNodes = new ArrayList<>();
        while ( users.hasNext() )
        {
            userNodes.add( users.next() );
        }

        for ( Node node : userNodes )
        {
            System.out.println( "The username of user " + idToFind + " is " + node.getProperty( "username" ) );
        }
    }
}


- User 의 이름을 업데이트 할때, 인덱스도 업데이트 됨.

try ( Transaction tx = graphDb.beginTx() )
{
    Label label = DynamicLabel.label( "User" );
    int idToFind = 45;
    String nameToFind = "user" + idToFind + "@neo4j.org";

    for ( Node node : loop( graphDb.findNodes( label, "username", nameToFind ) ) )
    {
        node.setProperty( "username", "user" + ( idToFind + 1 ) + "@neo4j.org" );
    }
    tx.success();
}


 - User 를 삭제하면 자동적으로 인덱스에서 삭제됨.

try ( Transaction tx = graphDb.beginTx() )
{
    Label label = DynamicLabel.label( "User" );
    int idToFind = 46;
    String nameToFind = "user" + idToFind + "@neo4j.org";

    for ( Node node : loop( graphDb.findNodes( label, "username", nameToFind ) ) )
    {
        node.delete();
    }
    tx.success();
}


- 데이터 모델을 바꿀때  , 필요없어진 인덱스를 드랍 할수있다.

try ( Transaction tx = graphDb.beginTx() )
{
    Label label = DynamicLabel.label( "User" );
    for ( IndexDefinition indexDefinition : graphDb.schema()
            .getIndexes( label ) )
    {
        // There is only one index
        indexDefinition.drop();
    }

    tx.success();
}



http://neo4j.com/docs/stable/tutorials-java-embedded-new-index.html

'NoSQL' 카테고리의 다른 글

Neo4j - Traversal  (0) 2015.10.30
Neo4j 인사이드 : 파일 스토리지  (0) 2015.10.30
시계열 DB (OpenTSDB , 인플럭스 DB , Graphite ) 정리  (0) 2015.10.22
MongoDB vs Couchbase (2)  (0) 2015.09.03
MongoDB vs Couchbase (1)  (0) 2015.09.03

Neo4j Hello world~!!


인사이드를 살펴보기전에 Neo4j 의 사용 예를 간단하게 체크하자.


-  Relationship 타입을 enum 으로 만든다. 

private static enum RelTypes implements RelationshipType { KNOWS // 아는 관계 ( 친구 , 가족 뭐 그런..) }


- 관련 변수 선언 

GraphDatabaseService graphDb;
Node firstNode;
Node secondNode;
Relationship relationship;


- 데이터 베이스 시작 

graphDb = new GraphDatabaseFactory().newEmbeddedDatabase( DB_PATH );
registerShutdownHook( graphDb );


- 트랜잭션으로 todo list  감싸기  

try ( Transaction tx = graphDb.beginTx() )
{
    // Database operations go here
    tx.success();
}


- 작은 그래프 만들어보자.

firstNode = graphDb.createNode();
firstNode.setProperty( "message", "Hello, " );
secondNode = graphDb.createNode();
secondNode.setProperty( "message", "World!" );

relationship = firstNode.createRelationshipTo( secondNode, RelTypes.KNOWS );
relationship.setProperty( "message", "brave Neo4j " );

  



- 그래프를 출력해보고 

System.out.print( firstNode.getProperty( "message" ) );
System.out.print( relationship.getProperty( "message" ) );
System.out.print( secondNode.getProperty( "message" ) );

- 그래프  데이터를 삭제해보자

// let's remove the data
firstNode.getSingleRelationship( RelTypes.KNOWS, Direction.OUTGOING ).delete();
firstNode.delete();
secondNode.delete();


위의 소스를 보면 중요한 3가지 특성이 있다는것을 알수있다.

1. 노드  (firstNode, SecondNode) : '노드 속성을 가지고있다'

2. 관계  (relationship) : '관계 타입, 노드 속성 , 노드  방향을 가지고있다.'

3. 속성  ( 노드속성 :  "hello" , 'world'  /  관계 속성 : "brave neo4j")


이제 이정도만 기억해두고 이런 정보들을 과연 파일에 어떻게 저장하는지에 관해 알아보자.


Neo4j Internals: File Storage



1.저장되는  파일 이름들 

   - 노드 .db  파일  (neostore.nodestore.db)
   - 관계 .db  파일  (neostore.relationshipstore.db)
   - 노드 .id   파일  (neostore.nodestore.db.id)
   - .propertystore   파일  (neostore.propertystore.db)
   - .index   파일  (neostore.propertystore.db.index)


2. 재 사용되는 Ids

    neo 의 데이터가 동일한 사이즈의 청크로 저장된다고 가정해보자. 이걸 "chunks records" 라고 말한다고 치고, 
각각 레코드들은 그것과 연계된 id 를 가지고있다고하자.  파일안에서 데이터를 어떻게 찾을수있을까?
만약 데이타베이스에 대해서 좀 알고있다면 , 다양한 파일 엑세스 기법들에 대해서 떠오를것이다.
제일 쉬운방법은 id 를 자동적으로 하나씩 증가하게하면, 파일내에서 id 의 위치는 id * 레코드사이즈가 될것이다.
이때 문제가 하나 생기는데, 중간에 id 들이 삭제되면 어떻게 될냐는건데, 새로운 노드를 그 중간에 붕 뜬 자리에 
넣어주면되는데, 위에서 말한바와 같이 id 와 파일에서의 offset 은 id * 레코드사이즈라는 관계가 있다는 문제가 있다.
그래서 이걸 해결하기위한 방법은 지워진 id 를 기억해두었다가, 새로운 레코드에 적용시켜주면 되는것이다.
결국 id 를 요청하면 , 서버는 재사용되는 id 가 있으면 할당해주고, 없으면 새로운 id 를 만들어주는 전력을 취하면된다.


3. idGenerator 와 데이터 파일 

    - neo 서버가 영원히 메모리위에 있을수가 없기때문에, 저런 재사용 ids 를 파일에 저장해두어야한다.
 - org.neo4j.kernel.impl.nioneo.store.IdGenerator 와 이것의 구현체인 IdGeneratorImpl 클래스가 사용된다.
 - 모든 데이터파일의 관리자는 IdGenerator 를 유지해야한다. 
 - "id" 파일은 ".db" 파일과  나란히 연결되있다.
 - 위에 헬로월드를 실행시키고 (삭제하기전에)  neostore.nodestore.db.id 파일을 살펴보면 , 9바이트를 볼수있는데
   처음 0 은 클린 셧다운을 말하고, 나머지 8 바이트는 3 의 long 타입이다. 이 3은 다음 사용될 id 를 나타낸다. 
   1과 2는 이미 사용되고있다. 지워진 노드가 없기때문에 재사용 ids 도 물론 없다. 

4. Neo4j 의 두가지 저장법.

      가.org.neo4j.kernel.impl.nioneo.store.AbstractStore
           -  고정된 사이즈의 레코드를 다룬다.

      나. org.neo4j.kernel.impl.nioneo.store.AbstractDynamicStore
          -  다양한 사이즈의 레코드를 블럭안에 저장한다. 그리고 LinkedList 같이 디스크상에서 연결해서  저장한다. 

       - 두가지 모두 그들의 빌딩블록들과 연결된 ids 를 사용한다. 차이점은 AbstractStore 는 바깥 세상과 ids 를 가지고
         소통한다는 점이고, DynamicStore 의 ids 는 오직 클래스안에서 의미를 가진다는점이다. 



Record - oriented storage : The primitives


 5. 노드 스토어 

        - org.neo4j.kernel.impl.nioneo.store.NodeStore 는 neostore.nodestore.db 파일을 만든다.
        - 모든 노드는 여기에 저장된다.
        - 간단하게 9 바이트의 고정 레코드를 살펴보면, 첫바이트는 사용 플레그이고, 그 다음 4바이트 integer 는
          relationships 의 첫번째 id 이고, 마지막 4 byte 는 이 노드가 가지고있는 첫번째 속성의 id 이다.  
        - AbstractStore 안의 레코드들은 ids 를 저장하지 않는다.  그들의 파일에서의 오프셋은 그들의 id 를 정의하며 
          그들의 id 는 그들의 파일에서의 오프셋을 정의한다. 


 6.  관계 스토어 

         - org.neo4j.kernel.impl.nioneo.store.RelationshipStore 는 neostore.relationshipstore.db 파일을 만든다.
         - 33  바이트의 관계 레코드에서, 
                * 첫번째 바이트는 사용플레그
                * 4 바이트 :  첫번째 노드의 id 
                * 4 바이트 :  두번째 노드의 id
                * 4 바이트 :  이 관계의 타입을 나타내는 레코드의 id   
                * 4 바이트 * 4  : 첫번째 노드의 이전관계 id  /  첫번째 노드의 다음 관계 id  /   
                                          두번째 노드의 이전관계 id  /  두번째 노드의 다음 관계 id  
                * 4 바이트 : 이 관계의 첫번째 속성의 id 
            - 관계는 디스크상에서 이중링크드리스트의 형태이다.

 7.  관계 타입  스토어 

         org.neo4j.kernel.impl.nioneo.store.RelationshipTypeStore 는 neostore.relationshipTypestore.db 파일 생성. 

         - 관계 타입 이름을 저장하기위한 장소가 필요한데 neostore.relationshipTypestore.db.names 파일이다.    


  8 . 속성  스토어  

         org.neo4j.kernel.impl.nioneo.store.PropertyStore 는 neostore.propertystore.db 파일을 만든다. 

           - 각각의 속성 레코드들은 in_use 바이트과 속성의 타입을 저장하는 integer 로 시작된다.


  Block - oriented storage: Strings and arrays


 * 이전 스토리지엔진들은 고정크기의 레코드들을 가지고, 다른 레코드들로의 포인터 및 작은 데이터로  구성되었다. 
   당신의  소중하고, 다양한크기를 가진 문자열 또는 배열 속성 값들은 다른 방식으로 다루어져야한다.  
   이것이 DynamicStores 가 만들어진 이유이다. 우리는 이미 PropertyStore 나 RelaltionshipTypeStore 의 부분으로 
   그것의 사용법을 살펴보았는데, 모두다 .Strings 나 배열같은 자바 기본 요소가 필요하다.
   이제 그들의 구조를 살펴보자.

   9.   일반 블록 구조 

    다양한 사이즈의 임의의 데이터는  dynamic store 파일안에 이중 링크드 블럭 구조에 의해  수용되어진다.
   여기서 필요로
 되어지는것은 in_use byte /  이전 블럭 / 다음 블럭으로의 포인터 / 데이타의 크기 및 데이터이다. 
   이것의 크기는  파일안에서의 오프셋을 발견하기 위해 가능한 맥시점 블럭사이즈 에 의해 한계가 지어진다. 만약 하나의
   데이터가  blockSize 보다 더 큰 
공간을 요구하면, 그 만큼의 블럭들이 그것의 체인에 알맞게 추가된다.
   그러나 체인의 마지막 블럭은 (만약 하나라면 첫번째)
 할당된 양보다 적게  사용가능하다. 

    각각 블럭헤더의 레이아웃은 in_use byte, 이전 블럭의 int id (만약 -1 이면 첫번째) , 길이 , 다음블럭의 int id,  이 
    13바이트 
이후에는 맥스 blockSize 크기의 데이터가 온다.전체 블럭의 크기는 고로 13 + blockSize 이다. 


  10.   Strings vs Arrays 


org.neo4j.kernel.impl.nioneo.store.DynamicArrayStore 

org.neo4j.kernel.impl.nioneo.store.DynamicStringStore 


  11.   개인적 노트



  12. 마무리

  몇몇 부분 정확한 이해없이 대략 정리한것이기때문에 잘못된것이 있을수 있습니다. 

  하기 레퍼런스를 보시면 더 정확한 정보를 알수있습니다.           



레퍼런스:

http://neo4j.com/docs/stable/tutorials-java-embedded-hello-world.html

http://digitalstain.blogspot.kr/2010/10/neo4j-internals-file-storage.html

'NoSQL' 카테고리의 다른 글

Neo4j - Traversal  (0) 2015.10.30
Neo4j - 인덱스 사용하기  (0) 2015.10.30
시계열 DB (OpenTSDB , 인플럭스 DB , Graphite ) 정리  (0) 2015.10.22
MongoDB vs Couchbase (2)  (0) 2015.09.03
MongoDB vs Couchbase (1)  (0) 2015.09.03

만약 자신의 집에서 사용중인 전기를 측정하는 센서가있다고 치고 , 그 센서는 1초에 한번씩 전력값을 측정한다고 치자. (사실 센서에서는 수천번 측정한다고한다.그것들의 1초 평균값을 가져온다고 생각하자. 더 상세하게 분석하려면 1초 평균값,최대피크값등등 가져오면 더 좋을것이다.물론BM에따라서는 1분당 , 1시간당 평균,최저,최고값을 사용할수도있겠다.)  이 측정 값을 분석해서 , 집안에서 사용중인 모든 가전제품등의 대기전력 및 여러가지 패을 알아낸다고 하자.  

막간 상식 코너 ~! 

와트(W)  : 움직이는 전하는 일을 한다. 예를 들어 열을 발생시키거나 전동기를 회전시키는 등의 일을 한다. 일의 능률(전기에너지가 빛이나 열에너지로 바뀌는 비율)을 전력이라 한다. 

전력 (P) : 단위시간(1초) 동안 전기기구에서 소모하는 전기에너지의 량. 

P  =  V(볼트) * A(암페어) 




1초에 한번씩 생성되는 데이터를   시간/값 => ( "2015-10-15 10:50:30" , 563.34)  라고 하면  Postgresql 같은 일반 관계형데이터베이스에 저장한다고 치자.   

timestamp  or Character (20 byte) : real  (4byte) 이렇게 2가지의 컬럼이 필요하겠지.  

대략 25 바이트라고치고 , 

1분에 1500 byte 

1시간에 90000 byte  

1일에    2160000 byte   (대략 2 mb)

1달에    64800000 byte  (61 mb) 

1년에    788400000 byte (751 mb)  


집안에 센서가 여러개면 더 늘어나겠지. 일단 센서1개 로 잡고  백업,인덱스등 부산물의 데이타용량은 제외 ,  데이터를 압축 등 적용안한다고 치고  대략적으로 1기가라고 잡자. 

한가구에 대략 1기가로 잡고 , 만약 전기서비스를 하는 회사를 만들었다고 치고, 빅데이터들을 관리 분석한다고 할때 , 1만가구쯤을 대상으로 서비스한다고 치면 1만기가 =  9 테라바이트 정도 된다.

요즘 테라단위의 HDD 들이 나오고 있다. 10TB 도 나온듯하다. 와우~  (http://it.donga.com/21465/)

프로토타입으로 저런 데이터를  postgresql 에 모아서 ,모여진 데이터를 실시간으로 d3.js 를 이용해서 가시화 하고 있다고 하자.



이제부터 본격적인 시스템을 설계한다고 치면, 언제까지 저런 데이터를 postgresql 에 담아두고 활용할거냐는건데.. 웬지 , 용량을 더 늘려야할거 같다.  스케일 업을 할수도있고, 스케일 아웃을 할수도있겠지..스케일 아웃에 대해서 고려해면다면  couchbase / hadoop hdfs /  mongodb 뭐 이런것들이 떠오른다. 분석을 생각해본다면 , Apache storm 이나 spark streaming 도 떠오른다. 

여기서 한발 더 나아가서 ,  저런 시계열 데이터를 특별히 다루는것들이 있는지도 살펴봐야겠는데..몇몇가지가 있더라~~  그래서 이번 게시물에서는 그 몇몇가지에 대해서 한번 살펴본다.

그 시계열 DB 들이 couchbase / hdfs / mongodb 와 경쟁을 하게될지, 협업을 하게될지..나는 아직 잘모른다. 그 시계열 DB 들이  RDBMS 들과 어떤 앙상블을 이루게될지 희미하다. 따라서 일단 정리해본다. -.-v    (일단 주워담고, 차근차근 정리해본다. 당분간 정신없을 게시물이될듯)



OpenTSDB (http://opentsdb.net/)







- OpenTSDB 는 타임시리즈데몬(TSD) 들로 구성된다. 
- 잘 알려진 에너지 회사인 OPower 에서도 사용한다.
- 각각의 TSD 들은 독립적이며 상태공유 없이 돌아간다. 
- 각각의 TSD 는 HBase 를 사용하며, 타임시리즈데이터를 가져와서 저장한다.
- HBase 스키마는 저장공간을 줄이기위해 비슷한 타임시리즈를 빠르게 집약하는것에 최적화.
- TSD 유저들은 HBase 에 직접적으로 접근할 필요가 없다. 



InfluxDB (https://influxdb.com/)


0) 2013년 첫 릴리즈 

1) InfluxDB는 data store를 위해 구글이 만든 key/value database library인 LevelDB를 사용하고 있습니다.따라서, 아래와 같은 LevelDB의 특징을 가지고 있습니다.(다음버전엔 RocksDB 로 이동예정)

–  기본적으로 데이타를 compression하기 때문에 읽기와 삭제에 다소 느릴 수 있습니다.
   (출처: http://obfuscurity.com/2013/11/My-Impressions-of-InfluxDB, https://code.google.com/p/leveldb/)

– 그러나, LevelDB와 다르게 SQL-like query language를 지원합니다. group by, join, 또 복수개의 time series(RDBMS에서 테이블이라고 이해하시면 됩니다.)를 merge하는 것도 가능합니다.

2) InfluxDB는 distributed and scale horizontally하게 설계되었습니다. 따라서, cluster에 새로운 node만 추가하면 쉽게 scale-out 할 수 있습니다. 

3) Continuous Queries를 지원합니다. 이게 뭐냐면, 정해준 시간마다 해당 query를 실행해서 그 결과 값을 지정하는 테이블의 특정 칼럼으로 저장하게 해줍니다. 보통 분석, 통계 데이타를 쌓을 때 스케줄러를 통해 많이 하는 작업(downsampling 같은 작업)인데요. InfuxDB는 이런 작업을 Continuous Query 한방으로 해결해줍니다. 

4) MongoDB 처럼 Schemaless design입니다.

5) Primary interface로 native HTTP API를 제공하고 Java, Javascript, Ruby, Rails, Python, PHP,          Node.js, Go 등 많은 library를 제공합니다.

6)  Go로 작성되었습니다.

7) InfluxDB는 오픈소스입니다. 소스를 다운로드 받아서 쓰실 수 도 있고 아니면, influxdb.org에서 매달 일정 비용을 내고 호스팅 서비스를 받으실 수 있습니다.

8) Linux & OS X

9) 스키마 프리 

10) HTTP API / JSON over UDP 

11) Numeric data and String 

12) Sharding 지원 /  Selelctable replication factor 

 InfluxDB 는 여러모로 시계열  DB 중에 가장 널리 선택받고 사용될듯 합니다.

(저도 전력빅데이터 분석을 위해서 InfluxDB + Grafana 조합을 사용중입니다) 


Graphite  (http://graphite.wikidot.com/)


Graphite 는 무엇인가?

Graphite 는 높은 확장성을 가진  실시간  그래픽 시스템이다. 당신이 관심있어하는 그래프를 그리기위한  수치 타임시리즈 데이터를 수집할수있는 어플리케이션을 만들수있으며. 그것을 Graphite 의 벡엔드로 보낼수도있다.  그 데이터들은 Carbon 이라 불리는 Graphite 의 특별한 데이터베이스에 저장될것이다. 그리고나서 Graphite 의 웹 인터페이스를 통해 가시화될수있을것이다. 

Graphite 는 수치 타임시리즈 데이터를 다루기위해 특별히 디자인되었으며, 따라서 주식을 나타내는데 도 좋다. 오직 수백건을 표현하기위해서라면 Graphite 는 지나치다. 수백개의 서버에서 나타내는 수많은 성능 지표들을 나타내기위해서 적절하다. 


어떻게 스케일 확장가능한가?

CPU 관점에서 Graphite 는 프런트엔드 와 백엔드 양쪽모두 수평적으로 확장할수있다. 이 말은 당신은 그저 더 많은 서버를 추가하기만 하면 된다는것이다. 그것은 벡엔드 서버가 고장났을때, 데이터 손실을 야기하는 상황에서 내고장성을 가지고있다는뜻이며, 만약 충분한 수용능력이 있을경우 시스템 운용에 지장받지 않는다는것이다. 

I/O 관점에서 봤을때, under load Graphite 는 매우 빠르게 많은 다른 파일들상에서 작은 I/O 오퍼레이션을 수행한다. 이것은 Graphite 로 보내는 각각의 distinct metric 들이 자신의 데이터베이스 파일에 저장되기 때문이다. 

Graphite 의 벡엔드는 만약 디스크가 크기가 작은 수많은 쓰기 연산을 다룰수 없을때 들어오는 데이터를 캐쉬한다.  (대부분의 디스크들은 초당 수천 I/O 연산을 할수없다.비록 각각의 데이타포인트가 오직 수 바이트일지라도. 추가: HDD의 경우 초당 랜덤 엑세스가 100~200회 밖에 되지 않는다. TPS가 1,000이 넘어가는 요청은 랜덤 엑세스로는 처리하기가 어렵다. MySQL InnoDB의 경우 Sequential write를 통해 초당 2,000회 정도의 Insert가 가능하다.) . 그런 현상이 발생했을때 Graphite 의 데이터베이스 엔진 (Whisper) 은  카본에 한번에 여러개의 데이터포인트를 쓰는것을 허용한다. 그리하여 전반적인 처리량을 증가시킨다. 



- 2006 년 첫 릴리즈 

- 최근 릴리즈는 0.9.10 (12년  5월 31일. 업데이트가 안되는듯?) 

- python 으로 만들어진 오픈소스이다. (python 관련 라이브러리를 잔뜩 설치해야한다) 

- webapp 은 Django 로 만들어졌으며 , ExtJS 를 사용한다. 그래프 랜더링은 Cairo 를 사용함.

- 주요모듈은  carbon / whisper / graphite-web 이다.

-  Linux / Unix  (설치가 비교적 복잡한 편)

-  데이터 스키마 있음  

- HTTP API / Sockets 

- Numeric data only

Sharding / replication factor  없음  

- Apache 2.0 License. (굉장히 자유스러운 라이센스) 

 Collector는 Graphite에 어떠한 데이터를 쌓기 위한 모듈입니다. 여기에 대한 특별한 제한은 없습니다만, 시계열 데이터베이스의 특성상 기본적으로 데이터가 저장될 네임스페이스와 시간, 데이터 이렇게 3가지 데이터가 필요합니다. 이러한 정보를 Graphite의 모듈인 Carbon-Cache에 보냅니다. Carbon-Cache는 Collector가 보낸 데이터를 받아 Whisper에 저장합니다. Carbon-Cache가 데이터 수집기라면 Whisper는 실제로 데이터를 파일시스템에 저장하고 읽어오는 모듈입니다. 자 이제 Whisper를 통해 데이터가 파일 시스템에 저장되었습니다. 그렇다면 이 데이터를 어떻게 가져올 수 있을까요. 이 시점에서 등장하는 게 Graphite-Web입니다. Graphite-Web은 http 프로토콜을 통해서 Whisper에 저장된 데이터를 읽어와 이미지 파일이나, 데이터 형식으로 출력합니다. Graphite-Web은 기본적으로 데이터를 제공하는 API와 대시보드 기능 두 가지를 제공하고 있습니다. 여기서 제공하는 대시보드 기능을 그냥 사용해도 무방합니다만, 기본적으로 그렇게 편리하지는 않습니다. 직접적인 Graphite 프로젝트는 아닙니다만, 이 Graphite-Web에서 대시보드를 제외하고 API 기능만을 따로 구현해둔 Graphite-api라는 모듈도 있습니다. 다른 대시보드를 사용한다면 Graphite-Web이나 Graphite-api 어느 툴을 사용해도 무방합니다.




Grafana  (http://grafana.org/)





Grafana 는 소스로 Graphite / Elasticsearch / InfluxDB 등을 이용하여  타임시리즈를 그래프 / 대쉬보드로 가시화해주는 툴입니다. 

- 일라스틱서치는 키바나라는걸 주로 사용하더군요. (ELK : 일라스틱서치, 로그스태시, 키바나) 






레퍼런스:

http://brad2014.tistory.com/334

http://blog.nacyot.com/articles/2014-07-17-graphite-with-dokcer/

https://josephkim75.wordpress.com/2014/03/12/influxdb-distributed-time-series-database/

'NoSQL' 카테고리의 다른 글

Neo4j - 인덱스 사용하기  (0) 2015.10.30
Neo4j 인사이드 : 파일 스토리지  (0) 2015.10.30
MongoDB vs Couchbase (2)  (0) 2015.09.03
MongoDB vs Couchbase (1)  (0) 2015.09.03
카우치베이스(Couchbase) - 아키텍쳐 구조  (0) 2015.05.06


MongoDB vs. Couchbase  (2)


 

Couchbase 의 문서는 JSON 인 반면  MongoDB 문서는 BSON 이다. 표기법은 많은 32비트 , 64비트 Integer 타입, 날짜타입, 바이트배열등을 포함한다. 양쪽은 모두 위치분석 데이타/쿼리를 지원하는데 카우치베이스는 아직 실험중이며 곧 완성될것이다. 2.4 새 몽고디비 버전에서는 풀 텍스트 검색이 포함되었으며 카우치베이스는 유사한 기능이 있는데 elasticsearch 플러그인이 필요하다.


카우치베이스와 몽고디비는 둘다 복제를 통한 데이타 안전을 제공하며, 둘다 클러스터링을 한다. 또한 둘다  샤딩을 통한 병렬접근을 제공한다. 카우치베이스와 몽고디비는 해쉬샤딩은 제공하는데 몽고디비는 범위샤딩 과 "태그" 샤딩도 제공한다. 이것은 양날의 칼인데 , 데이타베이스 관리에 유연함을 주는 동시에 오용은 클러스터에 밸런스를 깨지게하는 결과를 초래한다.


맵리듀스는 양쪽에 주요툴인데, 다른 목적으로 사용된다. 몽고디비에서는 일반적인 데이타 프로세싱, 정보집단화, 분석등을 위해 사용하며, 카우치베이스는 단지 데이타베이스를 쿼리하기위한 인덱스 생성용도로 사용한다. 그 결과 몽고디비가 인덱스 생성이나 애드혹 쿼리시엔느 좀 더 쉽긴하다.


카우치베이스는 메모리에 캐쉬하는것 (memcached) 이 장착되있는데,  몽고디비에는 대응되는것은 없다. Memcache 는  높은 처리량/데이타 집중 인터넷/ 인프라넷 어플리케이션 을 위해 오프젝트를 캐슁하는 시스템으로 강력하다. 만약 어플리케이션에서 Memcache 기능이 필요하다면 더 볼것도 없이 Couchbase 이다. 


두 시스템은  여러 유명 언어로 드라이버와 클라이언트 프레임워크를 제공하며 , 오픈소스이며 쉽게 인스톨할수있고 쉽게 온라인에서 문서를 찾을수있다. 


Couchbase Server vs MongoDB 

 

문서 조작 

Couchbase 는 2.0에서 원래 key/value 저장 아키텍처에서 문서형 특징을 추가 되었고  , MongoDB 는 원래 문서기반으로 디자인되었다. 몽고디비가  좀 더 개발되어졌다.

인덱싱 

Couchbase, 에서는 mapreduce 로 추가 인덱스를 만들어야하고 MongoDB, 에서는 개인적인 문서 필드에 인덱스를 추가할수있다. 몽고디비의 맵리듀스는 일반적인 프로세싱 용이다.

Memcached

Couchbase  는 Memcached 컴포넌트를 포함하며 , 문서저장용도와 독립적으로도 사용가능하며,  MongoDB 는 대응되는 부분이 없다.

샤딩 

Couchbase  는 hashed sharding 만 지원. MongoDB 는 hashed sharding 과  range sharding 지원.

Geospatial

MongoDB 는 geospatial 를 잘 지원하고 Couchbase 도 마찬가지인데 2.0 부터 추가되었고 발전중이다.


MongoDB vs. Couchbase  (1)



 


 NoSQL 분류중 문서 데이타베이스는  아마 가장 유명한 (많이 활용되는 )  할 것이다.  그것들의 엄청난유연성은 ( 스키마가 쉽게 변경되거나 늘어날수있는 ) 많은 어플리케이션에 적합하게 한다. 카우치베이스 서버는 비교적 최신임에도 불구하고  몽고디비와 함께  가장 유명한 오픈소스 문서형 디비가 되었다. 


이 게시글에서 말하는 "문서" 라는것은 워드프로세싱파일이나 PDF 를 말하지 않는다. 문서는 이름붙혀진 필드의 모음으로써  정의된 데이터 구조를 말한다. JSON(JavaScript Object Notation) 는 현재 문서형 디비에서 문서를 정의하기위해  가장 널리 쓰이는 표기법이다. JSON 의 객체 표기법으로서의 장점은 딱 봐서 이해하기 쉽다는것이고, 당신이 문서디비에서 많은 스키마를 정의하기위한  모든것을 나타낼수있을 것이다.  문서형 디비에서 각각의 문서는 자신만의 스키마를 가질수있다.  (해당 테이블에서 각각의 로우들이 동일한 컬럼들을 가져야하는  RDBMS 와는 다르게 ) 


카우치베이스 와 몽고디비의 최신 버전은 새롭게 나왔는데, 2012년 겨울 (역주: 옛날이네요 ㅎ)  카우치베이스는 2.0을 릴리즈했고 그 버전은 카우치베이스를 제대로된(full-fledged) 문서디비로 만들었다.  해당 릴리즈 전에  사용자는 JSON 데이터를 카우치베이스에 저장할수있었는데, 데이타베이스는 JSON 데이타를 blob 으로 썼다. 카우치베이스는 실제적으로 key/value 데이타베이스였다.


10gen 은 몽고디비 2.4 를 이번주에 릴리즈했고 몽고디비는 시작부터 문서형 디비였다. 최신버전은 성능이슈와 편의성 개선을 발전시켰다. 


두개의 디비는 모두 일반서버상에서 돌아가도록 디자인되었고, 샤딩을 경유해 수평적 확장을 할수있다. (카우치베이스는 샤드에 대해 러프한 동등성을 가지고있는데 파티션이라 불린다) . 두개의 디비는 문서정의 노테이션으로 JSON 을 사용하며 (몽고디비는 표시법으로 BSON (Binary JSON) ) 두 디비는 자바스크립트를 주요 데이타 관리 언어로 사용하며 데이타베이스를 조작하기위한 접근을 위해 많은 유명한 언어를 지원한다. 


주요 차이점  

물론 여러가지 차이점이 있다. 첫번째로 몽고디비의 도큐먼트 핸들링이 좀더 개발되어졌다. 이것은 몽고 쉘에서 명백히 드러나는데 , 몽고쉘은 관리와 개발윈도우를 제공한다.  데이타베이스, 컬렉션 그리고 문서들은 쉘에서 first-class 객체이고  컬렉션들은 사실 데이타베이스 객체상의 속성이다. 

위의 말이 카우치베이스가 좀 떨어진다는건 아니다. 당신은 쉽게 카우치베이스 클러스터를 관리할수있다. - 추가하고, 삭제하고, 문서를 패칭하고  카우치베이스 관리 GUI 를 통해서  -- 만약 당신이 GUI 컨솔을 이용하여 관리하는게 더 좋다면 카우치베이스에 준다.  뭐 당신의 삶이 까만 커맨드라인과 함께했다면 몽고디비쪽이겠지만~


클라우드 기반의 몽고디비 모니터링 서비스 (MMS) 는 모든것을 갖춘 디비관리인터페이스라고  말하는건 아니다.그러나 몽고디비환경은 데이타 오브젝트와 데이타베이스 엔터티 사이의 부드러운 연결을 해준다. 이것은 당신이 한번의 호출로 특정 문서필드상에서 인덱스를 만드는걸 발견할때 꽤나 분명해진다. 반면 카우치베이스의 인덱스는 반드시 복잡한 맵리듀스 오퍼레이션에 의해 만들어진다.


InfoWorld Scorecard
Installation (15.0%)
Ease of use (20.0%)
Management (20.0%)
Documentation (10.0%)
Value (10.0%)
Scalability (25.0%)
Overall Score (100%)
Couchbase Server 2.09.07.09.08.09.09.08.5
MongoDB 2.49.09.09.08.09.09.08.9



http://www.infoworld.com/article/2613970/nosql/nosql-showdown--mongodb-vs--couchbase.html  요약번역



카우치베이스의 아키텍쳐에서는 하둡 HDFS + MongoDB 의 냄새가 난다. 

Name 노드의 역할을 Cluster Manager

Data노드의 역할을 Data Manager 가 하고있다.

Cluster Manager 는 블럭 (카우치베이스에서는 버켓정보) 을 관리하고있고,

DataManager 는 메모리/물리계층의 데이터관리를 하고있다.

HDFS 처럼 클라이언트는 직접 데이터매니저로 접근해서 Query Engine 호출한다.


아래 글은 http://bcho.tistory.com/934  에서 가져온것으로 잘 정리되있다.







Couchbase Server

#6. Couchbase server 구조



 


소개


예전에 메모리 캐쉬 솔루션인 memcached에 디스크 persistence 기능을 추가하여 membase라는 솔루션이 있었는데, 이 제품에 Apache의 카우치디비(CouchDB)를 기반으로 새롭게 만든 솔루션이 카우치베이스 Server 라는 NoSQL 솔루션이다.카우치베이스는 mongoDB나, Riak과 같이 JSON document를 직접 저장할 수 있는 Document DB 형태를 가지며, NoSQL의 분산 이론인 CAP theorem에서 CP (Consistency & Partition tolerance) 의 부분에 해당하여 데이타에 대한 일관성과, 노드간의 네트워크 장애시에도 서비스를 제공할 수 있다. 근래에 들어서 600억원의 투자를 유치하는 등 가치를 인정 받고 있는데, mongoDB나 Cassandra에 가려서 그다지 주목을 받지 못하는 것 같아서, 이번 글을 통해서 소개하고자한다.


특장점

Memcached 기반의 Level 2 캐쉬를 내장하여 빠름

모바일 디바이스와 Sync

데이타 센터간 복제 가능

Indexing, Grouping ,Ordering,Join 가능

확장이 쉬움

Built in 관리 도구 제공

Memcached 프로토콜 지원


스키마가 없는 유연한 저장 구조 (Scheme-less)


이번에는 마지막으로 카우치베이스의 아키텍쳐에 대해서 알아보도록 하자

노드와 클러스터 (Node & Cluster)

노드는 물리적인 서버에서 기동하는 하나의 카우치베이스 인스턴스로, 카우치 베이스는 여러 개의 노드로 이루어진 클러스터로 구성된다


클라이언트 SDK (Client SDK)

프로그래밍 언어별로 카우치베이스에 접근하기 위한 API(SDK)를 제공한다.


vBucket 개념

카우치베이스는 실제데이타와 물리서버간의 맵핑을 vBucket이라는 것을 이용해서 관리한다. 카우치베이스는 키-밸류 스토어이다. 그래서, 각 키가 어디에 저장되어 있는지를 vBucket이라는 단위로 관리 하는데. 키에 대한 해쉬값을 계산한 후에, 각 해쉬값에 따라서 저장되는 vBucket을 맵핑한다음 각 vBucket을 노드에 맵핑한다.

아래는 서버 3대가 있었을 때, vBucket을 맵핑하는 구조에 대한 예제이다.



※ 출처 : http://docs.couchbase.com/couchbase-manual-2.5/cb-admin/#vbuckets

클라이언트 SDK는 이 vBucket와 노드에 대한 맵핑 정보를 클러스터로부터 받아서 관리한다. 즉 키에 대한 물리적인 서버 맵핑 정보를 클라이언트가 SDK를 통해 직접알 수 있기 때문에, 클라이언트가 PROXY등을 거치지 않고 직접 데이터가 저장된 노드로 접근이 가능하다.

CF. mongoDB의 경우 중간에 Proxy를 거쳐서 데이터가 저장된 물리 노드로 접근하게 된다.

만약에 노드가 추가되거나 삭제되었을 때, 물리적으로 데이터가 다른 노드로 다시 분산 배치되고, 새롭게 배치된 데이터에 따라서 vBucket to 노드간의 데이터 맵핑 정보도 업데이트 되는데, 이를 Rebalancing이라고 한다. (Rebalancing에 대한 내용은 뒤에 다시 설명)



http://docs.couchbase.com/couchbase-manual-2.5/images/vbuckets-after.png 


노드의 상세구조

그러면 각 노드는 어떤 형태로 구성이 될까? 아래는 노드의 대략적인 아키텍쳐이다.

카우치베이스의 노드는 아래 그림과 같이 크게 좌측의 Data Manager와 우측의 Cluster Manager로 나뉘어 진다.




Cluster Manager

Cluster Manager는 노드에 대한 상태와 클러스터에 대한 상태, 설정등을 관리하는 부분으로 Erlang/OTP 기반으로 구현되어 있다. 그 상위단에는 Admin Portal을 위한 Web UI가 8091 포트로 제공되고 있고, 같은 포트로 REST API가 함께 제공된다.

카우치베이스는 클라이언트 SDK는 이 8091 포트의 REST API를 통해서, 설정 정보와 앞서 설명한 vBucket 정보를 읽어온다. 여기에는 실제로 데이터에 대한 set/get이나 뷰 쿼리 수행용 포트정보도 포함이 되는데,  아래 Data Manager에서 제공되는 11211 포트나, 8092 포트가 사용된다..

그 외에도 클러스터 노드간의 통신을 위한 4389, 21100 포트등 다수의 포트가 사용되는데, 카우치베이스는 서버-클라이언트, 서버-서버간에 사용하는 포트들이 많기 때문에, 배포 전에 반드시 포트들을 확인하고 방화벽이나 네트워크 설정에 반영해야 한다.

※ http://docs.couchbase.com/couchbase-manual-2.5/cb-admin/#faqs 문서를 보면 배포시 오픈해야 하는 포트들이 설명되어 있다.


Data Manager

Data Manager 부분은 직접 데이터에 접근하는 부분으로 set/get 메서드를 이용하여 데이터를 저장하거나, 뷰에 대한 쿼리를 수행할 때 접근되는 인터페이스이다.

맨 아래단에는 멀티쓰레드 기반의 Persistence 엔진이 있으며, 디스크에 데이터를 저장하거나 읽어드릴때 사용되는 컴포넌트이다. 그 윗단에는 memcached가 있으며, 데이터를 캐슁하는데 사용된다. 또한 이 계층에서 뷰에 대한 쿼리 엔진이 제공된다.

Memcached 위에는 moxi 가 Proxy로 사용된다.


데이터 쓰기와 복제

클라이언트에서 데이터 쓰기가 발생했을 때, 카우치베이스는 어떻게 데이터를 저장할까?

먼저 클라이언트에서 Client SDK를 통해서 쓰기 요청을 하면, Client SDK는 해쉬 알고리즘에 따라데이터의 키 값에 맵핑 되는 vBucket을 찾아내고, 그 vBucket에 맵핑 되는 노드를 찾아서 쓰기 요청을 전달한다.

쓰기 요청은 해당 노드의 Listener로 전달되고, 이 Listener는 들어온 데이터를 로컬의 캐쉬에 쓰고 클러스터의 다른 노드로 복제 요청을 보낸다. 그리고 데이터는 노드의 디스크에 저장된다.




쓰기 과정중에 노드간의 복제가 발생한다.


노드별 메모리 레이아웃

그러면 각 노드별로 메모리 레이아웃은 어떻게 되어 있을까? 카우치베이스의 경우, memcached를 이용하는 만큼 서버의 메모리 공간 계산이 매우 중요하다. 앞서 글들에서도 설명하였지만, 메모리에 대해서 고려할 때, 카우치 베이스는 버킷의 키를 모두 메모리에 로딩해놓고 있다. 최소 메모리 공간은 전체키의 합보다는 최소한 커야 한다.그리고 각 도큐먼트당 60바이트의 메타 정보 저장공간이 필요하다. (키크기 + 60 바이트)*전체레코드수 / 노드수 * 3 (복제본수) 가 노드당 최소 메모리양이다. 최소 메모리란 말 그대로 최소한 돌릴 수 있는 수준을 이야기하는 것인데, 이 경우에는 캐쉬를 전혀 사용하지 못하기 때문에, 이 메모리 용량으로 서버를 운영하면 절대 안된다. (말그대로 아주 최~~소한이다.)


전체 하드웨어 공간에서 OS가 기본적으로 사용하는 용량을 제외 하면, 카우치베이스의 노드가 그 메모리 공간을 활용하는데, 카우치베이스에서는 노드에 할당된 메모리 공간을 버킷별로 다시 할당한다. (버킷을 생성할 때 설정할 수 있음)


리밸런스(Rebalance)

리밸런스 노드가 클러스터에 추가되거나, 장애등의 이유로 삭제되었을 때 데이터를 다시 노드에 분산 배치를 하는 작업이다. 노드간에 데이터 복제가 심하게 일어나기 때문에, 리밸런스는 부하가 적은 시간대에 하도록 권장하고 있다. (관리 콘솔을 보면 리밸런스를 멈추거나 시작할 수 있는 기능이 있다.) 향후에는 리밸런스를 Throttling 하는 기능이 나온다고 하니 기대해볼만하다.

NoSQL의 경우 특정 노드가 장애가 나서 시스템이 장애 나는 케이스보다 보통 노드를 추가/삭제할때 발생하는 이런 리밸런싱에 의해서 부하가 올라가거나 해서 장애가 나오는 케이스가 많기 때문에 특별히 주의를 기울일 필요가 있다.

 

XDCR

XDCR은 데이타 센터간에 카우치베이스 클러스터 데이타 복제를 지원하는 기능이다.

현재 최신 버전은 2.5 버전인데, 2.5 버전에서는 XDCR을 TLS/SSL을 이용해서 복제하기 때문에 자체적으로 보안을 지원한다. 그렇지만 무료 버전인 2.2 (Community Edition)의 경우 TLS/SSL 기반의 복제가 지원되지 않기 때문에, VPN 기반의 네트워크를 터널을 설정하고, VPN 터널을 통해서 XDCR 복제를 하도록 가이드 하고 있다.


Couchbase Gateway & CouchBase Mobile

카우치베이스의 흥미로운 점중의 하나는 모바일 디바이스에 탑재될 수 있는 Couchbase Lite버전을 제공한다는 것이다. iOS,안드로이드 버전을 제공하며, 또는 일반적인 애플리케이션에 사용할 수 있도록 자바버전과 .NET  버전도 제공된다. (모두 Community edition이 제공된다)

이 Couchbase Lite는 Couchbase 서버와 동기화가 가능하다.


다 못한 이야기

개발 관점에서는 GET/SET그리고 뷰 정도로 간편하지만, 운영과 설정에 대해서는 많아도 너무 많다. 대략적인 사용방법 아키텍쳐에 대해서 알아봤는데, 언급하지 못한 부분이 있어서 몇 가지만 언급하고자 한다.

카우치베이스는 다양한 커넥터를 이용하여 다른 솔루션과의 연동을 지원한다.

Elastic Search와 연동을 통하여 데이터에 대한 FTS (Full Text Search : 검색)을 지원할 수 있으며, Hadoop 연동을 통해서 Map & Reduce 기반의 데이터 처리가 가능하다. 또 오픈소스 ETL인 Talend 연동을 통해서 데이터를 다른 시스템으로 연동(복제)가 가능하며, 마지막으로, 데이터 암호화 솔루션인 Gazzang과 연동을 통해서 데이터를 암호화하여 저장할 수 있다.

http://www.couchbase.com/couchbase-server/connectors



'NoSQL' 카테고리의 다른 글

Neo4j - 인덱스 사용하기  (0) 2015.10.30
Neo4j 인사이드 : 파일 스토리지  (0) 2015.10.30
시계열 DB (OpenTSDB , 인플럭스 DB , Graphite ) 정리  (0) 2015.10.22
MongoDB vs Couchbase (2)  (0) 2015.09.03
MongoDB vs Couchbase (1)  (0) 2015.09.03

+ Recent posts