Cassandra disk 튜닝과 관련해서 몇가지 메모들


https://www.ibm.com/docs/en/cloud-app-management/2019.4.0?topic=performance-optimizing-disk-cassandra

 

6년? 8년? 전에 tokumx mongoDB 부터 사용했었는데,  mongodb java driver 소스를 몇 번 봤었습니다.

(그 당시에는 지금처럼 mongodb가 안정화? 되지 않은 부분도 많았습니다. db커넥션 유지관련 버그 등..)

 

mongodb java driver 소스 중에 인상 깊고, 이후  다른 시스템들의 pk설계할때 많이 참고한 부분이  objectId  부분입니다.(인스타그램의 id체계 참고와 함께)

 

지금도 가끔 필요한 경우 소스를 다시 보는데, 메모 목적으로 내용을 복사해둡니다.(기억력이 예전같지 않아요 ...;)

 

추가 참고 글들

1. 리소스의 ID, GUID로 할까 Sequential로 할까?(https://news.hada.io/topic?id=6038)
2. ulid git(https://github.com/ulid/spec)

 


참고 링크:  https://www.mongodb.com/docs/manual/reference/bson-types/

 

ObjectIds are small, likely unique, fast to generate, and ordered. ObjectId values are 12 bytes in length, consisting of:

  • A 4-byte timestamp, representing the ObjectId's creation, measured in seconds since the Unix epoch.
  • A 5-byte random value generated once per process. This random value is unique to the machine and process.
  • A 3-byte incrementing counter, initialized to a random value.

For timestamp and counter values, the most significant bytes appear first in the byte sequence (big-endian). This is unlike other BSON values, where the least significant bytes appear first (little-endian).

If an integer value is used to create an ObjectId, the integer replaces the timestamp.

In MongoDB, each document stored in a collection requires a unique _id field that acts as a primary key. If an inserted document omits the _id field, the MongoDB driver automatically generates an ObjectId for the _id field.

몇 달전에 redis client를 어떻게 사용하는게 더 잘 사용하는지에 대해서 여러가지 검토 및 테스트를 진행했었습니다.

시간이 지나서 많이 잊어버려서 몇가지만이라도 메모 해둡니다.

 

1. https://lettuce.io/core/release/reference/index.html#_pipelining_and_command_flushing 에 정리가 잘되어 있었습니다. (AWS 문서는 퀄리티가 높음)

 

2. lettuce를 사용했을때 Pipe 커맨드 내용

 - https://lettuce.io/core/release/reference/index.html#_pipelining_and_command_flushing

 

3. I/O 멀티플렉싱 향상된 redis 버전을 사용

 - 예) ElastiCache for Redis 버전 7.0(향상된 버전)

     https://aws.amazon.com/ko/blogs/database/enhanced-io-multiplexing-for-amazon-elasticache-for-redis/

AWS에서 Redis(Elastic cache)사용시 모니터링 등에 사용하는 커맨드
 - 예) 리얼 환경에서 사용하면 안되는 커맨드가 가끔 개발자 실수로 반영될 수 있음(예. keys)

-- redis에 keys 커맨드가 실행되고 있는지 모니터링하는 커맨드
 redis-cli -h 도메인혹은IP monitor | grep keys
  - 예) redis-cli -h 블라블라.apn2.cache.amazonaws.com monitor


-- redis에 접속되어있는 유니크한 클라이언 IP 리스트 확인 커맨드
redis-cli -h redis서버호스트 CLIENT LIST | awk '{print $2}' | sed s/:.*//g | sort -u

 


AWS ec2에 redis-cli설치하는 방법

# make 하기 위해서 gcc 다운
sudo yum install -y gcc

# redis-cli 설치 및 make
wget http://download.redis.io/redis-stable.tar.gz && tar xvzf redis-stable.tar.gz && cd redis-stable && make

#src/redis-cli 에서 사용 가능
예) ./redis-cli -h 'redis 호스트 도메인' -p '포트번호'

 

2020년경에 tokumx-> mongo 3.4 -> mongo 4.2로 DB서버 버전업시 어플리케이션 수정을 위해 정리했던 내용입니다.

 - java드라이버의 호환성내용입니다.

 - API는 4.0.4버전을 사용했고, admin은 하위버전의 DB까지 지원해야해서 3.4.3버전을 사용했습니다.

 


당시 조건을 간단히 정리하자면 아래와 같습니다.

  1. 분당 1만건/100Mb 이상의 좀 큰 사이즈의 트래픽이 저장요청 API 어플리케이션으로 몽고DB에 저장됨
  2. mongoDB는 10여개 이상의 셋트(replica형태로)로 aws ec2에 설치해서 사용 중
    1. mongoDB가 자체 개선되기전부터 특정 기능(tokumx의 압축, 파티션 등)을 사용했어야해서 tokumx, mongo 3.4가 혼재되어 있었음
    2. 여러 이슈로 mongo 4.2로 버전업 필요한 상황
  3. 저장 API는 1개의 소스-> 1개의 DB만 커넥션
  4. admin시스템은 1개의 소스->N개의 DB커넥션

 

참고

 - https://docs.mongodb.com/drivers/java/sync/current/compatibility/

 

구)

메모

 

어플리케이션을 개발하다보면 분산락 처리를 신경써야하는 과제들이 간혹 있는데 저는 보통 2가지를 사용합니다.

 

  1. mysql의 named lock 사용
    1. 보통 어플리케이션이 RDB는 사용중인 경우가 많음
    2. 트래픽이 아주 많아서 성능이 중요하지 않음
    3. 개인적으로 티어가 복잡해지지 않아서 선호
    4. 참고
      1. mysql lockking 링크 
  2. redis이용
    1. redis 가 추가로 필요
      1. 개인적으로는 atomic을 보장하는 setnxsetex 를 활용하여 심플하게 개발하기도 함
        1. 스핀락 형태라 주의할점이 있음(ex. ttl 없고, redis 부하 증가 등). 간단히 예상되는 트래픽에 많이 씀
    2. 참고
      1. 참고 글  링크
      2. redis redlock best practices 링크
  3. 기타
    1. 아주 예전에는 zookeeper를 사용하기도 했는데 번거로움

참고

 - https://aws.amazon.com/ko/blogs/database/optimize-redis-client-performance-for-amazon-elasticache/

 - https://aws.amazon.com/ko/blogs/database/best-practices-redis-clients-and-amazon-elasticache-for-redis/

 

 - https://github.com/lettuce-io/lettuce-core/wiki/Connection-Pooling

 - https://lettuce.io/core/release/reference/#connection-pooling.is-connection-pooling-necessary

  1. 필요 라이브러리
    1. maven dependency 추가
<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>5.3.3.RELEASE</version>
</dependency>


<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.8.1</version>
</dependency>

 

2. 샘플 소스 - java

 - async command 등 참고 링크

import io.lettuce.core.ClientOptions;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisConnectionException;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.support.ConnectionPoolSupport;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

/**
 * redis 커넥션풀 기반 연결 샘플
 *
 * @author 
 */
public class ConnectionSampleWithPool {

	private static final String REDIS_CON_URL = "redis://192.168.56.1:6379/0"; //로컬 redis 0번 사용
	public static GenericObjectPool<StatefulRedisConnection<String, String>> pool = null; //sync & 비 클러스터모드 커넥션풀

	public static void main(String[] args) throws InterruptedException {

		pool = nonClusterPoolUsage();
		//pool = useClusterPoolUsage(); //클러스터모드일때

		testSetValue("key-pool-test-1", "key-pool-test-1");
		testSetValue("key-pool-test-2", "key-pool-test-2");
		testSetValue("key-pool-test-3", "key-pool-test-3");

		Thread.sleep(10 * 1000); //커넥션풀 close 전에 대기 후, redis-cli(서버)에서 client list 명령어로 현재 연결된 클라이언트 리스트를 확인해보면 됨

		pool.close();
		System.out.println("finish");
	}

	/**
	 * set value 테스트
	 *
	 * @param key
	 * @param value
	 */
	private static void testSetValue(String key, String value) {

		try (StatefulRedisConnection<String, String> connection = pool.borrowObject()) { //pool을 이용해서 커맨드 실행
			connection.sync().set(key, value);
			value = connection.sync().get(key);
			System.out.println(value);
		} catch (RedisConnectionException e) {
			System.out.println(String.format("Failed to connect to Redis server: %s", e));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 비 클러스터 모드일때 커넥션풀 셋팅
	 *
	 * @return
	 */
	private static GenericObjectPool<StatefulRedisConnection<String, String>> nonClusterPoolUsage() {

		RedisClient client = RedisClient.create(REDIS_CON_URL);
		client.setOptions(ClientOptions.builder().autoReconnect(true).build());

		return ConnectionPoolSupport.createGenericObjectPool(() -> client.connect(), createPoolConfig());
	}

	/**
	 * 클러스터모드일때 커넥션 풀 셋팅
	 *  - 참고: https://github.com/Azure/azure-redis-cache-samples/blob/master/Java/ClientSamples/src/main/java/lettuce/PoolUsage.java
	 *
	 * @return
	 */
	private static GenericObjectPool<StatefulRedisClusterConnection<String, String>> useClusterPoolUsage() {

		RedisClusterClient clusterClient = RedisClusterClient.create(REDIS_CON_URL);
		clusterClient.setOptions(ClusterClientOptions.builder().autoReconnect(true).build());

		return ConnectionPoolSupport.createGenericObjectPool(() -> clusterClient.connect(), createPoolConfig());
	}

	/**
	 * 커넥션풀 설정 생성
	 *
	 * @return
	 */
	private static GenericObjectPoolConfig createPoolConfig() {

		GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();

		poolConfig.setMaxTotal(20);
		poolConfig.setMaxIdle(10);

		// "true" will result better behavior when unexpected load hits in production
		// "false" makes it easier to debug when your maxTotal/minIdle/etc settings need adjusting.
		poolConfig.setBlockWhenExhausted(true);
		poolConfig.setMaxWaitMillis(1000);
		poolConfig.setMinIdle(5);

		return poolConfig;
	}

}
 
 
 
  1. 인덱스 없을시 explain 결과
repl_set:PRIMARY> db.test_multikey_index.getIndexes()
[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_",
                "ns" : "test_db.test_multikey_index"
        }
]
 
 
repl_set:PRIMARY> db.test_multikey_index.find({topic:'server-3'}).explain()
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "test_db.test_multikey_index",
                "indexFilterSet" : false,
                "parsedQuery" : {
                        "topic" : {
                                "$eq" : "server-3"
                        }
                },
                "queryHash" : "28760B72",
                "planCacheKey" : "28760B72",
                "winningPlan" : {
                        "stage" : "COLLSCAN",
                        "filter" : {
                                "topic" : {
                                        "$eq" : "server-3"
                                }
                        },
                        "direction" : "forward"
                },
                "rejectedPlans" : [ ]
        },
        "serverInfo" : {
                "host" : "localhost.localdomain",
                "port" : 27017,
                "version" : "4.2.8",
                "gitVersion" : "43d25964249164d76d5e04dd6cf38f6111e21f5f"
        },
        "ok" : 1,
        "$clusterTime" : {
                "clusterTime" : Timestamp(1596622293, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        },
        "operationTime" : Timestamp(1596622293, 1)
}
 
 
 
 
 
 
  1. 인덱스 존재시 explain결과
repl_set:PRIMARY> db.test_multikey_index.getIndexes()
[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_",
                "ns" : "test_db.test_multikey_index"
        },
        {
                "v" : 2,
                "key" : {
                        "topic" : 1
                },
                "name" : "topic_1",
                "ns" : "test_db.test_multikey_index"
        }
]
 
 
 
repl_set:PRIMARY> db.test_multikey_index.find({topic:'server-3'}).explain()
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "test_db.test_multikey_index",
                "indexFilterSet" : false,
                "parsedQuery" : {
                        "topic" : {
                                "$eq" : "server-3"
                        }
                },
                "queryHash" : "28760B72",
                "planCacheKey" : "9C0A2855",
                "winningPlan" : {
                        "stage" : "FETCH",
                        "inputStage" : {
                                "stage" : "IXSCAN",
                                "keyPattern" : {
                                        "topic" : 1
                                },
                                "indexName" : "topic_1",
                                "isMultiKey" : true,
                                "multiKeyPaths" : {
                                        "topic" : [
                                                "topic"
                                        ]
                                },
                                "isUnique" : false,
                                "isSparse" : false,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "topic" : [
                                                "[\"server-3\", \"server-3\"]"
                                        ]
                                }
                        }
                },
                "rejectedPlans" : [ ]
        },
        "serverInfo" : {
                "host" : "localhost.localdomain",
                "port" : 27017,
                "version" : "4.2.8",
                "gitVersion" : "43d25964249164d76d5e04dd6cf38f6111e21f5f"
        },
        "ok" : 1,
        "$clusterTime" : {
                "clusterTime" : Timestamp(1596622053, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        },
        "operationTime" : Timestamp(1596622053, 1)
}
 
 
 
  1. 참고 - 테스트용 프로그램
package com.biz.mongodb;
 
 
import com.mongodb.BasicDBObject;
import com.mongodb.Block;
import com.mongodb.MongoClientSettings;
import com.mongodb.ServerAddress;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import com.mongodb.connection.ClusterSettings;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
 
 
import java.util.*;
 
 
/**
* 멀티키 Index 테스트
*
* @author 엄승하
*/
@Slf4j
public class FindMultiKeyIndexSample {
 
 
   private static final String dbAddr = "192.168.56.1"; //로컬 vm에 mongodb 4.2를 설치함
   private static final String dbName = "test_db"; //db명
 
 
   private static String colNm = "test_multikey_index";
   private static String indexField = "topic"; //인덱스 필드
 
 
   private static final int testDataSize = 100_000; //테스트 데이터 생성 갯수
 
 
   private static MongoDatabase mongoDB;
 
 
   public static void main(String[] args) {
 
 
      List<ServerAddress> serverList = Arrays.asList(new ServerAddress(dbAddr, 27017));
      Block<ClusterSettings.Builder> clusterSettings = builder -> builder.hosts(serverList);
 
 
      MongoClient mongoClient = MongoClients.create(MongoClientSettings.builder().applyToClusterSettings(clusterSettings).build());
      mongoDB = mongoClient.getDatabase(dbName);
 
 
      //컬렉션 추가
      mongoDB.createCollection(colNm);
      mongoDB.getCollection(colNm).createIndex(new BasicDBObject(indexField, 1)); //인덱스 추가
 
 
      saveBulkData(colNm, testDataSize); //테스트 데이터 저장
 
 
      mongoClient.close(); //DB커넥션 반환
 
 
      System.out.println("\n\n === 프로그램 종료 ===");
   }
 
 
   /**
    * bulk로 데이터 저장
    *
    * @param colNm 저장할 컬렉션명
    * @param dataSize 저장할 데이터 갯수
    */
   public static void saveBulkData(String colNm, int dataSize) {
 
 
      Date now = new Date();
      List<Document> docs = new ArrayList<>(dataSize);
 
 
      List<String> case1 = new ArrayList<>();
      case1.add("server-1");
      case1.add("server-2");
 
 
      List<String> case2 = new ArrayList<>();
      case2.add("server-2");
      case2.add("server-3");
 
 
      for (int i = 1; i <= dataSize; i++) {
 
 
         Document doc = new Document();
         doc.append("i", i);
         doc.append("uuid", UUID.randomUUID().toString());
         if (i % 2 == 0) { // 2가지의 경우로 데이터 저장
            doc.append("topic", case1);
         } else {
            doc.append("topic", case2);
         }
 
 
         doc.append("doc_make_date", now);
         docs.add(doc);
      }
 
 
      mongoDB.getCollection(colNm).insertMany(docs); //N개 insert
 
 
      //Thread.sleep(10); //부하분산 목적
   }
}
 

+ Recent posts