일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 친절한 SQL
- 논리 연산자
- 상속
- 친절한 SQL 튜닝
- 오버로딩
- 오버라이딩
- 인텔리제이 Web 애플리케이션
- 자바의정석
- 연산자
- 객체지향
- 반복문
- SQL 튜닝
- 객체
- java
- SQL튜닝
- spring 게시판 삭제
- 비교 연산자
- StringBuffer
- 스프링시큐리티 로그아웃
- 식별자
- 함수
- join
- @PreAuthorize("isAuthenticated()")
- 산술 연산자
- 이클립스 설치
- SQL
- SpringSecurity 로그인
- SpringSecurity 로그아웃
- 예약어
- 배열
Archives
- Today
- Total
gi_dor
[Refactor] 카페인 캐싱으로 성능개선 , Ngrinder 본문
728x90
Cache와 Caffeine Cache
캐시(cache) : 데이터나 값을 미리 복사해 놓는 임시 장소
Local Cache
- 서버마다 캐시를 따로 저장
- 다른 서버의 캐시를 참조하기 어려움
- 속도 빠름
- 로컬 서버 장비의 Resource를 이용한다. (Memory, Disk)
Global Cache
- 여러 서버에서 캐시 서버 접근 및 참조 가능
- 별도의 캐시 서버 이용 → 서버 간 데이터 공유가 쉬움
- 네트워크 트래픽을 사용해야 해서 로컬 캐시보다는 느리다.
- 데이터를 분산하여 저장 가능
Caffeine Cache
- Spring Boot 3부터 @Bean으로 org.springframework.cache.CacheManager의 EhCacheCacheManager 구현은 더 이상 지원되지 않는다고 한다....... 인강에서는 ehCache 쓰는데..
- 빠르게 적용하기 쉬운 Local cache caffeine 선택
1. 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'com.github.ben-manes.caffeine:caffeine'
2. 구조
3. 캐시 설정 CacheConfig
- TimeUnit.SECONDS 부분에 코드를 통해 캐시 만료 데이터 기간을 초,분,시단위로 설정가능.
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public List<CaffeineCache> caffeineCaches() {
return Arrays.stream(CacheType.values())
.map(cache -> new CaffeineCache(cache.getCacheName(), Caffeine.newBuilder().recordStats()
.expireAfterWrite(cache.getExpiredAfterWrite(), TimeUnit.SECONDS)
.maximumSize(cache.getMaximumSize())
.build()))
.toList();
}
@Bean
public CacheManager cacheManager(List<CaffeineCache> caffeineCaches) {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(caffeineCaches);
return cacheManager;
}
}
CacheType
package com.example.bookhub.common;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum CacheType {
INQUIRIES("MyPageMapper.cacheInquiries" , 10,10000),
NOTICE_FINDALL("NoticeMapper.findAll" , 10 ,10000);
private final String cacheName;
private final int expiredAfterWrite;
private final int maximumSize;
}
4. 사용하기
- 캐시 사용 전 , 캐시 사용 후
@Transactional(readOnly = true)
public PageListDTO<InquiryListDTO> getInquiryListByIdPage(String id , int page) {
// 사용자 정보조회
User user = userMapper.selectUserById(id);
// 사용자의 아이디로 전체 작성된 1:1문의 갯수 조회
int totalRows = myPageMapper.countInquiry(user.getId());
// 페이징 정보
UserPagination userPagination = new UserPagination(page , totalRows);
int offset = userPagination.getBegin() -1;
// 페이징된 결과 조회
List<InquiryListDTO> inquiryListDTO = myPageMapper.selectInquiryListPaging(user.getId(), offset);
PageListDTO<InquiryListDTO> pageListDTO = new PageListDTO<>();
pageListDTO.setItems(inquiryListDTO);
pageListDTO.setUserPagination(userPagination);
// PageListDTO<InquiryListDTO> pageListDTO = new PageListDTO<>(inquiryListDTO,userPagination);
return pageListDTO;
}
@Cacheable(value = "MyPageMapper.cacheInquiries" , key = "#page", condition = "#page <= 4")
public PageListDTO<InquiryListDTO> getCacheInquiriesList( int page) {
// 사용자의 아이디로 전체 작성된 1:1문의 갯수 조회
int totalRows = myPageMapper.countInquiriesAll();
// 페이징 정보
UserPagination userPagination = new UserPagination(page , totalRows);
int offset = userPagination.getBegin() -1;
// 페이징된 결과 조회 - 캐싱 Mapper
List<InquiryListDTO> inquiryListDTO = myPageMapper.cacheInquiries(offset);
PageListDTO<InquiryListDTO> pageListDTO = new PageListDTO<>();
pageListDTO.setItems(inquiryListDTO);
pageListDTO.setUserPagination(userPagination);
// PageListDTO<InquiryListDTO> pageListDTO = new PageListDTO<>(inquiryListDTO,userPagination);
return pageListDTO;
}
<!-- 캐싱 1:1 문의 -->
<select id="cacheInquiries" resultType="com.example.bookhub.user.dto.InquiryListDTO">
select
i.INDIVIDUAL_INQUIRY_NO as no,
i.INQUIRY_CATEGORY_NO as "faqCategory.no",
f.FAQ_CATEGORY_NAME as "faqCategory.name",
i.INQUIRY_USER_NO as "user.no",
u.USER_ID as "user.id",
i.INDIVIDUAL_INQUIRY_TITLE as title,
i.INDIVIDUAL_INQUIRY_CONTENT as content,
i.INDIVIDUAL_INQUIRY_ANSWER_YN as answerYn,
i.INDIVIDUAL_INQUIRY_DELETE_YN as deleteYn,
i.INDIVIDUAL_INQUIRY_CREATE_DATE as createdDate ,
i.INDIVIDUAL_INQUIRY_UPDATE_DATE as updatedDate,
u.USER_NAME as "user.name"
from INDIVIDUAL_INQUIRIES i , USER u , FAQ_CATEGORIES f
where i.INQUIRY_USER_NO = u.USER_NO
and i.INQUIRY_CATEGORY_NO = f.FAQ_CATEGORY_NO
order by i.INDIVIDUAL_INQUIRY_CREATE_DATE DESC
LIMIT #{offset} ,10
</select>
// 캐싱 1:1 문의
List<InquiryListDTO>cacheInquiries( @Param("offset")int offset);
@Slf4j
@Controller
@RequiredArgsConstructor
@RequestMapping("/mypage/list")
public class UserMyPageListController {
private final UserService userService;
private final MyPageService myPageService;
private final ReturnService returnService;
@PreAuthorize("isAuthenticated()")
@GetMapping("/inquiryListCache")
public String inquiryListPageCache(@RequestParam(name="page" , required = false ,defaultValue="1") int page,
Model model) {
System.out.println(" :: 캐싱처리 했음 :: ");
// 로그인한 사용자의 1:1 문의 목록 , 페이징 정보 조회
PageListDTO<InquiryListDTO> inquiryList = myPageService.getCacheInquiriesList( page);
// 로그인한 사용자가 작성한 글의 갯수 조회
int totalRows = myPageService.countInquiriesAll();
model.addAttribute("totalRows",totalRows);
model.addAttribute("inquiryList",inquiryList.getItems());
model.addAttribute("page",inquiryList.getUserPagination());
return "user/list/inquiryList";
}
}
5. Ngrinder Test
- 랜덤페이지 1이상 4이하로 설정
int randomPage = new Random().nextInt(4) + 1;
String url = "http://127.0.0.1:8080/mypage/list/inquiryList?page=" + randomPage
import static net.grinder.script.Grinder.grinder
import static org.junit.Assert.*
import static org.hamcrest.Matchers.*
import net.grinder.script.GTest
import net.grinder.script.Grinder
import net.grinder.scriptengine.groovy.junit.GrinderRunner
import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess
import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread
import static net.grinder.util.GrinderUtils.* // You can use this if you're using nGrinder after 3.2.3
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import org.ngrinder.http.HTTPRequest
import org.ngrinder.http.HTTPRequestControl
import org.ngrinder.http.HTTPResponse
import org.ngrinder.http.cookie.Cookie
import org.ngrinder.http.cookie.CookieManager
/**
* A simple example using the HTTP plugin that shows the retrieval of a single page via HTTP.
*
* This script is automatically generated by ngrinder.
*
* @author admin
*/
@RunWith(GrinderRunner)
class TestRunner {
public static GTest test
public static HTTPRequest request
public static Map<String, String> headers = [:]
public static Map<String, Object> params = [:]
public static List<Cookie> cookies = []
@BeforeProcess
public static void beforeProcess() {
HTTPRequestControl.setConnectionTimeout(300000)
test = new GTest(1, "127.0.0.1")
request = new HTTPRequest()
grinder.logger.info("before process.")
}
@BeforeThread
public void beforeThread() {
test.record(this, "test")
grinder.statistics.delayReports = true
grinder.logger.info("before thread.")
}
@Before
public void before() {
request.setHeaders(headers)
CookieManager.addCookies(cookies)
grinder.logger.info("before. init headers and cookies")
}
@Test
public void test() {
// 랜덤페이지 1이상 4이하
int randomPage = new Random().nextInt(4) + 1;
String url = "http://127.0.0.1:8080/mypage/list/inquiryList?page=" + randomPage
HTTPResponse response = request.GET(url, params)
if (response.statusCode == 301 || response.statusCode == 302) {
grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", response.statusCode)
} else {
assertThat(response.statusCode, is(200))
}
}
}
200이 아닌 302가 왜뜨는데 ?
사용자 인증이 되지 않아서 Test 실패
@PreAuthorize("isAuthenticated()")
- 스프링 시큐리티에서 제공하는 어노테이션
- 메서드를 호출하기전에 사용자가 인증되는지 검사한다
인증 어노테이션 제거후 Test
캐싱 비교 최종 결과
결과 TPS 31.9 → 43.8 약 27% 성능 개선
728x90
'Back_End > SpringBoot' 카테고리의 다른 글
Spring에서 비동기 @Async (0) | 2024.08.29 |
---|---|
스프링부트 + MyBatis +MYSQL 페이징 처리 (0) | 2024.05.09 |
스프링부트 설정파일(application.properties) 암호화 (Jasypt) (0) | 2024.05.02 |
비밀번호 찾기 + 임시비밀번호 이메일전송 (0) | 2024.04.30 |
마이페이지 ver.2- SpringSecurity, MySQL , MyBatis (1) | 2024.04.25 |