1)maven을 눌러서 clean-->pakage를 선택하고 jar파일을 만든다.
(테스트를 안하고 하려면 toggle skip test mode를 누른다)
2)jar 파일이 있는 곳에서 shift +우클릭 한 뒤 powershell로 열기를 한다.
(참고로 gradle에서는 ./gradlew build)
3)
scp .\team05-0.0.1-SNAPSHOT.jar root@ip주소:/root/
(비밀번호 입려할 때 마우스 오른쪽으로 해야함)
4)putty를 켜서 host name에다가 ip주소를 붙여넣고 open을 누른다.
5)이 상태에서 putty를 다시 켜서 db를 연다.
--처음에 putty들어갈 때 login as -->이건 아이디 그다음 비밀번호
6)DB에 도커 설치
sudo dnf update
sudo dnf install dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf install docker-ce docker-ce-cli containerd.io
sudo systemctl start docker
sudo systemctl enable docker
7) MYSQL 실행 커맨드
docker run --name mysql-container -e MYSQL_ROOT_PASSWORD=somepassword -e MYSQL_DATABASE=shortenurl -p 3306:3306 -v /root/mysql-data:/var/lib/mysql -d mysql:latest
docker exec -it mysql-container mysql -u root -p
password는 somepassword
8)
sudo dnf update 패키지 업데이트
sudo dnf install java-17-openjdk-devel
자바 17 설치
sudo update-alternatives --config java 하고서 2누르기
java -jar shortenurlservice-0.0.1-SNAPSHOT.jar
9)8080포트로 방화벽을 열어줘야 한다.
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload
백그라운드로 실행하기
nohup java -jar shortenurlservice-0.0.1-SNAPSHOT.jar > shortenurlservice.log 2>&1 &
인텔리제이에
create-load-test.yaml 파일 생성
config:
target: "http://{{ip주소}}:8080"
phases:
- duration: 100
arrivalRate: 10
rampTo: 100
payload:
path: "urls.csv"
fields:
- "url"
scenarios:
- name: "create shortenUrl"
flow:
- post:
url: "/shortenUrl"
json:
originalUrl: "{{ url }}"
urls.csv 파일 필요함
1)artillery run create-load-test.yaml -o create-load-report.json
이걸로 테스트 실행
2)artillery report --output create-load-report.html create-load-report.json
보고서 생성
여기서 극단적인 값은 그렇게 중요하지 않다.
중요한 건 p95로 95% 이용자가 얼마나 걸렸는지 확인
3)데이터 db에 넣고서
docker exec -i mysql-container bash -c "rm -f /var/lib/mysql-files/keys.csv && mysql -u root -psomepassword shortenurl -e \"SELECT shorten_url_key FROM shorten_url INTO OUTFILE '/var/lib/mysql-files/keys.csv' FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n';\"" && docker cp mysql-container:/var/lib/mysql-files/keys.csv /root/keys.csv
4) 서버에 있는 파일을 가져오기
scp root@{database-server ip주소}:/root/keys.csv .
5)테스트 스크립트 만들기
config:
target: "http://{ip주소}:8080"
phases:
- duration: 100
arrivalRate: 10
rampTo: 100
payload:
path: "keys.csv"
fields:
- "shortenUrlKey"
scenarios:
- flow:
- get:
url: "/{{ shortenUrlKey }}"
followRedirect: false
6)테스트 실행 artillery run read-load-test.yaml -o read-load-report.json
테스트 결과는 위처럼 나온다.
이미 db인덱스가 걸려 있기 때문에 속도가 상당히 빠르다.
(유니크 제약 조건을 걸면 자동으로 인덱스가 생기기 때문-->유니크한지 아닌지 확인하려면 해당 컬럼들을 한번씩 조회해야하기 때문에)
7) show databases;
SHOW INDEX FROM shorten_url;
uk-->유니크 인덱스
ALTER TABLE shorten_url DROP INDEX {유니크 인덱스 Key_name};
인덱스 제거
인덱스를 제거했더니 엄청나게 느려졌다...
timeout도 상당히 많이 발생했다.
*8080포트 확인
netstat -tuln | grep 8080
ps aux | grep java -->뭔지 확인
kill -9 1732
package kr.co.shortenurlservice.infrastructure;
import kr.co.shortenurlservice.domain.ShortenUrl;
import kr.co.shortenurlservice.domain.ShortenUrlRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Repository;
import java.util.concurrent.ConcurrentHashMap;
@Repository
public class ShortenUrlRepositoryImpl implements ShortenUrlRepository {
private final JpaShortenUrlRepository jpaShortenUrlRepository;
private final ConcurrentHashMap<String, ShortenUrl> cache;
@Autowired
public ShortenUrlRepositoryImpl(JpaShortenUrlRepository jpaShortenUrlRepository) {
this.jpaShortenUrlRepository = jpaShortenUrlRepository;
this.cache = new ConcurrentHashMap<>();
}
@Override
public void saveShortenUrl(ShortenUrl shortenUrl) {
jpaShortenUrlRepository.save(shortenUrl);
cache.put(shortenUrl.getShortenUrlKey(), shortenUrl);
}
@Async
@Override
public void asyncSaveShortenUrl(ShortenUrl shortenUrl) {
jpaShortenUrlRepository.save(shortenUrl);
cache.put(shortenUrl.getShortenUrlKey(), shortenUrl);
}
//위 메서드들은 캐시 업데이트도 바로한다.
@Override
public void increaseRedirectCount(ShortenUrl shortenUrl) {
jpaShortenUrlRepository.incrementRedirectCount(shortenUrl.getShortenUrlKey());
}
@Override
public ShortenUrl findShortenUrlByShortenUrlKey(String shortenUrlKey) {
// 먼저 캐시에서 조회
ShortenUrl shortenUrl = cache.get(shortenUrlKey);
if (shortenUrl == null) {
// 캐시에 없으면 데이터베이스에서 조회
shortenUrl = jpaShortenUrlRepository.findByShortenUrlKey(shortenUrlKey);
if (shortenUrl != null) {
// 데이터베이스에 있으면 캐시에 저장
cache.put(shortenUrlKey, shortenUrl);
}
}
return shortenUrl;
}
}
처음 테스트랑 그렇게 큰 차이가 없다.
로그를 보면, 캐시가 됐을텐데도 계속 select 쿼리가 나가는 걸 볼 수있다.
jpa의 특성상 update를 하려면 select를 해야 하기 때문에 그렇다.
@Query("UPDATE ShortenUrl su SET su.redirectCount = su.redirectCount + 1 WHERE su.shortenUrlKey = :shortenUrlKey")
int incrementRedirectCount(@Param("shortenUrlKey") String shortenUrlKey);
@Transactional(readOnly = false)
public String getOriginalUrlByShortenUrlKey(String shortenUrlKey) {
ShortenUrl shortenUrl = shortenUrlRepository.findShortenUrlByShortenUrlKey(shortenUrlKey);
if(null == shortenUrl)
throw new NotFoundShortenUrlException();
shortenUrl.increaseRedirectCount();
shortenUrlRepository.saveShortenUrl(shortenUrl);
String originalUrl = shortenUrl.getOriginalUrl();
return originalUrl;
}
이걸
@Transactional(readOnly = false)
public String getOriginalUrlByShortenUrlKey(String shortenUrlKey) {
ShortenUrl shortenUrl = shortenUrlRepository.findShortenUrlByShortenUrlKey(shortenUrlKey);
if(null == shortenUrl)
throw new NotFoundShortenUrlException();
shortenUrl.increaseRedirectCount();
// shortenUrlRepository.saveShortenUrl(shortenUrl);
shortenUrlRepository.increaseRedirectCount(shortenUrl);
String originalUrl = shortenUrl.getOriginalUrl();
return originalUrl;
}
이렇게 변경
처음에는 jpa더티체킹이랑 update 쿼리가 같이 나가서 총 3개의 쿼리가 나감
나중에는 1개만 나감
테스트를 몇번 더 해보면 성능이 좋아질 수 있음
(캐시라서)
테스트 결과를 보면 들쭉날쭉하다
강사님도 성능 테스트를 할 때 직접 확인하면서 좋아지는지 나빠지는지를 계쏙 확인해봐야 한다고 말했다.
레디스를 쓰면 애플리케이션 내에서만 캐시를 공유하게 된다고 한다. 애플리케이션 간의 캐시 공유를 막는 것
db서버에서 docker run --name redis-container -d -p 6379:6379 redis
application.properties에
spring.redis.host={데이터베이스 ip 주소}
spring.redis.port=6379
추가
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, ShortenUrl> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, ShortenUrl> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
@Repository
public class ShortenUrlRepositoryImpl implements ShortenUrlRepository {
private final JpaShortenUrlRepository jpaShortenUrlRepository;
private final RedisTemplate<String, ShortenUrl> redisTemplate;
private static final String CACHE_PREFIX = "shortenUrl::";
//여러 데이터를 넣어줄 수 있기에 프리픽스로 구분함
@Autowired
public ShortenUrlRepositoryImpl(JpaShortenUrlRepository jpaShortenUrlRepository,
RedisTemplate<String, ShortenUrl> redisTemplate) {
this.jpaShortenUrlRepository = jpaShortenUrlRepository;
this.redisTemplate = redisTemplate;
}
@Override
public void saveShortenUrl(ShortenUrl shortenUrl) {
jpaShortenUrlRepository.save(shortenUrl);
redisTemplate.opsForValue().set(CACHE_PREFIX + shortenUrl.getShortenUrlKey(), shortenUrl);
}
@Async
@Override
public void asyncSaveShortenUrl(ShortenUrl shortenUrl) {
jpaShortenUrlRepository.save(shortenUrl);
redisTemplate.opsForValue().set(CACHE_PREFIX + shortenUrl.getShortenUrlKey(), shortenUrl);
}
@Override
public void increaseRedirectCount(ShortenUrl shortenUrl) {
jpaShortenUrlRepository.incrementRedirectCount(shortenUrl.getShortenUrlKey());
}
@Override
public ShortenUrl findShortenUrlByShortenUrlKey(String shortenUrlKey) {
// 먼저 Redis 캐시에서 조회
ShortenUrl shortenUrl = redisTemplate.opsForValue().get(CACHE_PREFIX + shortenUrlKey);
if (shortenUrl == null) {
// 캐시에 없으면 데이터베이스에서 조회
shortenUrl = jpaShortenUrlRepository.findByShortenUrlKey(shortenUrlKey);
if (shortenUrl != null) {
// 데이터베이스에 있으면 캐시에 저장
redisTemplate.opsForValue().set(CACHE_PREFIX + shortenUrlKey, shortenUrl);
}
}
return shortenUrl;
}
}
레디스 쓸려면
sudo firewall-cmd --permanent --add-port=6379/tcp
sudo firewall-cmd --reload
방화벽 열어줘야함
근데, 지금 다시 application.properites값을 읽지 못하는 문제 발생
@Configuration
public class RedisConfig {
@Value("${spring.data.redis.host}")
private String redisHost;
@Value("${spring.data.redis.port}")
private int redisPort;
@Bean
public RedisConnectionFactory redisConnectionFactory(){
return new LettuceConnectionFactory(redisHost, redisPort);
}
@Bean
public RedisTemplate<String, ShortenUrl> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, ShortenUrl> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
이렇게 값을 직접 넣어줘야함
(보통 레디스를 쓸 때는 db 서버가 아닌 다른 서버에 놓는다고 한다. 그렇게 하지 않으면, db랑 레디스가 리소스 경합을 할 수 있다고 한다)
나는 저렇게 설정을 바꿔도 안됐는데
db 서버에 방화벽에서 6379 포트를 안 열어주었기 때문이다.
모든 키를 다 지우고 단축 url을 하나로만 만든 뒤에 성능테스트를 해도 반응속도가 그렇게까지 좋지는 않다.
왜냐하면, redirectcount 업데이트를 계속 해야하기 때문이다.
이 경우에는 업데이트를 위해서 레코드에 lock을 걸기 때문.
public interface ShortenUrlRepository {
void saveShortenUrl(ShortenUrl shortenUrl);
void asyncSaveShortenUrl(ShortenUrl shortenUrl);
ShortenUrl findShortenUrlByShortenUrlKey(String shortenUrlKey);
void increaseRedirectCount(ShortenUrl shortenUrl);
@Scheduled(fixedRate = 10000)
void updateRedirectCounts();
}
//10초마다 실행
@Repository
public class ShortenUrlRepositoryImpl implements ShortenUrlRepository {
private final JpaShortenUrlRepository jpaShortenUrlRepository;
private final ConcurrentHashMap<String, ShortenUrl> cache;
private final ConcurrentHashMap<String, AtomicInteger> redirectCountMap;
@Autowired
public ShortenUrlRepositoryImpl(JpaShortenUrlRepository jpaShortenUrlRepository) {
this.jpaShortenUrlRepository = jpaShortenUrlRepository;
this.cache = new ConcurrentHashMap<>();
this.redirectCountMap = new ConcurrentHashMap<>();
}
@Override
public void saveShortenUrl(ShortenUrl shortenUrl) {
jpaShortenUrlRepository.save(shortenUrl);
cache.put(shortenUrl.getShortenUrlKey(), shortenUrl);
}
@Async
@Override
public void asyncSaveShortenUrl(ShortenUrl shortenUrl) {
jpaShortenUrlRepository.save(shortenUrl);
cache.put(shortenUrl.getShortenUrlKey(), shortenUrl);
}
@Override
public void increaseRedirectCount(ShortenUrl shortenUrl) {
jpaShortenUrlRepository.incrementRedirectCount(shortenUrl.getShortenUrlKey());
}
@Override
public ShortenUrl findShortenUrlByShortenUrlKey(String shortenUrlKey) {
// 먼저 캐시에서 조회
ShortenUrl shortenUrl = cache.get(shortenUrlKey);
if (shortenUrl == null) {
// 캐시에 없으면 데이터베이스에서 조회
shortenUrl = jpaShortenUrlRepository.findByShortenUrlKey(shortenUrlKey);
if (shortenUrl != null) {
// 데이터베이스에 있으면 캐시에 저장
cache.put(shortenUrlKey, shortenUrl);
}
}
return shortenUrl;
}
@Scheduled(fixedRate = 10000)
@Override
public void updateRedirectCounts() {
redirectCountMap.forEach((key, count) -> {
int increment = count.getAndSet(0); //가져오고 키값을 0으로 변경
if (increment > 0) {
jpaShortenUrlRepository.incrementRedirectCount(key, increment);
}
});
}
}
리다이렉트를 여러번에 모아서 했을 때 결과다.
이건 내 노트북의 네트워크 통신 문제인거같기도하다...
쿼리는 굉장히 조금 나갔다.
다시 진행해보니 상당히 성능이 괜찮았다.
redirectcount를 카프카에 저장해도 괜찮다고 한다.
특히 유실되면 안되는 데이터는 이렇게 애플리케이션 내부에 저장하는 건 위험하다고 한다. -->카프카가 더 안전