태그 보관물: Google Protocol Buffers

고성능 앱 서비스를 위한 아키텍처링..

아래 내용은 제가 테스트한 내용이 아니라, 경험을 기반으로 제가 생각하는 고성능의 서비스를 위한 아키텍처링에 대한 내용이고, 아래 내용은 앱<->API 서버 간의 성능향상을 위한 방안에 대한 내용이다.

요즘 상당히 많은 앱들이 네트웍으로 데이터를 전송받아서 랜더링하고, 유저가 만들어낸 데이터를 전송하기도 한다. 그래서, 네트웍으로 데이터를 주고 받는 앱들은 서버(보통 웹서버)와의 통신에서 JSON을 표준(많이 사용하는 추세) 프로토콜처럼 사용하고 있다. JSON을 사용하는 아키텍처가 가지고 있는 성능저하 요소를 살펴보면, 아래의 목록을 예상할 수 있습니다.

– JSON의 사용은 파싱에 드는 비용, 데이터 사이즈로 인한 전송(3G 대비)속도 저하가 예상이 된다.
– HTTP/HTTPS 프로토콜을 사용하는 서버(웹 서버)를 사용해서, 연결과 데이터 전송과 수신에 오버헤드(HTTP 프로토콜의 헤더만 읽어봐도 이해가 되실 듯)가 발생한다. 

그래서, 위의 성능저하 요소를 걷어내고 고성능의 앱을 개발하기 위한 방법으로, 아래의 방식을 고려할 필요가 있다. 아래의 형태로 개발하게 되면, 개발 비용이 증가하는 단점이 있다. 하지만, 네트웍을 많이 사용하는 앱(예로 카카오톡이나 채팅앱)이라면 연결기반의 프로토콜을 서버가 제공하고, 앱이 그 프로토콜을 사용한다면, 매번 연결하지 않고 네트웍으로 데이터를 보내고 받는 것이, 위의 비 연결지향의 프로토콜인 HTTP/HTTPS와는 비교할 수 없을 정도로 빠른것을 알게 될 것이다.

– JSON 보다는 고 성능의 데이터 직렬화 라이브러리(MessagePack, Thrift, Google Protocol Buffers)를 사용한다.
– 소켓(SSL 적용) 서버(Netty 프레임윅 등으로 개발)로 연결지향 프로토콜 기반에서 데이터를 처리한다. 이 방식의 경우에는 소켓 서버에서 클라이언트의 idle time을 적절하게 계산해서 주기적으로 클라이언트의 소켓을 제거해 주는 로직이 필요합니다.. 이 로직이 없을 경우, file descriptor가 부족해 지겠죠.. ^^

당장은, 필요하다고 느끼지 못하거나, 너무 오버한다고 생각할 수 있으나, 기본적으로 유저가 많은 포털의 앱이나, 이미 많은 유저가 사용하는 앱들은 프로토콜과 데이터 형식만 바꿔도 상당한 성능향상을 가져올 수 있을 것이라 생각한다. ^^

Cassandra.Client 풀링하기..

Cassandra.Client는 org.apache.cassandra.thrift 패키지의 클래스이고, 결국, Client는 Cassandra의 Inner 클래스가 됩니다..그리고, 위 패키지는 Thrift라는 데이타 serialize/deserialize 라이브러리(Google Protocol Buffers랑 비슷)를 통해서 전송될 데이타를 만들고, 전송된 데이타를 처리하고 있겠죵.. Cassandra의 언어별 클라이언트 라이브러리는 http://wiki.apache.org/cassandra/ClientOptions 페이지에 자세히 기술이 되어 있습니다.. 클라이언트 라이브러리들은 보통 Cassandra 서버에 붙는 Connection에 대한 풀링을 제공하는데, Connection 풀링은 org.apache.cassandra.thrift 패키지의 Cassandra.Client를 풀링해서 구현 할 수 있습니다. 
아래 코드는 Cassandra.Client의 풀링을 통한 Cassandra 서버의 연결에 대한 풀링기능을 제공하고 있습니다..^^

* CassandraClientArrayFactory.java



package net.sjava.cassandra.test;


import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue;


import org.apache.cassandra.thrift.Cassandra;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;


/**
 * Cassandra.Client 풀링 클래스
 *
 * @author mcsong@gmail.com
 * @since 2010/11/30
 *
 */
public class CassandraClientArrayFactory {
 /**
  * host array
  */
 private static String[] hosts = “127.0.0.1,127.0.0.1”.split(“,”) ;
 /**
  * port
  */
 private static int port = 9160;
 /**
  * pooing count
  */
 private static int count = 10;
 
 //
 private static ConcurrentLinkedQueue<Cassandra.Client> clients = new  ConcurrentLinkedQueue<Cassandra.Client>();
 
 /**
  * Cassandra.Client를 생성한다.
  * @return
  * @throws Exception
  */
 private static Cassandra.Client createCassandraClient() throws Exception {
  int index = new Random().nextInt(hosts.length);
  TTransport socket = new TSocket(hosts[index], port);
  socket.open();
  return new Cassandra.Client(new TBinaryProtocol(socket, false, false));
 }


 /**
  * Cassandra.Client를 풀에서 가져온다.
  * @return
  * @throws Exception
  */
 public static Cassandra.Client poll() throws Exception {
  if (!clients.isEmpty() && clients.peek() != null)
   return clients.poll();
  
  return createCassandraClient();
 }
 
 /**
  * Cassandra.Client를 풀에 넣는다.
  * @param client
  * @throws Exception
  */
 public static void push(Cassandra.Client client) throws Exception {
  System.out.println(“channel size : ” + clients.size());
  if (clients.size() >= count) {
   client = null; // garbage
   return;
  }
  
  clients.offer(client);
 }
}




* CassandraClientArrayFactoryTest.java 



package net.sjava.cassandra.test;


import org.apache.cassandra.thrift.Cassandra;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnPath;
import org.apache.cassandra.thrift.ConsistencyLevel;


import static net.sjava.cassandra.test.CassandraClientArrayFactory.poll;
import static net.sjava.cassandra.test.CassandraClientArrayFactory.push;


public class CassandraClientArrayFactoryTest {


 public static void main(String[] args) {
  
  String keyspace = “Keyspace1”;
  String columnFamily = “Standard2”;
  String key = “key1”;
  long timestamp = System.currentTimeMillis();
  
  Cassandra.Client client = null;
  
  try {
   client = poll();   
   for(int i=0; i < 15; i++)
    push(client);
     
   String value =”bbbbbbbbbbbbbbbbbbbbbbbbbbbbb”;
   ColumnPath cPath = new ColumnPath(columnFamily);
   cPath.setColumn(“name”.getBytes(“utf-8”));
   client.insert(keyspace, key, cPath, value.getBytes(“utf-8”), timestamp, ConsistencyLevel.ONE);
   Column col = client.get(keyspace, key, cPath, ConsistencyLevel.ONE).getColumn();
   System.out.println(“Column name: ” + new String(col.name, “utf-8”)); 
   System.out.println(“Column value: ” + new String(col.value, “utf-8”)); 
  
   value =”ccccccccccccccccccccccccccccccccccccccc”;
   long time2 = System.currentTimeMillis();
   client.insert(keyspace, key, cPath, value.getBytes(“utf-8”), time2, ConsistencyLevel.ONE);
   Column col2 = client.get(keyspace, key, cPath, ConsistencyLevel.ONE).getColumn();
   System.out.println(“Column2 name: ” + new String(col2.name, “utf-8”)); 
   System.out.println(“Column2 value: ” + new String(col2.value, “utf-8”)); 
  } catch(Exception e) {
   e.printStackTrace();
  } finally {
   if(client != null)
    try {
     push(client);
    } catch (Exception e) {
     e.printStackTrace();
    }
  }
 }


}