From 8302790fcc20c2b77d25ab17046ed1a410ca04f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=B0=ED=98=84=EC=84=9C?= Date: Mon, 7 Oct 2024 16:48:30 +0900 Subject: [PATCH] =?UTF-8?q?[hotfix]:=20=EA=B8=B4=EA=B8=89=20=EA=B3=B5?= =?UTF-8?q?=EC=A7=80=20=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/jpa/acl/entity/QBoardAclEntity.java | 59 ------------------- .../infra/jpa/acl/entity/QGlobalEntity.java | 45 -------------- .../csv_user/entity/QStudentCsvEntity.java | 6 +- .../homepage/domain/post/PostRepository.java | 1 + .../post/service/PostStatusProcessor.java | 55 +++++++++++++++++ .../infra/jpa/post/PostRepositoryImpl.java | 45 ++++++++++++++ .../infra/jpa/post/entity/PostEntity.java | 3 + 7 files changed, 106 insertions(+), 108 deletions(-) delete mode 100644 src/main/generated/ussum/homepage/infra/jpa/acl/entity/QBoardAclEntity.java delete mode 100644 src/main/generated/ussum/homepage/infra/jpa/acl/entity/QGlobalEntity.java diff --git a/src/main/generated/ussum/homepage/infra/jpa/acl/entity/QBoardAclEntity.java b/src/main/generated/ussum/homepage/infra/jpa/acl/entity/QBoardAclEntity.java deleted file mode 100644 index 5e7ac248..00000000 --- a/src/main/generated/ussum/homepage/infra/jpa/acl/entity/QBoardAclEntity.java +++ /dev/null @@ -1,59 +0,0 @@ -package ussum.homepage.infra.jpa.acl.entity; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.PathInits; - - -/** - * QBoardAclEntity is a Querydsl query type for BoardAclEntity - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QBoardAclEntity extends EntityPathBase { - - private static final long serialVersionUID = 1242137438L; - - private static final PathInits INITS = PathInits.DIRECT2; - - public static final QBoardAclEntity boardAclEntity = new QBoardAclEntity("boardAclEntity"); - - public final EnumPath action = createEnum("action", Action.class); - - public final ussum.homepage.infra.jpa.post.entity.QBoardEntity boardEntity; - - public final NumberPath id = createNumber("id", Long.class); - - public final EnumPath orderType = createEnum("orderType", OrderType.class); - - public final EnumPath target = createEnum("target", Target.class); - - public final EnumPath type = createEnum("type", Type.class); - - public QBoardAclEntity(String variable) { - this(BoardAclEntity.class, forVariable(variable), INITS); - } - - public QBoardAclEntity(Path path) { - this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); - } - - public QBoardAclEntity(PathMetadata metadata) { - this(metadata, PathInits.getFor(metadata, INITS)); - } - - public QBoardAclEntity(PathMetadata metadata, PathInits inits) { - this(BoardAclEntity.class, metadata, inits); - } - - public QBoardAclEntity(Class type, PathMetadata metadata, PathInits inits) { - super(type, metadata, inits); - this.boardEntity = inits.isInitialized("boardEntity") ? new ussum.homepage.infra.jpa.post.entity.QBoardEntity(forProperty("boardEntity")) : null; - } - -} - diff --git a/src/main/generated/ussum/homepage/infra/jpa/acl/entity/QGlobalEntity.java b/src/main/generated/ussum/homepage/infra/jpa/acl/entity/QGlobalEntity.java deleted file mode 100644 index e1179b2f..00000000 --- a/src/main/generated/ussum/homepage/infra/jpa/acl/entity/QGlobalEntity.java +++ /dev/null @@ -1,45 +0,0 @@ -package ussum.homepage.infra.jpa.acl.entity; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; - - -/** - * QGlobalEntity is a Querydsl query type for GlobalEntity - */ -@Generated("com.querydsl.codegen.DefaultEntitySerializer") -public class QGlobalEntity extends EntityPathBase { - - private static final long serialVersionUID = -1896694339L; - - public static final QGlobalEntity globalEntity = new QGlobalEntity("globalEntity"); - - public final EnumPath action = createEnum("action", Action.class); - - public final NumberPath id = createNumber("id", Long.class); - - public final EnumPath orderType = createEnum("orderType", OrderType.class); - - public final EnumPath target = createEnum("target", Target.class); - - public final EnumPath type = createEnum("type", Type.class); - - public QGlobalEntity(String variable) { - super(GlobalEntity.class, forVariable(variable)); - } - - public QGlobalEntity(Path path) { - super(path.getType(), path.getMetadata()); - } - - public QGlobalEntity(PathMetadata metadata) { - super(GlobalEntity.class, metadata); - } - -} - diff --git a/src/main/generated/ussum/homepage/infra/jpa/csv_user/entity/QStudentCsvEntity.java b/src/main/generated/ussum/homepage/infra/jpa/csv_user/entity/QStudentCsvEntity.java index e301ce57..fa6e6ca1 100644 --- a/src/main/generated/ussum/homepage/infra/jpa/csv_user/entity/QStudentCsvEntity.java +++ b/src/main/generated/ussum/homepage/infra/jpa/csv_user/entity/QStudentCsvEntity.java @@ -23,11 +23,9 @@ public class QStudentCsvEntity extends EntityPathBase { public final StringPath major = createString("major"); - public final NumberPath STID = createNumber("STID", Long.class); + public final StringPath program = createString("program"); - public final StringPath studentEmail = createString("studentEmail"); - - public final StringPath studentGroup = createString("studentGroup"); + public final StringPath specificMajor = createString("specificMajor"); public final NumberPath studentId = createNumber("studentId", Long.class); diff --git a/src/main/java/ussum/homepage/domain/post/PostRepository.java b/src/main/java/ussum/homepage/domain/post/PostRepository.java index ff4dd78b..c66e41c1 100644 --- a/src/main/java/ussum/homepage/domain/post/PostRepository.java +++ b/src/main/java/ussum/homepage/domain/post/PostRepository.java @@ -20,6 +20,7 @@ public interface PostRepository { Page findAllByBoardIdAndCategory(Long boardId, Category category, Pageable pageable); Post save(Post post); void updatePostStatusNewToGeneral(LocalDateTime dueDateOfNew); + void updatePostStatusEmergencyToGeneralInBatches(); void delete(Post post); Page findBySearchCriteria(Pageable pageable,String boardCode, String q, String categoryCode); Page findPostDtoListByBoardCode(String boardCode, Pageable pageable); diff --git a/src/main/java/ussum/homepage/domain/post/service/PostStatusProcessor.java b/src/main/java/ussum/homepage/domain/post/service/PostStatusProcessor.java index 9391f9b2..0bf56139 100644 --- a/src/main/java/ussum/homepage/domain/post/service/PostStatusProcessor.java +++ b/src/main/java/ussum/homepage/domain/post/service/PostStatusProcessor.java @@ -5,8 +5,10 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ussum.homepage.domain.post.PostRepository; +import ussum.homepage.infra.jpa.post.entity.PostEntity; import java.time.LocalDateTime; +import java.util.List; @Service @RequiredArgsConstructor @@ -19,4 +21,57 @@ public void updatePostStatusNewToGeneral() { LocalDateTime dueDateOfNew = LocalDateTime.now().minusDays(3); postRepository.updatePostStatusNewToGeneral(dueDateOfNew); } + + /** + 메모리 효율성: + + IN 쿼리 방식: 모든 대상 엔티티를 한 번에 메모리에 로드하므로 대량의 데이터 처리 시 OutOfMemoryError 발생 가능성이 있습니다. + 단일 UPDATE 방식: 서버 측 메모리 사용은 적지만, DB 서버에서 큰 부하가 발생할 수 있습니다. + 배치 처리 방식: 한 번에 제한된 수의 레코드만 처리하므로 메모리 사용을 효과적으로 제어할 수 있습니다. + + + 데이터베이스 부하: + + IN 쿼리 방식: 많은 데이터를 한 번에 조회하므로 DB에 순간적으로 큰 부하를 줄 수 있습니다. + 단일 UPDATE 방식: 대량의 레코드를 한 번에 업데이트하여 DB에 극심한 부하를 줄 수 있습니다. + 배치 처리 방식: 작업을 작은 단위로 나누어 실행하므로 DB 부하를 분산시킬 수 있습니다. + + + 트랜잭션 관리: + + IN 쿼리 방식: 모든 업데이트가 하나의 큰 트랜잭션에서 이루어져, 롤백 시 모든 변경사항이 취소됩니다. + 단일 UPDATE 방식: 하나의 대규모 트랜잭션으로 인해 롤백 시 많은 시간이 소요될 수 있습니다. + 배치 처리 방식: 작은 트랜잭션들로 나누어 처리하므로, 문제 발생 시 해당 배치만 롤백되어 리스크를 줄일 수 있습니다. + + + 락(Lock) 관리: + + IN 쿼리 방식: 많은 레코드를 동시에 락을 걸 수 있어 다른 트랜잭션을 차단할 수 있습니다. + 단일 UPDATE 방식: 대량의 레코드에 대해 동시에 락을 획득하여 다른 작업을 오랫동안 차단할 수 있습니다. + 배치 처리 방식: 소수의 레코드만 짧은 시간 동안 락을 걸기 때문에 다른 트랜잭션과의 충돌 가능성이 낮습니다. + + + 에러 처리와 재시도: + + IN 쿼리 방식: 전체 프로세스 중 오류 발생 시 어떤 레코드가 처리되었는지 파악하기 어렵습니다. + 단일 UPDATE 방식: 오류 발생 시 전체 작업이 실패하며, 부분적 성공을 처리하기 어렵습니다. + 배치 처리 방식: 각 배치별로 오류를 독립적으로 처리할 수 있어, 실패한 배치만 재시도할 수 있습니다. + + + 진행 상황 모니터링: + + IN 쿼리와 단일 UPDATE 방식: 전체 작업의 진행 상황을 중간에 확인하기 어렵습니다. + 배치 처리 방식: 각 배치마다 처리된 레코드 수를 확인할 수 있어 진행 상황을 실시간으로 모니터링할 수 있습니다. + + + 시스템 리소스 활용: + + 배치 처리 방식은 시스템 리소스를 더 균형있게 사용할 수 있게 해주며, 다른 작업과의 리소스 경쟁을 줄일 수 있습니다. + **/ + @Scheduled(cron = "0 0 0 * * ?") + @Transactional + public void updatePostStatusToGeneral() { + postRepository.updatePostStatusEmergencyToGeneralInBatches(); + System.out.println("게시물 상태 업데이트 작업이 실행되었습니다. - " + LocalDateTime.now()); + } } diff --git a/src/main/java/ussum/homepage/infra/jpa/post/PostRepositoryImpl.java b/src/main/java/ussum/homepage/infra/jpa/post/PostRepositoryImpl.java index e81b08e8..2bb244d1 100644 --- a/src/main/java/ussum/homepage/infra/jpa/post/PostRepositoryImpl.java +++ b/src/main/java/ussum/homepage/infra/jpa/post/PostRepositoryImpl.java @@ -7,6 +7,7 @@ import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -69,6 +70,7 @@ public class PostRepositoryImpl implements PostRepository { private final JPAQueryFactory queryFactory; private final PostReplyCommentJpaRepository postReplyCommentJpaRepository; private final PostCommentJpaRepository postCommentJpaRepository; + private final EntityManager entityManager; @Override public Optional findById(Long postId) { @@ -570,4 +572,47 @@ public void updatePostStatusNewToGeneral(LocalDateTime dueDateForNewStatus) { .and(postEntity.createdAt.loe(dueDateForNewStatus))) .execute(); } + + @Override + public void updatePostStatusEmergencyToGeneralInBatches() { + // 3일 전 날짜 계산 + LocalDateTime threeDaysAgo = LocalDateTime.now().minusDays(3); + // 한 번에 처리할 게시물 수 설정 + int batchSize = 500; + // 총 처리된 게시물 수를 추적하기 위한 변수 + long processedCount = 0; + + while (true) { + // 배치 크기만큼의 게시물 ID를 가져옴 + List batchIds = queryFactory + .select(postEntity.id) + .from(postEntity) + .where(postEntity.boardEntity.id.eq(2L) // 공지사항 게시판 ID가 1이라고 가정 + .and(postEntity.status.ne(Status.GENERAL)) // 상태가 GENERAL이 아닌 것 + .and(postEntity.createdAt.before(threeDaysAgo))) // 3일 이전에 생성된 것 + .limit(batchSize) + .fetch(); + + // 더 이상 처리할 게시물이 없으면 반복 종료 + if (batchIds.isEmpty()) { + break; + } + + // 가져온 ID에 해당하는 게시물들의 상태를 GENERAL로 업데이트 + long updatedCount = queryFactory + .update(postEntity) + .set(postEntity.status, Status.GENERAL) + .where(postEntity.id.in(batchIds)) + .execute(); + + // 처리된 게시물 수 누적 + processedCount += updatedCount; + + // 영속성 컨텍스트 초기화 (메모리 관리를 위해) + entityManager.clear(); + } + + // 총 처리된 게시물 수 출력 + System.out.println("총 업데이트된 게시물 수: " + processedCount); + } } diff --git a/src/main/java/ussum/homepage/infra/jpa/post/entity/PostEntity.java b/src/main/java/ussum/homepage/infra/jpa/post/entity/PostEntity.java index 7cbee634..87cfe4d4 100644 --- a/src/main/java/ussum/homepage/infra/jpa/post/entity/PostEntity.java +++ b/src/main/java/ussum/homepage/infra/jpa/post/entity/PostEntity.java @@ -61,4 +61,7 @@ public void updateCategory(Category newCategory) { this.category = newCategory; // this.ongoingStatus = newStatus; } + public void updateStatus(Status newStatus) { + this.status = newStatus; + } }