제목, 태그, 카테고리로 검색

모든 글
약 6분 분량 프로젝트/에듀밋

EduMeet - 첫 팀 프로젝트를 마무리하며

목차

프로젝트 소개

EduMeet은 실시간 음성-자막 변환으로 청각장애인의 학습을 지원하는 온라인 교육 플랫폼입니다. 한국수어통역사협회 자료에 따르면 수화 통역사 대비 청각장애인 비율이 1:300이라고 합니다. 온라인 강의에서 청각장애인은 자막 없이는 학습이 사실상 불가능한데, AI 음성인식으로 실시간 자막을 제공하면 이 문제를 크게 개선할 수 있다고 판단했습니다.

EduMeet 아키텍처

기간: 2025.07.07 - 2025.08.15 (6주) 팀 구성: 6명 (프론트엔드 3명, 백엔드 3명) 기술 스택: Java, Spring Boot, JPA, QueryDSL, MySQL, AWS S3, Docker


내 역할

백엔드 개발(기여도 33%)을 담당했습니다. 구체적으로는 게시판 CRUD, 이미지 업로드, 단위테스트 전반을 맡았습니다.

첫 팀 프로젝트라서 “기능 구현”에만 집중할 줄 알았는데, 실제로는 기능 구현보다 방어 로직과 테스트 코드에 훨씬 더 많은 시간을 쏟았습니다.


주요 구현

N+1 문제 해결 (66.9% 성능 개선)

게시판 목록 조회 시 쿼리가 게시글 수에 비례해서 증가하는 N+1 문제를 발견했습니다. @BatchSize(20)으로 IN절 배치 조회를 적용해 쿼리 수를 11개에서 2개로 줄이고, 응답 시간을 38.23ms에서 12.66ms로 66.9% 개선했습니다.

상세 분석: N+1 문제 분석과 해결

127개 단위테스트 & H2 전환

MySQL로 테스트를 돌리면 9.57초가 걸렸는데, H2 인메모리 DB로 전환하니 5.23초로 45% 빨라졌습니다. 단위테스트는 H2, 통합테스트는 MySQL로 이원화했습니다.

127개 테스트 케이스 중 절반 이상이 “빈 제목으로 등록하면?”, “좋아요가 Integer.MAX_VALUE를 넘으면?” 같은 비정상 상황 대응 테스트였습니다.

상세 분석: 단위테스트 DB 마이그레이션

S3 이미지 업로드 최적화

원본 이미지(5MB)를 그대로 저장하면 스토리지 비용이 문제가 됩니다. 이미지 리사이징으로 91.8% 용량을 감소시키고, DB PK는 Auto Increment로, 파일명은 UUID로 역할을 분리했습니다.

상세 분석: S3 업로드 최적화


기억에 남는 트러블슈팅

OneToMany 중간테이블 자동 생성

Board-BoardImage 1:N 관계를 설정했는데, JPA가 엉뚱하게 board_image_set이라는 중간테이블을 자동으로 만들어버렸습니다. ERD에 없는 테이블이 갑자기 생기니 팀원들이 혼란스러워했죠.

원인은 @OneToManymappedBy를 지정하지 않아서 단방향 연관관계로 인식한 것이었습니다. mappedBy = "board"를 추가하니 깔끔하게 해결됐습니다.

상세 분석: OneToMany 중간테이블 문제

QueryDSL 파일 이동 오류

레이어드 아키텍처를 적용하려고 Repository 파일을 옮기면서 클래스명을 바꿨더니 “No property searchAll found for type Board” 에러가 발생했습니다. Spring Data JPA의 인터페이스명 + Impl 네이밍 규칙 때문이었는데, 이걸 몰랐으면 한참 헤맸을 겁니다.

상세 분석: 파일 이동 오류


느낀 점

기능보다 방어 로직이 중요하다

게시판 CRUD 기능 구현은 3일 만에 끝났습니다. 그런데 테스트 코드를 작성하면서 비정상 상황을 하나씩 따져보니, 방어 로직 구현에 5일이 더 걸렸습니다. 기능이 동작한다와 서비스가 안전하다는 다르다는 걸 처음으로 체감했습니다.

코드 리뷰의 가치

GitLab MR 기반 코드 리뷰를 도입했습니다. “왜 이렇게 구현했나요?”라는 질문에 답하려면 공식 문서를 확인하고 근거를 정리해야 했습니다. 번거롭지만, 이 과정 덕분에 모든 코드에 “왜 이렇게 했는가”의 근거를 준비하는 습관이 생겼습니다.

완성의 경험

2주차에 기능 목록이 일정 대비 많다는 걸 깨닫고, 핵심 기능부터 완성하자고 제안했습니다. 게시판 CRUD → 이미지 업로드 → WebRTC 순서로 우선순위를 정하고, 매일 작은 단위로 완성해나갔습니다. 6주 안에 기획부터 배포까지 마무리한 첫 경험이었습니다.

About the Project

EduMeet is an online education platform that supports hearing-impaired learners through real-time speech-to-subtitle conversion. According to the Korea Association of Sign Language Interpreters, the ratio of sign language interpreters to hearing-impaired individuals is 1:300. Online lectures are virtually inaccessible without subtitles, and we believed AI speech recognition could significantly address this issue.

EduMeet Architecture

Duration: Jul 7 – Aug 15, 2025 (6 weeks) Team: 6 members (3 Frontend, 3 Backend) Tech Stack: Java, Spring Boot, JPA, QueryDSL, MySQL, AWS S3, Docker


My Role

I was responsible for backend development (33% contribution). Specifically, I owned board CRUD, image upload, and unit testing across the project.

Being my first team project, I expected to focus solely on feature implementation, but in reality I spent far more time on defensive logic and test code than the features themselves.


Key Implementations

Fixing the N+1 Problem (66.9% Performance Improvement)

I discovered an N+1 problem where the number of queries grew proportionally to the number of posts when loading the board list. By applying @BatchSize(20) for IN-clause batch loading, I reduced the query count from 11 to 2 and improved response time from 38.23ms to 12.66ms — a 66.9% improvement.

Detailed analysis: N+1 Problem Analysis and Solution

127 Unit Tests & H2 Migration

Running tests against MySQL took 9.57 seconds. Switching to H2 in-memory DB brought it down to 5.23 seconds — 45% faster. I separated unit tests (H2) from integration tests (MySQL).

More than half of the 127 test cases covered abnormal scenario handling — things like “What if a title is empty?” or “What if likes exceed Integer.MAX_VALUE?”

Detailed analysis: Unit Test DB Migration

S3 Image Upload Optimization

Storing original images (5MB) as-is creates storage cost issues. By applying image resizing, I achieved a 91.8% reduction in file size, and separated concerns by using Auto Increment for DB PKs and UUID for file names.

Detailed analysis: S3 Upload Optimization


Memorable Troubleshooting

OneToMany Auto-Generated Join Table

When I set up a Board–BoardImage 1:N relationship, JPA unexpectedly auto-created a board_image_set join table. A table that didn’t exist in our ERD suddenly appeared, confusing the entire team.

The cause was not specifying mappedBy on @OneToMany, which made JPA treat it as a unidirectional relationship. Adding mappedBy = "board" resolved it cleanly.

Detailed analysis: OneToMany Join Table Problem

QueryDSL File Move Error

While applying layered architecture, I moved and renamed Repository files, which triggered a “No property searchAll found for type Board” error. It was caused by Spring Data JPA’s interface name + Impl naming convention — without knowing this rule, I would have been stuck for hours.

Detailed analysis: File Move Error


Takeaways

Defensive Logic Matters More Than Features

The board CRUD feature took just 3 days to implement. But writing test code and handling each abnormal scenario one by one took another 5 days. This was the first time I truly felt that “the feature works” and “the service is safe” are two different things.

The Value of Code Reviews

We adopted GitLab MR-based code reviews. To answer “Why did you implement it this way?”, I had to check official documentation and prepare justifications. It’s tedious, but this process built a habit of always having a rationale for every code decision.

The Experience of Completion

In week 2, I realized our feature list was too ambitious for the timeline, so I proposed completing core features first. We prioritized: Board CRUD → Image Upload → WebRTC, completing small units daily. This was my first experience of finishing everything from planning to deployment within 6 weeks.

Author
작성자 @범수

오늘의 노력이 내일의 전문성을 만든다고 믿습니다.

댓글