Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

week5(지예): 5주차 학습 #46

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,16 @@ dependencies {
annotationProcessor 'org.projectlombok:lombok'

// h2
runtimeOnly 'com.h2database:h2'
// runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'

// mysql
runtimeOnly 'mysql:mysql-connector-java:8.0.33'

implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'com.github.ben-manes.caffeine:caffeine'
implementation 'net.sf.ehcache:ehcache'
// implementation 'org.ehcache:ehcache:3.10.8'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,73 @@
package com.order.perf.config;

import com.github.benmanes.caffeine.cache.Caffeine;
import net.sf.ehcache.Cache;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.DiskStoreConfiguration;
import net.sf.ehcache.config.PersistenceConfiguration;
//import org.ehcache.CacheManager;
//import org.ehcache.config.builders.CacheConfigurationBuilder;
//import org.ehcache.config.builders.CacheManagerBuilder;
//import org.ehcache.config.builders.ExpiryPolicyBuilder;
//import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.jcache.JCacheCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

@Configuration
@EnableCaching
public class CacheConfig {
// @Bean
// public CaffeineCacheManager cacheManager() {
// CaffeineCacheManager cacheManager = new CaffeineCacheManager();
// cacheManager.setCaffeine(Caffeine.newBuilder()
// .initialCapacity(100) // 초기 용량
// .maximumSize(500) // 최대 캐시 크기
// .expireAfterWrite(10, TimeUnit.MINUTES) // 캐시 만료 시간
// .recordStats()); // 통계 활성화
// return cacheManager;
// }

// @Bean
// public CacheManager ehCacheCacheManager() {
// org.ehcache.config.CacheConfiguration<Long, Object> cacheConfiguration =
// CacheConfigurationBuilder.newCacheConfigurationBuilder(
// Long.class, Object.class,
// ResourcePoolsBuilder.heap(500))
// .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofMinutes(10)))
// .build();
//
// org.ehcache.CacheManager ehcacheManager = CacheManagerBuilder.newCacheManagerBuilder()
// .withCache("orders", cacheConfiguration)
// .build(true);
//
// return ehcacheManager;
// }
@Bean
public CaffeineCacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(100) // 초기 용량
.maximumSize(500) // 최대 캐시 크기
.expireAfterWrite(1, TimeUnit.HOURS) // 캐시 만료 시간
.recordStats()); // 통계 활성화
return cacheManager;
public EhCacheCacheManager ehCacheCacheManager() {
net.sf.ehcache.config.Configuration configuration = new net.sf.ehcache.config.Configuration();

net.sf.ehcache.CacheManager manager = net.sf.ehcache.CacheManager.create(configuration);

CacheConfiguration getCacheConfig = new CacheConfiguration()
.maxEntriesLocalHeap(500)
.maxEntriesLocalDisk(500)
.eternal(false)
.timeToIdleSeconds(1800)
.timeToLiveSeconds(1800)
.memoryStoreEvictionPolicy("LRU")
.transactionalMode(CacheConfiguration.TransactionalMode.OFF)
.persistence(new PersistenceConfiguration().strategy(PersistenceConfiguration.Strategy.LOCALTEMPSWAP))
.name("orders");
Cache getAuthenticatedMenuByUriCache = new Cache(getCacheConfig);
manager.addCache(getAuthenticatedMenuByUriCache);

return new EhCacheCacheManager(manager);
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
spring:
datasource:
url: 'jdbc:h2:mem:order_perf;MODE=MYSQL' # H2를 MySQL 모드로 설정
username: 'user'
password: ''
driver-class-name: org.h2.Driver
defer-datasource-initialization: true
url: jdbc:mysql://localhost:3306/ORDERS?serverTimezone=UTC&characterEncoding=UTF-8
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
minimum-idle: 41
maximum-pool-size: 200
jpa:
database: mysql
database-platform: org.hibernate.dialect.MySQLDialect
hibernate:
ddl-auto: none
properties:
hibernate:
# dialect: org.hibernate.dialect.MySQL5Dialect
format_sql: true
show_sql: true

sql:
init:
mode: ALWAYS # 데이터베이스 초기화 시 data.sql을 항상 실행
h2:
console:
enabled: true
path: /h2-console
# sql:
# init:
# mode: ALWAYS # 데이터베이스 초기화 시 data.sql을 항상 실행
# h2:
# console:
# enabled: true
# path: /h2-console
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//console에서 실행하면 됨

CREATE PROCEDURE GenerateTestData(IN numberOfRecords INT)

BEGIN
DECLARE num INT DEFAULT 1;

-- 주문 데이터 생성
WHILE num <= numberOfRecords DO
INSERT INTO ORDERS (delivery_id, refund_id, payment_id, order_number, order_status, created_at, updated_at)
VALUES (
num,
num,
num,
FLOOR(1000 + RAND() * 9000),
CASE FLOOR(RAND() * 3)
WHEN 0 THEN 'CONFIRMED'
WHEN 1 THEN 'CANCELLED'
ELSE 'SHIPPED'
END,
NOW(),
NOW()
);

-- 상품 데이터 생성
INSERT INTO PRODUCTS (order_id, product_name, description, price, bundle_name, bundle_quantity, created_at, updated_at)
VALUES (
num,
CONCAT('상품 ', num),
'asdfsdfwef',
FLOOR(1000 + RAND() * 9000),
CONCAT('세트 ', FLOOR(RAND() * 10) + 1),
FLOOR(RAND() * 10) + 1,
NOW(),
NOW()
);

-- 결제 데이터 생성
INSERT INTO PAYMENTS(id, payment_method, payment_method_name, payment_amount, created_at, updated_at)
values (
num,
case floor(rand()*3)
WHEN 0 THEN 'BILLING_CREDIT_CARD'
WHEN 1 THEN 'BILLING_NAVER_PAY'
ELSE 'BILLING_PAYCO'
end,
case floor(rand()*3)
when 0 then '신용카드'
when 1 then '네이버페이'
else '페이코'
end,
floor(10000+rand()*19000),
now(),
now()
);

-- 배송 데이터 생성
INSERT INTO DELIVERYS (delivery_id, recipient_name, mobile, address, zip_code, store_password, delivery_memo, created_at, updated_at)
VALUES (
num,
concat('수령인', num),
concat('010-', floor(1000+rand()*9000), '-', floor(1000+rand()*9000)),
concat('주소', num),
LPAD(floor(rand()*10000), 4, '0'),
concat('password', num),
concat('asdjklfjskldfjklsdjflk'),
now(),
now()
);

-- 환불 데이터!
INSERT INTO REFUNDS (refund_id, refund_method_name, refund_amount, refund_status, created_at, updated_at)
values (
num,
case floor(rand()*2)
WHEN 0 THEN 'CREDIT_CARD'
ELSE 'NAVER_PAY'
end,
floor(10000+rand()*19000),
case floor(rand()*3)
when 0 then 'PENDING'
when 1 then 'COMPLETED'
else 'FAILED'
end,
now(),
now()
);

SET num = num + 1;
END WHILE;
END;

call GenerateTestData(100);
Binary file added activity/week5/yujiyea/image/img.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added activity/week5/yujiyea/image/img1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added activity/week5/yujiyea/image/img2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added activity/week5/yujiyea/img.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions activity/week5/yujiyea/week5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
## LocalCache 성능 비교

https://github.com/ben-manes/caffeine/wiki/Benchmarks 이 글을 보면 caffeine cache에서 작성한 글로 ehcache와 성능 비교를 하고 있다.

해당 글만 보면 단순히 값을 임시 저장, 조회하는 용도로 사용한다면 caffeine의 성능이 압도적으로 좋게 보인다.

그래서 단순하게 캐시 관련하여 정책이나 다른 설정 없이 캐시를 저장하고 조회했을 때 성능의 차이가 있을지 확인을 해보고 싶었다.

<aside>
💡

**ehcache 도입 트러블 슈팅**

ehcache 버전 2, 3는 설정 하는 방식이 조금 다르다.

이걸 모르고 그냥 적용하다가 꽤 오래동안 붙잡고 있었다.

- ehcache 버전 2의 경우 net.sf.ehcache:ehcache이며
- ehcache 버전 3은 org.ehcache:ehcache로 시작한다.

</aside>

아래가 ehcache의 결과이며 receivin 평균이 1.9ms이고
![img.png](image/img.png)
지난 번의 caffeine의 결과는 2.24ms이다.
![img.png](image/img1.png)

벤치마크 글과 달리 ehcache가 더 빠르게 나왔다.

성능 테스트를 돌릴 때 캐시를 한번 저장하고 계속 똑같은 인덱스를 조회하기 때문인 것인지, 너무 단순하게 진행해서 인지....?

### 그렇다면 caffeine이 좋은 점을 찾아보고 이를 구현해보자.

1. window TinyLFU 알고리즘을 사용
2. 비동기 데이터 로딩을 지원
3. JVM 내부에 최적화되어 있음

`concurrentHashMap` 기반의 설계로 데이터 접근 및 삽입이 매우 빠르고 락 경합이 최소화되어 있다.


⇒ Ehcache는 분산환경, 디스크 저장에 유리하지만 caffeine은 단일 JVM 환경에서 유리하다.

특히 window TinyLFU를 사용한 캐시 제거 부분이 우월하다고 한다.

결국 caffeine과 ehcache의 캐시 제거가 발생할 수 있도록 많은 데이터를 삽입하고 조회하는 방식으로 성능 테스트를 진행해야 한다.

### 1. 프로시저로 데이터를 대량 삽입하기
mysql DB를 기준으로 대량 데이터를 생성할 수 있는 프로시저
100개 데이터 생성 후
1. ehcache receiving 2.5ms
![img.png](image/img2.png)
2. caffeine cache 3.26ms
![img.png](img.png)

마찬가지로 ehcache가 더 빠르다.