백로그

1
높음 b973f9ea

Cloudflare CDN 설정

## 문제 정적 자산(JS, CSS, 이미지)이 Rails 서버에서 직접 서빙. 모든 요청이 서버에 도달. ## 변경 (코드 외 인프라 작업) 1. Cloudflare에 9way.org 도메인 등록 (무료 플랜) 2. DNS를 Cloudflare 네임서버로 변경 3. SSL: Full(Strict) 모드 설정 (Kamal Proxy Let's Encrypt와 공존) 4. 캐싱 규칙: `/assets/*` → Cache Everything, Edge TTL 30일 5. Page Rules: `/up` → Bypass Cache (health check) ## 리스크 - Cloudflare + Kamal Proxy 이중 SSL → Full(Strict) 모드 필수 (리다이렉트 루프 방지) ## 완료 기준 - [ ] 정적 자산 CDN 서빙 (cf-cache-status: HIT) - [ ] SSL 정상 작동 - [ ] 진단 플로우 정상 동작

미배정 2 days

할 일

0
티켓 없음

진행 중

0
티켓 없음

리뷰

0
티켓 없음

완료 (7일)

15
높음 4b9177ac

Big5 성격 진단 기능 구현

Big5 성격 진단 전체 구현: 마이그레이션+모델+시드, 점수계산, 컨트롤러+라우트, 뷰+Stimulus, 결과 포매팅+결과뷰, i18n

C
claude
1 day
긴급 c37f11a4
부모 티켓

Puma 2워커/5스레드 튜닝

## 목표 1000명 동시 진단 대응을 위해 Puma 동시 처리 능력을 3 → 10으로 향상. ## 변경 파일 - `config/deploy.yml` — `WEB_CONCURRENCY: 2`, `RAILS_MAX_THREADS: 5` 환경변수 추가 - `config/puma.rb` — `workers ENV.fetch("WEB_CONCURRENCY", 0)` + `preload_app!` 추가 ## 완료 기준 - 배포 후 Puma 2워커 기동 확인 (`ps aux | grep puma`) - 메모리 2GB 이내 확인 (`free -m`) - 헬스체크 정상 ## 주의사항 - **절대 3워커 이상 금지** — 4GB 서버에서 PDF 생성(Chromium) 시 OOM 위험 - DB 풀은 RAILS_MAX_THREADS 참조로 자동 반영 (워커당 5개) - PostgreSQL max_connections(100) 내 여유 확인: 2워커 × 5스레드 × 4DB = 최대 40 커넥션

1/1
팀리드
5 days
긴급 b313d9d8
서브 티켓 Puma 2워커/5스레드 튜닝

Puma 워커/스레드 + DB 풀 설정

config/deploy.yml에 WEB_CONCURRENCY: 2, RAILS_MAX_THREADS: 5 추가. config/puma.rb에 workers + preload_app! 추가. 테스트 통과 확인.

I
infra-dev
5 days
긴급 7f1b04ff
서브 티켓 Big5 응답 일괄 제출 (레거시 패턴)

Server: 일괄 제출 API + upsert_all + N+1 제거

## 목표 Big5 일괄 제출을 위한 서버 API 구현 + 기존 코드 N+1 제거. ## ⚠️ 리뷰 반려 사유 (2026-03-09) ### [CRITICAL] r[:selected] 타입 안전성 (controller:110) - `r[:selected] ? 1 : 0`에서 ActionController::Parameters를 통해 문자열 `"false"`가 올 경우 Ruby에서 truthy → 모든 응답이 1로 저장되는 치명적 버그 - **수정**: `ActiveModel::Type::Boolean.new.cast(r[:selected]) ? 1 : 0` 사용 ### [HIGH] question_id 타입 불일치 (controller:87-88) - `submitted_ids`가 문자열 Set, `valid_question_ids`가 UUID Set → 검증 무력화 가능 - **수정**: `r[:question_id].to_s`로 타입 통일 ### [HIGH] valid_questions_index 키 타입 불일치 (controller:107) - `index_by(&:id)` 키가 UUID인데 `r[:question_id]`가 문자열 → dna_type이 nil로 저장 - **수정**: `valid_questions_index[r[:question_id].to_s]` 또는 키 타입 통일 --- ## 작업 내용 (이하 기존 내용 동일) ### 1. 새 컨트롤러 액션: submit_all_big5_responses - POST /diagnosis_sessions/:id/submit_all_big5_responses - params: { responses: [{ question_id:, selected: true/false, response_time: }] } - 90개 응답(30라운드 × 3문항)을 한 번에 수신 - 서버 검증: 응답 수 확인, question_id 유효성, 세션 상태 확인 ### 2. session_manager: save_responses_bulk - upsert_all 활용 ### 3. N+1 제거 (기존 코드) ### 4. 라우트 추가 ## 완료 기준 - 반려 사유 3건 모두 수정 - 타입 안전성 테스트 추가 - 기존 테스트 통과

B
backend-dev
2 days
긴급 be0bd511

database.yml pool 키 수정 (max_connections → pool)

## 문제 `config/database.yml`에서 `max_connections` 키를 사용 중이나, Rails ActiveRecord가 인식하는 키는 `pool`임. 현재 AR 기본값 5가 우연히 맞아서 동작 중이나, 향후 스레드 수 변경 시 의도대로 동작하지 않을 위험. ## 변경 ```yaml # AS-IS max_connections: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> # TO-BE pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> ``` ## 완료 기준 - [ ] `pool` 키로 변경 - [ ] `docker compose exec web bin/rails runner "puts ActiveRecord::Base.connection_pool.size"` → 5 출력 - [ ] bin/ci 통과

팀리드
2 days
긴급 2c69826e
부모 티켓

Big5 응답 일괄 제출 (레거시 패턴)

## 목표 Big5 진단 응답을 매 라운드 서버 전송 → JS 누적 후 마지막 1번 제출로 변경. HTTP 요청 30배 감소 (1000명: 30,000 → 1,000). ## 배경 (레거시 분석) 레거시(Next.js)는 모든 응답을 localStorage에 누적 → 마지막에 POST 1번으로 1000명 동시 진단 처리. 현재 Rails는 30라운드 × 개별 AJAX = 30배 더 많은 서버 요청 발생. ## 변경 범위 - **Stimulus 컨트롤러**: 응답 데이터를 JS 메모리/sessionStorage에 누적 - **Turbo Stream 라운드 전환**: 서버 저장 제거, 클라이언트에서 다음 라운드 전환 (UI 유지) - **diagnosis_sessions_controller**: 최종 제출 액션 추가 (submit_all_big5_responses) - **session_manager**: save_responses_bulk 메서드 (upsert_all 활용) - **sessionStorage 폴백**: 네트워크 끊김/새로고침 대비 임시 저장 ## 완료 기준 - Big5 진단 30라운드 완료 시 서버 요청 1회만 발생 - sessionStorage에 중간 응답 저장 확인 - 새로고침 시 진행 상태 복원 - 기존 테스트 통과 ## 리스크 - 네트워크 끊김 시 데이터 손실 → sessionStorage 폴백으로 대응 - 라운드별 서버 검증 제거 → 최종 제출 시 일괄 검증으로 보완 - submit_part2_round도 동일 패턴 적용 검토 ## 의존성 없음 (PR #1과 병렬 작업 가능)

2/2
팀리드
2 days
보통 5deb8f08
서브 티켓 Big5 결과 페이지 캐싱

진단 결과 Rails.cache.fetch 적용

diagnoses_controller.rb show에서 Big5/9Way/Engagement 결과를 Rails.cache.fetch로 캐싱. expires_in: 30.days. big5_result_formatter.rb의 find_quality_code 맵핑도 캐시. 테스트 통과 확인.

C
cache-dev
5 days
긴급 5e4a64e1
서브 티켓 Big5 응답 일괄 제출 (레거시 패턴)

Client: Stimulus 컨트롤러 + sessionStorage + 라운드 전환

## 목표 Big5 진단의 라운드별 AJAX 제출을 클라이언트 누적 + 최종 1회 제출로 변경. ## ⚠️ 리뷰 반려 사유 (2026-03-09) ### [MUST-FIX] 접근성 ARIA 속성 누락 - 질문 카드에 `role="radio"`, `aria-checked` 없음 - 컨테이너에 `role="radiogroup"` 없음 - neither 버튼에 `aria-label` 없음 - 카운트다운에 `role="timer"`, `aria-live="polite"` 없음 - 선택 시 `aria-checked="true"` 상태 변경 필요 ### [MUST-FIX] JS 내 한국어 텍스트 하드코딩 - `_showSubmitting()`, `_showComplete()`, `_showError()` 내 텍스트가 한국어만 - 4개 언어(ko, en, zh, vi) 지원인데 i18n 미적용 - **수정**: Stimulus value로 번역 텍스트 전달 또는 서버 렌더링 `<template>` 사용 ### [MUST-FIX] 타임아웃 자동 스킵 피드백 부재 - 30초 경과 시 자동으로 다음 라운드로 넘어가지만 사용자 안내 없음 - 토스트 메시지 또는 안내 텍스트 필요 --- ## 작업 내용 (이하 기존 내용 동일) ### 1. Stimulus 컨트롤러: big5_batch_controller.js ### 2. 클라이언트 라운드 전환 ### 3. sessionStorage 폴백 ### 4. 뷰 수정 ## 완료 기준 - 반려 사유 3건 모두 수정 - ARIA 접근성 테스트 확인 - 4개 언어에서 UI 텍스트 정상 표시 - 기존 기능 동작 유지

F
frontend-dev
2 days
긴급 f3099df8

prepare_all_big5_rounds 세션별 캐싱

## 문제 `diagnosis_sessions_controller.rb#prepare_all_big5_rounds`가 매 show 요청마다 실행 (300-500ms). 90개 문항 + 번역 로드 + 45라운드 생성. 세션 ID 기반 결정론적 RNG이므로 결과 항상 동일. ## 변경 ```ruby def prepare_all_big5_rounds Rails.cache.fetch(["big5_rounds", @session.id, I18n.locale], expires_in: 1.day) do # 기존 로직 end end ``` 또한 `load_questions`와 `prepare_all_big5_rounds`의 이중 쿼리 통합: - load_questions에서 한 번 로드한 것을 재활용하도록 리팩토링 ## 완료 기준 - [ ] show 페이지 캐시 히트 시 응답시간 300-500ms → 10-30ms - [ ] 새로고침 시 동일 라운드 데이터 반환 - [ ] bin/ci 통과

팀리드
2 days
높음 72c7068d

N+1 제거 + upsert_all + includes(:translations)

## 목표 일괄 제출된 응답을 효율적으로 저장. 쿼리 90개 → 1-2개로 감소. ## 변경 파일 - `app/services/diagnosis/session_manager.rb` — save_responses_bulk 메서드 추가 (upsert_all) - `app/controllers/diagnosis_sessions_controller.rb` — SurveyQuestion.find × N → where(id:).index_by - `app/controllers/diagnosis_sessions_controller.rb` — load_questions에 `.includes(:translations)` 추가 - submit_part2_round에도 동일 N+1 제거 적용 ## 구현 핵심 ```ruby # session_manager.rb def save_responses_bulk(session, responses_data) records = responses_data.map { |d| { diagnosis_session_id: session.id, ... } } DiagnosisResponse.upsert_all(records, unique_by: [:diagnosis_session_id, :survey_question_id]) end ``` ## 완료 기준 - 기존 테스트 통과 - DB 로그에서 최종 제출 시 쿼리 2개 이하 확인 - upsert_all 시 created_at 덮어쓰기 방지 확인 ## 주의사항 - upsert_all은 AR validation 스킵 → DB NOT NULL 제약으로 보장 - unique index `idx_responses_session_question` 활용 ## 의존성 PR #2 이후 (일괄 제출 구조에 맞춰 구현)

팀리드
2 days
높음 60426b17

Big5Description 쿼리 캐싱

## 문제 `Big5ResultFormatter#build_description_cache`가 매 요청마다 `Big5Description.where(locale:)` 실행. 결과 페이지 자체는 Rails.cache로 캐싱되나, 캐시 미스 시 포맷팅 과정의 DB 쿼리는 캐싱 없음. ## 변경 ```ruby def build_description_cache(locale) @big5_cache = Rails.cache.fetch("big5:descriptions:#{locale}", expires_in: 1.day) do Big5Description.where(locale: locale) .each_with_object({}) do |desc, hash| hash[[desc.description_type, desc.key, desc.field_type]] = desc.content end end @name_suffix = locale == "ko" ? "ko" : locale.to_s end ``` ## 완료 기준 - [ ] Big5Description 쿼리가 locale별 1일 캐싱 - [ ] 캐시 미스 시에만 DB 쿼리 발생 - [ ] bin/ci 통과

팀리드
2 days
보통 53b3c5c0
부모 티켓

Big5 결과 페이지 캐싱

## 목표 완료된 진단 결과(불변 데이터)를 Rails.cache로 캐싱하여 반복 조회 시 DB 쿼리 0개. ## 변경 파일 - `app/controllers/diagnoses_controller.rb` — show 액션에 Rails.cache.fetch 적용 - `app/services/diagnosis/big5_result_formatter.rb` — find_quality_code 맵핑 캐시 ## 구현 ```ruby # diagnoses_controller.rb @result = Rails.cache.fetch(["big5_result", @session.id, I18n.locale], expires_in: 30.days) do Diagnosis::Big5ResultFormatter.new(@session).format end ``` ## 완료 기준 - 첫 조회 시 Formatter 실행, 이후 캐시 히트 (로그 확인) - 9Way, Engagement 결과에도 동일 패턴 적용 - expires_in: 30일 (진단 결과는 불변) ## 의존성 없음 (독립 작업 가능)

1/1
팀리드
5 days
높음 36f8b950

response_timeout 120초 → 60초 축소

## 문제 Kamal Proxy의 response_timeout이 120초로, 느린 요청이 Puma 스레드를 2분간 점유 가능. 10개 동시 스레드에서 1개가 120s 묶이면 10% 용량 손실. ## 변경 ```yaml # config/deploy.yml proxy: ssl: true host: 9way.org response_timeout: 60 # 120 → 60 ``` 60초로 설정하는 이유: PDF 생성(grover gem)이 10-30초 소요 가능하므로 30초는 위험. ## 완료 기준 - [ ] deploy.yml response_timeout 60으로 변경 - [ ] kamal deploy 후 정상 동작 확인 - [ ] 진단 완료 + 결과 페이지 + PDF 생성 정상 작동

팀리드
2 days
보통 3284c93a

Puma 스레드 5 → 7 증가

## 문제 현재 2 workers × 5 threads = 10 동시 슬롯. show 캐싱 후 대부분 I/O bound 작업이므로 스레드 증가가 효과적. ## 변경 ```yaml # config/deploy.yml env: clear: RAILS_MAX_THREADS: 7 # 5 → 7 ``` database.yml의 pool도 RAILS_MAX_THREADS를 참조하므로 자동 연동. 2 × 7 = 14 슬롯 (40% 동시성 증가). 스레드당 스택 ~1MB이므로 메모리 영향 미미 (~14MB 추가). ## 완료 기준 - [ ] RAILS_MAX_THREADS=7 설정 - [ ] DB pool이 7로 연동 확인 - [ ] 메모리 사용량 200MB 이내 증가 확인 - [ ] bin/ci 통과

팀리드
2 days
보통 b73d2b38

k6 부하 테스트 스크립트 작성

## 목적 최적화 효과를 실측으로 검증할 부하 테스트 도구 도입. ## 변경 - `test/load/diagnosis_flow.js` (신규, k6 스크립트) ## 시나리오 1. 진단 세션 생성 → show 페이지 로드 → 45라운드 응답 → complete 2. 동시 사용자: 10 → 50 → 100 → 500 ramp-up 3. 목표 메트릭: p95 < 1s, 에러율 < 1% ## 완료 기준 - [ ] k6 스크립트 작동 - [ ] 스테이징에서 테스트 실행 가능 - [ ] before/after 성능 비교 가능 - [ ] p50, p95, p99, 에러율 메트릭 정의

팀리드
1 day