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

fix: abtest 데드락 이슈 발견/해결 #1075

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from

Conversation

Choi-JJunho
Copy link
Member

🔥 연관 이슈

🚀 작업 내용

  1. 데드락이 발생했는데 로직의 흐름을 파악하기 어려워 코드 작성자의 리뷰를 받으면서 수정하고자합니다.

💬 리뷰 중점사항

abtest의 로직 흐름이 이해가 가지 않아 중복되지 않는데 메소드로 분리되어있는 내용들을 다 하나로 합쳐놨습니다.

로직의 흐름에 대해 설명해주실 수 있나요?

@Choi-JJunho Choi-JJunho added the 버그 정상적으로 동작하지 않는 문제상황입니다. label Nov 24, 2024
@Choi-JJunho Choi-JJunho self-assigned this Nov 24, 2024
Copy link

Unit Test Results

340 tests   338 ✔️  1m 35s ⏱️
  37 suites      1 💤
  37 files        1

For more details on these failures, see this check.

Results for commit 902ad6c.

Copy link
Collaborator

@songsunkook songsunkook left a comment

Choose a reason for hiding this comment

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

주말인데도 제 코드에서 발생한 문제를 해결하고자 소중한 시간 써주셔서 감사합니다.. 😭🙇‍♂️

중복되지 않는데 메소드로 분리한 이유는 로직만 나열되어있으면 가독성이 떨어져서 이를 위해 진행했었습니다.
도움이 될지 모르겠지만 #843 에 간단하게나마 로직 설명을 기록해두었습니다.

Comment on lines +145 to +146
// 이 값은 어디에쓰이는지?
UserAgentInfo userAgentInfo,
Copy link
Collaborator

Choose a reason for hiding this comment

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

기기 정보 저장에 사용됩니다.
Device 엔티티의 model 및 type에 사용자의 기기 정보를 저장합니다.

저장 목적: 어드민 페이지에서의 사용자 실험군 수동 편입 시 기기 특정을 위해 기기 정보 제공

Comment on lines +179 to +198
// -- syncCacheCount 시작
// abTest 변수들 중 Redis에 올라와있는 변수를 가져온다.
List<AbtestVariableCount> cachedCount = abtest.getAbtestVariables().stream()
.map(AbtestVariable::getId)
.filter(Objects::nonNull)
.map(abtestVariableCountRedisRepository::findById)
.filter(Optional::isPresent)
.map(Optional::get)
.toList();

// Redis에 캐싱되어있는 count 값을 RDB로 동기화시킨다.
for (AbtestVariableCount abtestVariableCount : cachedCount) {
// RDB에서 abtestVariable 값을 영속성 컨텍스트에 올린다. (Dirty Checking 대상으로 등록)
AbtestVariable variable = abtestVariableRepository.getById(abtestVariableCount.getVariableId());
variable.addCount(abtestVariableCount.getCount()); // RDB count update
abtestVariableCount.resetCount(); // Redis reset
}
// reset된 값들을 반영
abtestVariableCountRedisRepository.saveAll(cachedCount);
// -- syncCacheCount 끝
Copy link
Collaborator

Choose a reason for hiding this comment

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

A

이 로직(syncCacheCount)이 사용자 편입부분에서 직접 포함되면 안되는데 제가 왜 넣었는지 잘 모르겠네요..
이렇게 사용하면 Redis를 활용한 이유가 없을텐데... 😥

+) syncCacheCount는 다른 메서드에서도 사용중인 로직인데 여기에 원본을 복사해 넣은 이유는 리뷰 간 가독성 향상을 위해서인가요..??

Comment on lines +201 to +206
TODO : 해당 부분 설명 필요
조건문 진입 시점에서 유저아이디가 Null이 아님을 보장하고있는데 이 로직이 존재하는 이유는?
기존
if (userId != null) {
createDeviceIfNotExists(userId, userAgentInfo, accessHistory, abtest);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

userId != null 조건이 기입된 이유는 기존 로직 상 조건문 진입 시점에서 유저아이디가 Null이 아님을 보장하지 못하고 있기 때문입니다.

기존 로직 코드는 아래와 같습니다.

    private boolean isAssignedUser(Abtest abtest, Integer accessHistoryId, Integer userId) {
        AccessHistory accessHistory = accessHistoryRepository.getById(accessHistoryId);
        if (userId != null && accessHistory.getDevice() != null
            && !Objects.equals(accessHistory.getDevice().getUser().getId(), userId)) {
            return false;
        }
        return abtest.getAbtestVariables().stream()
            .anyMatch(abtestVariable -> accessHistory.hasVariable(abtestVariable.getId()));
    }

여기서 false가 반환되는 경우 기존에 편입되지 않은 사용자로 판단하고 이후 로직을 진행합니다.
수정해주신 로직에서는 첫 조건문으로 전체 로직을 감싸주셨지만 첫 조건문이 만족하지 못하더라도(비로그인 사용자라도) false가 반환되는 경우(accessHistory가 대상 abtest에 아직 속하지 않은 경우)는 존재합니다.

이 케이스에서는 비로그인 사용자도 여기까지 넘어올 수 있기 때문에 한번 더 검증해주었던 걸로 기억하고 있습니다.

+) createDevice를 여기서 진행하는 이유는 기존에는 비로그인으로 사용중이었던 기기가 로그인으로 바뀔 경우 기기 정보를 기록하기 위해서였던 걸로 기억하고 있습니다.

Comment on lines +265 to +268
// TODO: 설명 필요
// if (userId != null) {
// createDeviceIfNotExists(userId, userAgentInfo, accessHistory, abtest);
// }
Copy link
Collaborator

Choose a reason for hiding this comment

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

여기(assignOrGetVariable)서도 이 메서드를 호출하는 이유는 getMyVariable을 호출하지 않는 경우(새로 편입되는 accessHistory의 경우)에도 디바이스 생성 로직이 필요하기 때문입니다.

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.

데드락 이슈를 해결한다
2 participants