오프라인 구성원 때문에 그룹 시험이 시작되지 않는 문제

이 글의 목차 8개 항목

문제와 원인

대표와 동료가 모두 접속해 준비를 마쳐도 오프라인 구성원 1명 때문에 그룹 시험을 시작할 수 없었다. 개별 ready는 오프라인일 때 false인데, 최종 allReady가 전체 구성원의 준비 상태를 검사해 접속자들의 준비 완료까지 막았다.

수정 전 코드

GroupExamReadinessRepository.fromSnapshotRows

const publicMembers = roster.map(m => ({
  membership_id: m.public_id,
  public_name: m.public_name,
  is_owner: m.is_owner,
  is_self: m.is_self,
  membership_epoch: m.membership_epoch,
  online: m.online,
  presence_state: m.online ? "online" : "offline",
  ready: Boolean(
    m.ready && m.context_digest === contextVersion &&
    m.online && m.prepared_run_id === null
  ),
}));

allReady: publicMembers.length > 0 && publicMembers.every(m => m.ready)

GroupExamReadinessRepository.startGuard

async startGuard(group:Configuration,members:MemberRow[],now:Date) {
    const context=await readinessContext(group,members);const config=readinessConfigurationGuard(group,members);
    return {sql:`${config.sql} AND NOT EXISTS(SELECT 1 FROM study_group_members m
      WHERE m.group_id=? AND m.status='active' AND NOT EXISTS(SELECT 1 FROM study_group_readiness r
        WHERE r.group_id=m.group_id AND r.user_key=m.user_key AND r.membership_epoch=m.membership_epoch AND r.ready=1 AND r.prepared_run_id IS NULL AND r.context_digest=?))
      AND NOT EXISTS(SELECT 1 FROM study_group_members m WHERE m.group_id=? AND m.status='active'
        AND NOT EXISTS(SELECT 1 FROM study_group_presence_sessions p WHERE p.group_id=m.group_id AND p.user_key=m.user_key AND p.visible=1 AND p.last_seen_at>=?))`,
      values:[...config.values,group.id,context,group.id,new Date(now.getTime()-60_000).toISOString()]};
  }

수정 후 코드

GroupExamReadinessRepository.fromSnapshotRows

onlineRoster와 onlineMembers를 분리하고, 접속자 1명 이상·대표 접속·접속자 전원 준비를 요구하도록 바꿨다. 개별 준비의 맥락 일치와 prepared_run_id 조건은 유지한다.

const onlineRoster = roster.filter(m => m.online);
const onlineMembers = publicMembers.filter(m => m.online);

allReady: onlineMembers.length > 0 &&
  onlineMembers.some(m => m.is_owner) &&
  onlineMembers.every(m => m.ready)

GroupExamReadinessRepository.startGuard

시작 직전 startGuard에는 참여자의 사용자 키·가입 세대를 고정한 목록을 전달한다. DB에서 대표 포함 여부, 접속자 수, 준비 상태, 가입 세대와 60,000ms(60초) 접속 기준을 다시 검사한다.

const expected = JSON.stringify(participants.map(m => ({
  key: m.user_key,
  epoch: m.membership_epoch,
})));
const cutoff = new Date(now.getTime() - 60_000).toISOString();

선택 중 구성원의 접속·이탈로 DB 참여 조건이 달라지면 시험 시작을 다시 시도하도록 처리한다.

검증 결과

Swagger UI에서 수정 전후 원본 fromSnapshotRows를 다시 실행했다. DB 조회 행 형태의 합성 입력 5개를 그대로 사용했다.

입력 조건수정 전 allReady수정 후 allReady
대표 준비·동료 오프라인falsetrue
접속 2명 준비·1명 오프라인falsetrue
접속 동료 미준비falsefalse
대표 오프라인falsefalse
접속자 없음falsefalse
원본 준비 상태 함수 재실행: 오프라인 차단 2/2→0/2, 기존 제한 3/3 유지원본 보기 (새 창)

원본 준비 상태 함수 재실행: 오프라인 차단 2/2→0/2, 기존 제한 3/3 유지

100%원본 보기 (새 창)

확대한 이미지는 상하좌우로 이동할 수 있습니다. 키보드는 이미지 영역에 초점을 두고 방향키를 사용하세요. Esc로 닫습니다.

오프라인 구성원이 접속자 준비 완료를 막던 조건은 2/2개에서 0/2개로 줄었다. 미준비 접속자·대표 오프라인·접속자 없음의 시작 제한은 3/3개 유지됐다.