성능 테스트 메뉴얼 - SeungpilPark/uEngine-bill GitHub Wiki
mysql 에서 다음의 에러가 남으로...
java.sql.SQLSyntaxErrorException: Row size too large (> 8126). Changing some columns to TEXT or BLOB may help
mysql 디렉토리의 my.cnf 파일을 다음으로 수정한다.
[mysqld]
.
.
max_allowed_packet=500M
innodb_file_per_table
innodb_file_format = Barracuda
innodb_log_file_size = 256M
이후 mysql 의 tenant_kv 테이블을 업데이트한다.
ALTER TABLE tenant_kvs ENGINE=InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
이후 http://localhost:8080/1.0/kb/catalog 엔드포인트로 POST 전송을 한다.
전문을 보낼때의 헤더규칙.
String auth = this.user + ":" + this.password;
BASE64Encoder encoder = new BASE64Encoder();
String encode = encoder.encode(auth.getBytes());
Map mergeHeaders = new HashMap();
mergeHeaders.put("Authorization", "Basic " + encode);
mergeHeaders.put("Content-Type", "application/xml");
mergeHeaders.put("Accept", "application/json");
mergeHeaders.put("X-Killbill-CreatedBy", "OpenBill");
mergeHeaders.put("X-Killbill-ApiKey", this.apiKey);
mergeHeaders.put("X-Killbill-ApiSecret", this.apiSecret);
대용량 카달로그 업데이트시 소요시간
상품 수 | 상품 당 플랜 수 | 총 플랜 수 | 용량(KB) | 카달로그 생성 소요 시간(s) | 카달로그 전송과 서버처리 시간(s) | WAS 환경 |
---|---|---|---|---|---|---|
10000 | 1 | 10000 | 5734 | 4 | 12 | Xmx 2G |
20000 | 1 | 20000 | 10686 | 9 | 34 | Xmx 2G |
30000 | 1 | 30000 | 15637 | 18 | 51 | Xmx 2G |
40000 | 1 | 40000 | 20588 | 42 | 172 | Xmx 2G |
50000 | 1 | 50000 | 25539 | 79 | 369 | Xmx 2G |
150000 | 1 | 150000 | 86714 | 298 | 1182 | Xmx 2G |
데일리마다 한번씩 대용량 카달로그가 업데이트 된다는 가정하에, 카달로그 버젼을 업데이트 해가며 서브스크립션 차지 계산 및 인보이스 생성 능력을 테스트함.
카달로그 버젼 수 | 전체 플랜 수 | 전체 서브스크립션 수 | 인보이스 당 서브스크립션 수 | 인보이스 처리 속도(분당) | 모니터링 시간(분) |
---|---|---|---|---|---|
2 | 20000 | 10000 | 2 | 201 | 5 |
4 | 20000 | 10000 | 2 | 212 | 5 |
6 | 20000 | 10000 | 2 | 서버 정지 | 5 |
카달로그가 6개째가 되면서 2G 를 가지고 있는 was 가 힙메모리 부족이 뜨면서 죽음.
에러 원인은 서브스크립션 계산을 위해 카달로그 캐쉬를 사용하는 것과는 별개로, 예전 버전의 가격변동 여부를 확인하기 위해 데이터베이스에서 카달로그를 가져오는 과정에 힙메모리가 걸림.
Caused by: java.lang.OutOfMemoryError: Java heap space
at java.lang.StringCoding$StringDecoder.decode(StringCoding.java:149)
at java.lang.StringCoding.decode(StringCoding.java:193)
at java.lang.String.<init>(String.java:426)
at java.lang.String.<init>(String.java:491)
at org.mariadb.jdbc.internal.common.AbstractValueObject.getString(AbstractValueObject.java:82)
at org.mariadb.jdbc.internal.mysql.MySQLValueObject.getString(MySQLValueObject.java:78)
at org.mariadb.jdbc.MySQLResultSet.getString(MySQLResultSet.java:131)
at com.zaxxer.hikari.proxy.ResultSetJavassistProxy.getString(ResultSetJavassistProxy.java)
at org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapper.map(LowerToCamelBeanMapper.java:126)
at org.skife.jdbi.v2.RegisteredMapper.map(RegisteredMapper.java:37)
at org.skife.jdbi.v2.Query$4.munge(Query.java:183)
at org.skife.jdbi.v2.QueryResultSetMunger.munge(QueryResultSetMunger.java:43)
at org.skife.jdbi.v2.SQLStatement.internalExecute(SQLStatement.java:1340)
at org.skife.jdbi.v2.Query.fold(Query.java:173)
at org.skife.jdbi.v2.Query.list(Query.java:82)
at org.skife.jdbi.v2.sqlobject.ResultReturnThing$IterableReturningThing.result(ResultReturnThing.java:255)
at org.skife.jdbi.v2.sqlobject.ResultReturnThing.map(ResultReturnThing.java:48)
at org.skife.jdbi.v2.sqlobject.QueryHandler.invoke(QueryHandler.java:45)
at org.skife.jdbi.v2.sqlobject.SqlObject.invoke(SqlObject.java:175)
at org.skife.jdbi.v2.sqlobject.SqlObject$2.intercept(SqlObject.java:92)
at org.skife.jdbi.v2.sqlobject.CloseInternalDoNotUseThisClass$$EnhancerByCGLIB$$ad218284.getTenantValueForKey(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperInvocationHandler$2.execute(EntitySqlDaoWrapperInvocationHandler.java:207)
at org.killbill.commons.profiling.Profiling.executeWithProfiling(Profiling.java:33)
at org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperInvocationHandler.invokeRaw(EntitySqlDaoWrapperInvocationHandler.java:204)
at org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperInvocationHandler.invokeSafely(EntitySqlDaoWrapperInvocationHandler.java:199)
at org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperInvocationHandler.access$000(EntitySqlDaoWrapperInvocationHandler.java:82)
at org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperInvocationHandler$1.execute(EntitySqlDaoWrapperInvocationHandler.java:120)
at org.killbill.commons.profiling.Profiling.executeWithProfiling(Profiling.java:33)
이를 통하여 하나의 xml 카달로그에 모든 제품을 담는 방식은 회피하고, WorkAround 코드를 작성하여 운용해야 한다는 것을 알 수 있다.
사용자 생성 api 호출을 다음과 같이 돌렸을 때 성능 측정.
환경은 모두 local. 쓰레드가 100 이상일때 데이터 유실의 위험이 있으므로 쓰레드 제한을 걸어 운용해야 함을 알 수 있다.
쓰레드 | 반복횟수 | 총 횟수 | 완료된 데이터 건 | 소요시간(s) | 호출당 소요시간(ms) | 실패 사유 | WAS 환경 |
---|---|---|---|---|---|---|---|
1 | 10000 | 10000 | 10000(100%) | 4121 | 412.1 | Xmx 2G | |
10 | 1000 | 10000 | 10000(100%) | 943 | 94.3 | Xmx 2G | |
20 | 500 | 10000 | 10000(100%) | 607 | 60.7 | Xmx 2G | |
50 | 200 | 10000 | 10000(100%) | 524 | 52.4 | Xmx 2G | |
100 | 100 | 10000 | 9941(99.4%) | 498 | 49.8 | DB 인서트 실패 | Xmx 2G |
테스트 코드
public static void main(String[] args) throws Exception {
LogOff.logOff();
//포시에스 테넌트에 유저(구매자) 1만명 등록
//100명씩 100개 쓰레드로 접속.
int userNumbers = 100;
int thredsNumbers = 100;
for (int i = 0; i < thredsNumbers; i++) {
int startCount = i * userNumbers + 1;
int endCount = startCount + userNumbers;
CreateAcount test = new CreateAcount(startCount, endCount);
test.start();
}
}
public static class CreateAcount extends Thread {
int startCount;
int endCount;
public CreateAcount(int startCount, int endCount) {
this.startCount = startCount;
this.endCount = endCount;
}
public void run() {
String user = "admin";
String password = "password";
String apiKey = "forcs";
String apiSecret = "forcs";
String host = "localhost";
int port = 8080;
KillbillApi api = new KillbillApi(host, port, user, password, apiKey, apiSecret);
for (int c = startCount; c < endCount; c++) {
String name = "test-user-" + c;
String email = name + "@gmail.com";
String currency = "USD";
String externalKey = name;
int billCycleDayLocal = 5;
api.createAccount(name, email, currency, billCycleDayLocal, externalKey);
}
}
}