Skip to content
Merged
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
18 changes: 1 addition & 17 deletions .github/workflows/dev-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,29 +35,13 @@ jobs:
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar

# - name: S3에서 init.sql 다운로드
# env:
# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
# AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
# run: |
# mkdir -p docker
# aws s3 cp s3://devcos4-team08-bucket/db/init.sql ./docker/init.sql


- name: Docker Compose 설치
run: |
sudo curl -L "https://github.com/docker/compose/releases/download/v2.17.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

- name: CI용 빈 init.sql 생성
run: |
mkdir -p docker
touch docker/init.sql

- name: Docker Compose 실행
run: |
chmod -R 755 ./docker
docker-compose -f docker-compose.yml up -d

- name: 컨테이너 실행 대기
Expand Down Expand Up @@ -117,7 +101,7 @@ jobs:
run: |
chmod +x ./gradlew
# 소나클라우드 임시 비활성화 ./gradlew build jacocoTestReport sonar --info -Pprofile=dev -Dsonar.branch.name=${{ github.ref_name }}
./gradlew build -i jacocoTestReport -Pprofile=dev
./gradlew build -i jacocoTestReport -Pprofile=test


- name: Docker Compose 종료
Expand Down
1 change: 0 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ services:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
- ./docker/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- log4u-net

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public LocalContainerEntityManagerFactoryBean postgresqlEntityManagerFactory() {

Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
properties.put("hibernate.hbm2ddl.auto", "none");
properties.put("hibernate.hbm2ddl.auto", "update");
properties.put("hibernate.format_sql", true);
em.setJpaPropertyMap(properties);

Expand Down
6 changes: 3 additions & 3 deletions src/main/java/com/example/log4u/common/config/S3Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
@Configuration
public class S3Config {

@Value("${AWS_ACCESS_KEY_ID}")
@Value("${spring.aws.access-key-id}")
private String accessKey;

@Value("${AWS_SECRET_ACCESS_KEY}")
@Value("${spring.aws.secret-access-key}")
private String secretKey;

@Value("${AWS_REGION:ap-northeast-2}")
@Value("${spring.aws.region:ap-northeast-2}")
private String region;

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.example.log4u.common.executor;

import java.util.function.Supplier;

import org.springframework.stereotype.Component;

import com.example.log4u.common.infra.cache.CacheManager;

import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class DistributedLockExecutor {

private final CacheManager cacheManager;

public void runWithLock(String lockKey, Runnable task) {
runWithLock(lockKey, () -> task);
}

public <T> T runWithLock(String lockKey, Supplier<T> task) {
if (!cacheManager.tryLock(lockKey)) {

Check warning on line 22 in src/main/java/com/example/log4u/common/executor/DistributedLockExecutor.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a primitive boolean expression here.

See more on https://sonarcloud.io/project/issues?id=prgrms-web-devcourse-final-project_WEB3_4_Log4U_BE&issues=AZqnf3bh76rzY11mWhNC&open=AZqnf3bh76rzY11mWhNC&pullRequest=70
return null;
}
try {
return task.get();
} finally {
cacheManager.releaseLock(lockKey);
}
}
}
39 changes: 39 additions & 0 deletions src/main/java/com/example/log4u/common/executor/RetryExecutor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.example.log4u.common.executor;

import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import org.springframework.stereotype.Component;

import com.example.log4u.domain.map.exception.MaxRetryExceededException;

@Component
public class RetryExecutor {

int MAX_RETRY_CNT = 10;

Check warning on line 13 in src/main/java/com/example/log4u/common/executor/RetryExecutor.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this field "MAX_RETRY_CNT" to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=prgrms-web-devcourse-final-project_WEB3_4_Log4U_BE&issues=AZqnf3dx76rzY11mWhNE&open=AZqnf3dx76rzY11mWhNE&pullRequest=70
int INITIAL_DELAY_MSEC = 1_000;

Check warning on line 14 in src/main/java/com/example/log4u/common/executor/RetryExecutor.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this field "INITIAL_DELAY_MSEC" to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=prgrms-web-devcourse-final-project_WEB3_4_Log4U_BE&issues=AZqnf3dx76rzY11mWhNF&open=AZqnf3dx76rzY11mWhNF&pullRequest=70
int MAX_DELAY_MSEC = 100_000;

Check warning on line 15 in src/main/java/com/example/log4u/common/executor/RetryExecutor.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this field "MAX_DELAY_MSEC" to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=prgrms-web-devcourse-final-project_WEB3_4_Log4U_BE&issues=AZqnf3dx76rzY11mWhNG&open=AZqnf3dx76rzY11mWhNG&pullRequest=70

public <T> T runWithRetry(Supplier<T> task) {
for (int attempt = 0; attempt < MAX_RETRY_CNT; attempt++) {
T result = task.get();
if (result != null) {
return result;
}
backoff(attempt);
}
throw new MaxRetryExceededException();
}

private void backoff(int attempt) {
try {
int delay = Math.min(
INITIAL_DELAY_MSEC * (int) Math.pow(2, attempt - 1),

Check warning on line 31 in src/main/java/com/example/log4u/common/executor/RetryExecutor.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Cast one of the operands of this subtraction operation to a "double".

See more on https://sonarcloud.io/project/issues?id=prgrms-web-devcourse-final-project_WEB3_4_Log4U_BE&issues=AZqnf3dx76rzY11mWhND&open=AZqnf3dx76rzY11mWhND&pullRequest=70
MAX_DELAY_MSEC);

TimeUnit.MILLISECONDS.sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@

public interface CacheManager {

void cache(String key, String value, Duration ttl);
void init();

void cache(String key, String value, Duration ttl);

String get(String key);

void evict(String key);

Boolean tryLock(String key);

void releaseLock(String key);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@
@RequiredArgsConstructor
public class RedisCacheManagerImpl implements CacheManager {

private final RedisTemplate<String, String> redisTemplate;
private static final String LOCK_VALUE = "locked";

private final RedisTemplate<String, String> redisTemplate;

public void init() {
Objects.requireNonNull(redisTemplate.getConnectionFactory())
.getConnection().flushAll();
}

public void cache(String key, String value, Duration ttl) {
redisTemplate.opsForValue().set(key, value, ttl);
Expand All @@ -27,4 +34,12 @@ public String get(String key) {
public void evict(String key) {
redisTemplate.delete(key);
}

public Boolean tryLock(String key) {
return redisTemplate.opsForValue().setIfAbsent(key, LOCK_VALUE);
}

public void releaseLock(String key) {
redisTemplate.delete(key);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

import org.springframework.stereotype.Component;

import com.example.log4u.common.executor.DistributedLockExecutor;
import com.example.log4u.common.infra.cache.CacheManager;
import com.example.log4u.domain.map.cache.CacheKeyGenerator;
import com.example.log4u.domain.map.cache.RedisTTLPolicy;
import com.example.log4u.domain.map.dto.response.DiaryClusterResponseDto;
import com.example.log4u.domain.map.exception.InvalidMapLevelException;
Expand All @@ -23,13 +23,19 @@
@Slf4j
public class ClusterCacheDao {

private static final String CLUSTER_CACHE_KEY = "cluster:geohash:%s:level:%d";
public static final String CLUSTER_LOCK_KEY = "cluster-lock";

private final CacheManager cacheManager;

private final DistributedLockExecutor distributedLockExecutor;

private final SidoAreasRepository sidoAreasRepository;
private final SiggAreasRepository siggAreasRepository;

public List<DiaryClusterResponseDto> load(String geohash, int level) {
String value = cacheManager.get(CacheKeyGenerator.clusterCacheKey(geohash, level));
String key = String.format(CLUSTER_CACHE_KEY, geohash, level);
String value = cacheManager.get(key);
if (value == null) {
return null;
}
Expand All @@ -42,9 +48,11 @@ private List<DiaryClusterResponseDto> convertToClusters(String value) {
}

public List<DiaryClusterResponseDto> loadAndCache(String geohash, int level) {
List<DiaryClusterResponseDto> clusters = loadClustersFromDb(geohash, level);
cache(clusters, geohash, level);
return clusters;
return distributedLockExecutor.runWithLock(CLUSTER_LOCK_KEY, () -> {
List<DiaryClusterResponseDto> clusters = loadClustersFromDb(geohash, level);
cache(clusters, geohash, level);
return clusters;
});
}

private List<DiaryClusterResponseDto> loadClustersFromDb(String geohash, int level) {
Expand All @@ -56,17 +64,17 @@ private List<DiaryClusterResponseDto> loadClustersFromDb(String geohash, int lev
}

private void cache(List<DiaryClusterResponseDto> clusters, String geohash, int level) {
String key = CacheKeyGenerator.clusterCacheKey(geohash, level);
String key = String.format(CLUSTER_CACHE_KEY, geohash, level);
cacheManager.cache(key, writeValueAsString(clusters), RedisTTLPolicy.CLUSTER_TTL);
}

public void evictSido(String geohash) {
String key = CacheKeyGenerator.clusterCacheKey(geohash, 1);
String key = String.format(CLUSTER_CACHE_KEY, geohash, 1);
cacheManager.evict(key);
}

public void evictSigg(String geohash) {
String key = CacheKeyGenerator.clusterCacheKey(geohash, 2);
String key = String.format(CLUSTER_CACHE_KEY, geohash, 2);
cacheManager.evict(key);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

import org.springframework.stereotype.Component;

import com.example.log4u.common.executor.DistributedLockExecutor;
import com.example.log4u.common.infra.cache.CacheManager;
import com.example.log4u.domain.diary.entity.Diary;
import com.example.log4u.domain.diary.repository.DiaryGeoHashRepository;
import com.example.log4u.domain.diary.repository.DiaryRepository;
import com.example.log4u.domain.map.cache.CacheKeyGenerator;
import com.example.log4u.domain.map.cache.RedisTTLPolicy;
import com.example.log4u.domain.map.dto.response.DiaryMarkerResponseDto;
import com.fasterxml.jackson.core.type.TypeReference;
Expand All @@ -24,13 +24,19 @@
@Slf4j
public class MarkerCacheDao {

private static final String MARKER_CACHE_KEY = "marker:geohash:%s";
public static final String MARKER_LOCK_KEY = "marker-lock";

private final CacheManager cacheManager;

private final DistributedLockExecutor distributedLockExecutor;

private final DiaryRepository diaryRepository;
private final DiaryGeoHashRepository diaryGeoHashRepository;

public List<DiaryMarkerResponseDto> load(String geohash) {
String value = cacheManager.get(CacheKeyGenerator.markerCacheKey(geohash));
String key = String.format(MARKER_CACHE_KEY, geohash);
String value = cacheManager.get(key);
if (value == null) {
return null;
}
Expand All @@ -43,9 +49,11 @@ private List<DiaryMarkerResponseDto> convertToMarkers(String value) {
}

public List<DiaryMarkerResponseDto> loadAndCache(String geohash) {
List<DiaryMarkerResponseDto> markers = loadMarkersFromDb(geohash);
cache(markers, geohash);
return markers;
return distributedLockExecutor.runWithLock(MARKER_LOCK_KEY, () -> {
List<DiaryMarkerResponseDto> markers = loadMarkersFromDb(geohash);
cache(markers, geohash);
return markers;
});
}

private List<DiaryMarkerResponseDto> loadMarkersFromDb(String geohash) {
Expand All @@ -60,11 +68,12 @@ private List<DiaryMarkerResponseDto> loadMarkersFromDb(String geohash) {
}

private void cache(List<DiaryMarkerResponseDto> markers, String geohash) {
String key = CacheKeyGenerator.markerCacheKey(geohash);
String key = String.format(MARKER_CACHE_KEY, geohash);
cacheManager.cache(key, writeValueAsString(markers), RedisTTLPolicy.MARKER_TTL);
}

public void evict(String geohash) {
cacheManager.evict(CacheKeyGenerator.markerCacheKey(geohash));
String key = String.format(MARKER_CACHE_KEY, geohash);
cacheManager.evict(key);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.log4u.domain.map.exception;

import lombok.Getter;

@Getter
public class MaxRetryExceededException extends RuntimeException {
}
Loading
Loading