Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat/#215] article_view_his 테이블 추가 및 조회수 응답 추가 #216

Merged
merged 20 commits into from
Jul 19, 2024

Conversation

hun-ca
Copy link
Member

@hun-ca hun-ca commented Jul 17, 2024

🎫 연관 이슈

resolved #215

💁‍♂️ PR 내용

  • article_view_his 테이블 추가
  • 조회수 응답 추가 (기존 아티클 조회 API)
  • 각 모듈마다 있던 .gitignore 파일을 최상위 디렉토리의 .gitignore로 통합관리하도록 변경
  • 신규로 개발될 메인페이지 내 아티클 전체 조회 API와는 무관한 PR입니다(그걸 하기 위한 사전작업?)

🙏 작업

🙈 PR 참고 사항

📸 스크린샷

🤖 테스트 체크리스트

  • 체크 미완료
  • 체크 완료

@hun-ca hun-ca added feature 새로운 기능을 만들 때 사용됩니다 infra 인프라를 추가할 때 사용됩니다 labels Jul 17, 2024
@hun-ca hun-ca self-assigned this Jul 17, 2024
@hun-ca hun-ca requested a review from belljun3395 as a code owner July 17, 2024 14:48
Copy link
Member Author

@hun-ca hun-ca left a comment

Choose a reason for hiding this comment

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

리뷰 부탁드려요

Comment on lines +98 to +104
# DB schema migration path (except data module)
data/src/main/resources/**/*.sql
api/**/*.sql
api-repo/**/*.sql
batch/**/*.sql
email/**/*.sql
storage/**/*.sql
Copy link
Member Author

Choose a reason for hiding this comment

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

각 모듈별로 jooq 필드시 생성되는 .sql 파일이 gitignore되어있었는데, 최상위 위치에 통합관리하도록 변경했습니다

Comment on lines +14 to +23
fun insertArticleViewHis(command: ArticleViewHisCommand) {
dslContext.insertInto(
ArticleViewHis.ARTICLE_VIEW_HIS,
ArticleViewHis.ARTICLE_VIEW_HIS.ARTICLE_MST_ID,
ArticleViewHis.ARTICLE_VIEW_HIS.MEMBER_ID
).values(
command.articleId,
command.memberId
).execute()
}
Copy link
Member Author

Choose a reason for hiding this comment

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

article_view_his 테이블 insert. articleId에 인덱스 추가해야겠다...

Comment on lines 25 to 30
fun selectArticleViews(query: ArticleViewHisCountQuery): Long {
return dslContext.selectCount()
.from(ArticleViewHis.ARTICLE_VIEW_HIS)
.where(ArticleViewHis.ARTICLE_VIEW_HIS.ARTICLE_MST_ID.eq(query.articleId))
.fetchOne(0, Long::class.java) ?: 0L
}
Copy link
Member Author

Choose a reason for hiding this comment

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

아티클 별 view 개수 조회. 이거때문에 article id에 인덱스 해야겠네요 추가커밋 할게요

import org.jooq.DSLContext
import org.springframework.stereotype.Repository

@Repository
Copy link
Member Author

@hun-ca hun-ca Jul 17, 2024

Choose a reason for hiding this comment

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

종준님은 DAO에 @Repository 쓰셨던데 저는 @Component 썼거든요 뭐가 더 어울릴까요? 통일하는게 좋을거 같아서

Copy link
Collaborator

Choose a reason for hiding this comment

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

스크린샷 2024-07-18 오전 12 21 48 스크린샷 2024-07-18 오전 12 21 17 우선 블로그 글이긴 한데 저도 저렇게 알고 있어서 `@Repository`로 하는게 좋지 않을까 생각합니다..!

Comment on lines +2 to +10
CREATE TABLE ARTICLE_VIEW_HIS
(
id BIGINT NOT NULL AUTO_INCREMENT,
article_mst_id BIGINT NOT NULL,
member_id BIGINT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (id)
);
Copy link
Member Author

Choose a reason for hiding this comment

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

ARTICLE_VIEW_HIS 테이블 스키마

Comment on lines 12 to 13
-- [인덱스 추가] --
CREATE INDEX article_view_his_idx1 ON PROBLEM (article_mst_id);
Copy link
Member Author

Choose a reason for hiding this comment

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

article_mst_id 컬럼에 대한 인덱스 추가입니다. 조회수 가져오는 쿼리에서 쓰여요

Copy link
Member Author

Choose a reason for hiding this comment

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

테이블명 잘못되어서 수정함

).execute()
}

fun selectArticleViews(query: ArticleViewHisCountQuery): Long {
Copy link
Collaborator

Choose a reason for hiding this comment

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

스크린샷 2024-07-18 오후 1 11 46

저는 countXXX 형식으로 네이밍을 했는데
저희 요 부분도 통일하기 위한 논의가 필요할 것 같아요!

@@ -19,6 +22,7 @@ class ReadArticleUseCase(
private val articleDao: ArticleDao,
private val readArticleWriterRecordService: ReadArticleWriterRecordService,
private val browseArticleProblemsService: BrowseArticleProblemsService,
private val articleViewHisService: ArticleViewHisService,
Copy link
Collaborator

Choose a reason for hiding this comment

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

articleView 도 article 도메인에 포함된다고 생각해서 저는 ArticleViewDao를 바로 사용해도 괜찮지 않을까 생각하는데 어떻게 생각하시는지 궁금합니다

return dslContext.selectCount()
.from(ArticleViewHis.ARTICLE_VIEW_HIS)
.where(ArticleViewHis.ARTICLE_VIEW_HIS.ARTICLE_MST_ID.eq(query.articleId))
.fetchOne(0, Long::class.java) ?: 0L
Copy link
Collaborator

Choose a reason for hiding this comment

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

여기서는 Long? 타입으로 반환하고 ?: 0L 로 처리한 부분을 UC에서 하는게 어떨까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

로직은 최대한 UC로 뺴는게 좋겠네요

Comment on lines 42 to 43
articleViewHisService.addArticleViewHis(AddArticleViewHisInDto(useCaseIn.articleId, useCaseIn.memberId))
val views = articleViewHisService.readArticleViews(ReadArticleViewsInDto(useCaseIn.articleId))
Copy link
Collaborator

Choose a reason for hiding this comment

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

요거는 아이디어 차원의 이야기인데 아래 방법은 어떨지 궁금합니다.

기존:

  1. addArticleViewHis로 뷰 확인 기록을 추가한다.
  2. readArticleViews로 뷰 확인 기록을 조회한다.

제안:

  1. readArticleViews로 뷰 확인 기록을 조회한다.
  2. 조회한 값에 +1 하여 views를 반환한다.
  3. addArticleViewHis로 뷰 확인 기록을 추가는 별도의 스레드에서 비동기로 진행한다.

Copy link
Member Author

Choose a reason for hiding this comment

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

제안한 방식 나도 생각했던건데.. Insert는 테이블 락 + 인덱스 재구조화 때문에 항상 신중해야하는데, 안그래도 엄청 자주 호출될 SQL이라... 그게 좋은거 같아요

@@ -27,7 +27,7 @@ class ArticleController(
@Min(value = 1, message = "{min.id}")
articleId: Long,
): ApiResponse<ApiResponse.SuccessBody<ReadArticleResponse>> {
val useCaseOut = ReadArticleUseCaseIn(articleId).let { useCaseIn: ReadArticleUseCaseIn ->
val useCaseOut = ReadArticleUseCaseIn(articleId = articleId, memberId = 1L).let { useCaseIn: ReadArticleUseCaseIn ->
Copy link
Collaborator

Choose a reason for hiding this comment

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

요거는 관련 처리를 하지 않아서 1L로 한것이죠?
1L은 값이 있어서 0L로 하고 todo로 표시하는건 어떨까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

네네 유저관련 기능 개발해주시면서 해당 부분 변경 부탁드립니다! 일단 0으로 해둘게요

@github-actions github-actions bot added the config 설정 파일과 관련된 내용을 다룰 때 사용됩니다 label Jul 18, 2024
Copy link
Member Author

@hun-ca hun-ca left a comment

Choose a reason for hiding this comment

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

2차 리뷰 부탁드립니다

Comment on lines +10 to +37
@Configuration
class DatabaseAccessThreadPoolConfig {
private val log = KotlinLogging.logger {}

companion object {
const val DATABASE_ACCESS_POOL = "database-task-"
}

@Bean
@ConfigurationProperties(prefix = "database.thread-pool")
fun databaseAccessThreadPoolProperties(): ThreadPoolProperties {
return ThreadPoolProperties()
}

@Bean(DATABASE_ACCESS_POOL)
fun databaseAccessThreadPool() = ThreadPoolTaskExecutor().apply {
val properties = databaseAccessThreadPoolProperties()
corePoolSize = properties.getCorePoolSize()
maxPoolSize = properties.getMaxPoolSize()
queueCapacity = properties.getQueueCapacity()
setWaitForTasksToCompleteOnShutdown(properties.getWaitForTasksToCompleteOnShutdown())
setAwaitTerminationSeconds(properties.getAwaitTerminationSeconds())
setThreadNamePrefix("databaseAccessThreadPool-")
setRejectedExecutionHandler { r, _ ->
log.warn { "Database Access Task Rejected: $r" }
}
initialize()
}
Copy link
Member Author

Choose a reason for hiding this comment

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

디코 훅 쓰레드 풀이랑 똑같이 디비 접근용 쓰레드 풀 생성. 추후 이걸 활용할 것들이 많아보여서 네이밍을 좀 일반적이게 했어요(조회수랑 관련없이)

browseArticleProblemsService.execute(query)
}

val views = (articleViewHisDao.countArticleViews(ArticleViewHisCountQuery(useCaseIn.articleId)) ?: 0L) + 1L
Copy link
Member Author

Choose a reason for hiding this comment

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

조회수 가져온 뒤에 우선 + 1 하도록 변경

Comment on lines 68 to 77
@Async(value = DATABASE_ACCESS_POOL)
@Transactional
fun insertArticleViewHisAsync(articleId: Long, memberId: Long) {
try {
articleViewHisDao.insertArticleViewHis(ArticleViewHisCommand(articleId, memberId))
log.debug { "Successfully inserted article view history for articleId: $articleId and memberId: $memberId" }
} catch (e: Exception) {
log.error { "Failed to insert article view history for articleId: $articleId and memberId: $memberId" }
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Insert 는 DATABASE_ACCESS_POOL 풀에서 수행하도록 변경. 어차피 다른 쓰레드에서 수행되기 때문에 기존 아티클을 조회하던 쓰레드와 트랜잭션 컨텍스트를 공유하지 않고, 따라서 트랜잭션 전파 레벨 지정은 필요 없음. (전파 자체가 안됨, new transaction)

Copy link
Member Author

Choose a reason for hiding this comment

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

해당 로직은 일단 UC 내부에 넣어뒀는데 서비스로 뺴기도 애매한게, 같은 도메인이여서,, 좋은 생각있음 공유좀요

Copy link
Collaborator

@belljun3395 belljun3395 Jul 18, 2024

Choose a reason for hiding this comment

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

해당 로직은 일단 UC 내부에 넣어뒀는데 서비스로 뺴기도 애매한게, 같은 도메인이여서,, 좋은 생각있음 공유좀요

스크린샷 2024-07-19 오전 12 24 33

저는 요렇게 했습니다.

UC 내부에 넣어뒀는데

아 추가로 같은 클래스내의 메서드는 비동기 처리가 가능하지 않으니 무조건 빼야하긴 하겠네요!

Copy link
Member Author

Choose a reason for hiding this comment

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

일단 handler로 함...

Copy link
Collaborator

@belljun3395 belljun3395 Jul 19, 2024

Choose a reason for hiding this comment

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

handler 가 안보이는데 혹시 어디 있을까요?
아아 죄송합니다..
확인했어요!

Comment on lines +17 to +23
database:
thread-pool:
core-pool-size: 10
max-pool-size: 30
queue-capacity: 70
wait-for-tasks-to-complete-on-shutdown: true
await-termination-seconds: 60
Copy link
Member Author

Choose a reason for hiding this comment

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

이건 VM 환경변수로 뺼까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

이거 설정하면서 든 생각이, 만약 조회가 엄청 많이 발생하는 상황에선 max 개수인 30 + queue 대기 70 해서 100 명 까지만 Insert가 될 거 같음...

Copy link
Collaborator

Choose a reason for hiding this comment

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

그럼 databaseAccessThreadPool의 setRejectedExecutionHandler 부분을 수정하면 되지 않을까요?
제가 디스코드에 해둔 것은 단순히 실패 로그를 남기는 것 뿐인데 다른 기본 정책들도 있더라고요
예를들어 CallerRunsPolicy 요런 정책이 있습니다.

@hun-ca hun-ca requested a review from belljun3395 July 18, 2024 12:46
@belljun3395
Copy link
Collaborator

추가로 액션이 성공 못하는 것은 app 모듈의 test의 application-test.yml에 현 PR에서 추가된 설정들이 추가되지 않아서 그런 것 같아요!

@belljun3395
Copy link
Collaborator

@hun-ca 테스트 통과합니다~

@hun-ca hun-ca merged commit 7ffe45c into main Jul 19, 2024
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
config 설정 파일과 관련된 내용을 다룰 때 사용됩니다 feature 새로운 기능을 만들 때 사용됩니다 infra 인프라를 추가할 때 사용됩니다
Projects
None yet
Development

Successfully merging this pull request may close these issues.

아티클 조회수 관련 기능 설계/개발
2 participants