Skip to content

[Feature] DOMjudge 채점 폴링 성능 베이스라인 측정 도구 적용 #121

@Diggydogg

Description

@Diggydogg

배경

현재 SubmissionService의 DOMjudge 폴링 로직에 대한 최적화를 계획 중이며,
개선 효과를 정량적으로 증명하기 위해 현재 상태의 베이스라인 데이터를 먼저 수집해야 합니다.

현재 코드에는 성능 측정 로직이 전혀 없으며, System.out.println으로 폴링 시도 횟수만 출력하고 있습니다.

목표

개선 전/후 비교를 위한 성능 데이터 수집 인프라 구축

수집 대상 지표

시간 지표

지표 설명
E2E 응답 시간 프론트 요청 → 응답 수신까지 총 시간
DOMjudge 제출 시간 submitCode() API 호출 소요 시간
채점 대기 시간 폴링 시작 → 결과 수신까지 시간
DOMjudge API 응답 시간 개별 getResult() / getResultOutput() 호출 소요 시간

횟수/자원 지표

지표 설명
폴링 횟수 결과를 받기까지 DOMjudge에 보낸 요청 수
타임아웃 발생 횟수 제한 시간 내 결과를 못 받은 건수
에러 발생 횟수 DOMjudge 통신 실패 건수

분포 통계 (수집 후 SQL/Locust 분석)

  • P50 / P90 / P95 / P99 응답 시간
  • 언어별 채점 시간 분포
  • 시간대별 제출량 및 응답 시간 변화
  • 동시 사용자 수 증가에 따른 degradation 곡선

구현 작업

1. SubmissionMetric 테이블 생성 (별도 엔티티)

  • SubmissionMetric 엔티티 생성 (submission_metrics 테이블)
  • SubmissionMetricRepository 생성
  • 비즈니스 로직(Submission)과 측정 로직을 분리하여, 측정 코드 제거 시 스키마 영향 없도록 함
@Entity
@Table(name = "submission_metrics")
public class SubmissionMetric {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToOne
    @JoinColumn(name = "submission_id")
    private Submission submission;

    private String domjudgeSubmissionId;
    private Long submitDurationMs;      // DOMjudge 제출 API 소요시간
    private Long judgingDurationMs;     // 채점 대기 시간
    private Long e2eDurationMs;        // 전체 E2E 시간
    private Integer pollingAttempts;    // 폴링 시도 횟수
    private String language;            // 비정규화 (JOIN 없이 통계 가능)
    private Long problemId;            // 비정규화
    private Long sectionId;            // 비정규화
    private Boolean timedOut;           // 타임아웃 여부
    private String errorMessage;        // 에러 발생 시 메시지
    private LocalDateTime measuredAt;
}

2. SubmissionService 구간별 시간 계측 로그 추가

  • submitAndGetResult() E2E 시간 측정
  • domjudgeService.submitCode() 제출 소요시간 측정
  • pollForResult() / pollForResultOutput() 채점 대기 시간 + 폴링 횟수 측정
  • 개별 getResult() / getResultOutput() API 응답 시간 로그 (log.debug)
  • System.out.printlnlog.warn / log.debug 전환
  • 측정 완료 시 SubmissionMetric DB 저장
  • [METRIC] 접두사로 grep 가능한 구조화 로그 적용

3. Locust 부하 테스트 시나리오 작성

  • Locust 테스트 환경 구성 (pip install locust, locustfile.py)
  • JWT 인증 처리 (on_start에서 토큰 획득)
  • 시나리오 1: 단일 제출 (1 user) — 순수 응답시간 베이스라인
  • 시나리오 2: 일반 상황 (10 users) — 평소 수업 중
  • 시나리오 3: 피크 (30~50 users) — 과제 마감 직전 / 코딩 테스트
  • 시나리오 4: 스트레스 (100 users) — 쓰레드 풀 한계점 확인
  • 결과 CSV export + 웹 UI 스크린샷 저장

Locust 선정 이유

  • gevent 코루틴 기반 → 30초 블로킹 폴링 요청을 경량 처리 (JMeter의 thread-per-user 대비 유리)
  • 실시간 웹 UI (localhost:8089)로 RPS, 응답시간 분포, 에러율 그래프 즉시 확인
  • Python 스크립트 → git 버전관리 용이, 복잡한 시나리오(JWT 갱신 등) 작성 자유로움
  • pip install locust 한 줄 설치, 100 VU 규모는 단일 머신으로 충분

분석 쿼리 예시

-- 언어별 응답시간 분포
SELECT 
    language,
    COUNT(*) AS total,
    AVG(e2e_duration_ms) AS avg_ms,
    AVG(polling_attempts) AS avg_polls,
    SUM(CASE WHEN timed_out = true THEN 1 ELSE 0 END) AS timeout_count
FROM submission_metrics
GROUP BY language;

-- 개선 전/후 비교 (개선 적용 후 동일 쿼리에 phase 컬럼 추가)
SELECT 
    CASE WHEN measured_at < '2026-03-20' THEN 'before' ELSE 'after' END AS phase,
    language,
    COUNT(*) AS total,
    AVG(e2e_duration_ms) AS avg_ms,
    AVG(polling_attempts) AS avg_polls
FROM submission_metrics
GROUP BY phase, language;

영향 범위

  • 신규: SubmissionMetric 엔티티, SubmissionMetricRepository, locustfile.py
  • 수정: SubmissionService.java — 측정 로직 추가 (비즈니스 로직 변경 없음)

비고

Metadata

Metadata

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions