Skip to content

Conversation

@PicturePark1101
Copy link
Contributor

@PicturePark1101 PicturePark1101 commented Apr 22, 2025

작업내용

이런 걸 했답니다

상세설명_ & 캡쳐

Redis 관련 추가 설정이 필요합니다.

  • 로컬 yml에는 아래와 같이 설정해주시고 터미널에서 레디스 설치해주시면 됩니다. dev와 prod에는 제가 머지 전 Docker compose 설정파일에 추가하겠습니다.

application-local.yml

spring:
  data:
    redis:
      host: localhost
      port: 6379

구현 로직 - 조회수

  • 구상한 로직은 아래와 같습니다.
  • 목적 : redis로 지연 업데이트하여 빈번한 update문을 줄여 성능개선
  1. Redis에서 {sharedNoteId}로 key 생성, 조회수 조회 및 + 1 증가
  2. 실패한다면 RDB에서 조회
  3. Redis에서 {sharedNoteId + memberId} 조합으로 key 생성 후, TTL을 6시간으로 설정하여 중복 조회 방지
  4. Spring 스케줄러를 통해 Redis - RDB 간 데이터 동기화. redis의 조회수를 RDB의 조회수로 합친 후 key 제거

조회수 상세 설명

	long viewCount = sharedNote.getViewCount() + getViewCount(sharedNoteId);
		if (!isDuplicate(member.getMemberId(), sharedNoteId)) {
			increaseViewCount(sharedNoteId);
			viewCount++;
			recordViewerHistory(member.getMemberId(), sharedNoteId);
		}

상세설명

  • viewCount 변수 : 총 조회수(RDB 조회수 + Redis조회수), 유저에게 반환하는 값
    아래의 함수를 통해 Redis 조회수를 가져옵니다.
	private Long getViewCount(long sharedNoteId) {
		try {
			Object value = redisTemplate.opsForValue().get(RedisKeyFactory.viewCountKey(sharedNoteId));
			return value == null ? 0L : Long.parseLong(value.toString());
		} catch (RedisConnectionFailureException | RedisSystemException e) {
			log.error("Redis 장애 발생, 기본값 반환 sharedNoteID={}", sharedNoteId, e);
			return 0L;
		}
	}
  • isDuplicate 메소드 : Redis에서 해당 공유노트에 대한 유저의 접속기록이 있는 지 확인
	private boolean isDuplicate(long memberId, long sharedNoteId) {
		try {
			return Boolean.TRUE.equals(redisTemplate.hasKey(RedisKeyFactory.viewHistoryKey(sharedNoteId, memberId)));
		} catch (RedisConnectionFailureException | RedisSystemException e) {
			log.error("Redis 장애로 조회 기록 확인 실패. sharedNoteId={}, memberId={}", sharedNoteId, memberId, e);
			return false;
		}
	}
  • increaseViewCount : 기록에 없다면 Redis에서 해당 공유노트의 조회수 증가
	private void increaseViewCount(long sharedNoteId) {
		try {
			redisTemplate.opsForValue().increment(RedisKeyFactory.viewCountKey(sharedNoteId));
		} catch (RedisConnectionFailureException | RedisSystemException e) {
			log.error("Redis 조회수 증가 실패, sharedNoteId={}", sharedNoteId, e);
		}
	}
  • recordViewerHistory : 접속 기록을 Redis에 갱신
	private void recordViewerHistory(long memberId, long sharedNoteId) {
		try {
			redisTemplate.opsForValue()
				.setIfAbsent(RedisKeyFactory.viewHistoryKey(sharedNoteId, memberId), "1", Duration.ofHours(3));
		} catch (RedisConnectionFailureException | RedisSystemException e) {
			log.error("Redis 연결 실패 - 중복 조회 기록 불가, sharedNoteId={}, memberId={}", sharedNoteId, memberId, e);
		}
	}

Redis

common 패키지 아래에 아래와 같이 레디스 키를 관리하는 클래스를 생성했습니다.
VIEW_COUNT는 조회수 카운트 관련, VIEW_HISTORY는 유저의 접속 기록 관리입니다.

public class RedisKeyFactory {

	public static final String VIEW_COUNT = "viewcount:sharedNoteId:";
	private static final String VIEW_HISTORY = "viewed:sharedNoteId:";

	public static String viewCountKey(long sharedNoteId) {
		return VIEW_COUNT + sharedNoteId;
	}

	public static String viewHistoryKey(long sharedNoteId, long memberId) {
		return VIEW_HISTORY + sharedNoteId + ":member:" + memberId;
	}
}

RDB - Redis 동기화(스프링 스케줄러)

ViewCountSyncScheduler

  • 동기화하는 방법으로 스프링 스케줄러를 고려해봤습니다.
  • 현재 시간상의 이유로 스케줄러로 고려했으나, 추후 유저가 늘어날 것을 대비해 메시지큐를 통해 동기화해볼 수도 있을 것 같습니다.

흐름

  1. Redis의 조회수 카운트를 가져옴
  2. 해당하는 sharedNote에 update문을 통해 기존 조회수에 +N함
  3. redis에서 해당 key값 제거
	@Scheduled(cron = "0 0 */6 * * *") // 6시간마다 실행
	@Transactional
	public void syncRedisViewCountsToRDB() {
		Set<String> keys = redisTemplate.keys(VIEW_COUNT + "*");
		if (keys == null || keys.isEmpty())
			return;

		log.info("Redis RDB 조회수 동기화 시작: 총 {}개", keys.size());

		for (String key : keys) {
			try {
				long sharedNoteId = Long.parseLong(key.split(":")[2]);
				Object value = redisTemplate.opsForValue().get(key);
				if (value == null) {
					log.warn("조회수 값 없음 - key={}", key);
					continue;
				}
				long addAmount = Long.parseLong(value.toString());
				sharedNoteUpdater.updateViewCount(sharedNoteId, addAmount);
				log.info("조회수 동기화: sharedNoteId={}, Redis={}", sharedNoteId, addAmount);
				redisTemplate.delete(key);
			} catch (Exception e) {
				log.error("동기화 실패: key={}, error={}", key, e.getMessage(), e);
			}
		}
	}

@PicturePark1101 PicturePark1101 added the 진이 작업했습니다. label Apr 22, 2025
@PicturePark1101 PicturePark1101 self-assigned this Apr 22, 2025
Copy link
Contributor

@essaysir essaysir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다. Redis 적용까지 잘하셨네요 대단 하십니다.

@PicturePark1101 PicturePark1101 merged commit 077c07c into dev Apr 23, 2025
1 check passed
PicturePark1101 added a commit that referenced this pull request Jun 1, 2025
[feat/#342] 공유임장 조회 구현
@essaysir essaysir deleted the feat/#342 branch September 10, 2025 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

진이 작업했습니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants