경상국립대학교 학우들이 학교 생활에 필요한 정보를 빠르게 확인할 수 있도록 돕는 서비스입니다.
공지사항, 학식, 학사일정, 셔틀버스 시간표 등 학교 생활에 필요한 정보를 제공합니다.
- 116개의 학과, 169개의 게시판에서 실시간으로 공지사항을 스크래핑하여 제공합니다.
- 사용자는 자신의 단과대학 및 학과를 선택하여 맞춤형 공지사항을 제공받을 수 있습니다.
- 공지사항을 클릭하면 원본 게시글로 이동할 수 있습니다.
- 4개 캠퍼스, 9개의 식당 학식 메뉴를 조회할 수 있습니다.
- 원하는 캠퍼스 및 식당을 선택하면 해당 날짜의 메뉴를 한눈에 확인할 수 있습니다.
- 매주 새로운 학식 메뉴가 자동으로 업데이트됩니다.
- 매월 주요 학사 일정을 한눈에 확인할 수 있습니다.
- 개강, 중간·기말고사, 수강신청 등 중요한 일정을 놓치지 않도록 도와줍니다.
- 학사 일정이 업데이트되면 자동으로 반영됩니다.
- 카카오 챗봇은 사용자의 발화를 미리 정의된 의도와 매칭하고, 각 의도에 연결된 API를 호출하는 구조입니다.
- 예를 들어 "내일 기숙사 식당 점심 알려줘" 와 같은 자연어 요청이 들어오면, NLU가 식단 조회 의도로 분류 후 연결된 식단 API를 호출해 결과를 반환합니다.
제한된 기간과 팀 상황 안에서 왜 그렇게 설계했는지를 남기기 위해 작성했습니다.
각 의사결정은 문제 → 원인 → 해결 → 결과/트레이드오프 → 느낀 점 순서로 정리했습니다.
캠퍼스/단과대학/학과(Campus/College/Department)
- 캠퍼스, 단과대학, 학과는 서로 계층적인 관계를 가지고 있습니다.
- 4개의 캠퍼스, 17개의 단과대학, 116개의 학과가 있습니다. (2024년 기준)
- 캠퍼스, 단과대학, 학과는 각각 고유한 한글 이름과 영어 이름을 가지고 있습니다.
- 한글 이름은 사용자에게 보여주는 값으로 사용하고, 영어 이름은 데이터 수집 시 식별자로 사용합니다.
공지사항(Notice)
- 각 학과에는 0개 또는 1개 이상의 공지사항 게시판이 있습니다.
- 게시판 정보는
notice_category에 저장하고, 실제 공지 내용은notice에 저장했습니다. notice_category에는 학과 ID, 게시판 ID, 마지막으로 수집한 공지 번호를 저장합니다.notice에는 제목, 작성일처럼 사용자에게 보여줄 공지 데이터를 저장합니다.- 이렇게 분리하면 게시판별 수집 상태와 실제 공지 데이터를 따로 관리할 수 있습니다.
학식(Cafeteria)
- 학식 데이터는 식당 정보와 날짜별 메뉴 정보로 나누었습니다.
cafeteria에는 캠퍼스, 식당명, 식당 타입, 외부 식당 ID 같은 정보를 저장합니다.cafeteria_diet에는 날짜, 요일, 시간대, 메뉴명처럼 매일 바뀌는 정보를 저장합니다.- 식당 정보와 식단 정보를 분리해 같은 식당 정보를 반복해서 저장하지 않도록 했습니다.
학사일정(Academic Calendar)
- 학사일정은 일정 유형, 시작일, 종료일, 내용을 기준으로 저장했습니다.
- 개강, 시험, 수강신청처럼 기간이 있는 일정이 많아 시작일과 종료일을 따로 두었습니다.
- 특정 학과나 사용자보다 학교 전체에서 공통으로 사용하는 정보에 가까워 독립 테이블로 관리했습니다.
- 일정 유형을 저장해 특정 유형의 일정 필터링(예: 대학생, 대학원생 등)이 가능하도록 했습니다.
문제
- 서비스 초기에 GCP 프리티어 단일 인스턴스에서 API 서버, 스크래핑 작업, 모니터링을 함께 운영했습니다.
- 스크래핑 작업이나 메트릭 수집 시 API 응답이 눈에 띄게 느려지는 문제가 발생했습니다.
원인
- 배치 작업이 CPU와 메모리를 많이 사용하면서 API 서버가 사용할 자원이 부족해졌습니다.
- 메모리가 부족해지면서 스왑 메모리를 사용하게 되었고, 이로 인해 디스크 I/O가 증가했습니다.
- 여러 작업이 동시에 실행되면서 컨텍스트 스위칭이 증가해 전체적인 성능이 저하되었습니다.
해결
- 문제를 해결하기 위해 역할에 따라 인스턴스를 분리하는 구조로 개선했습니다.
- API 서버 전용 인스턴스
- 스크래핑 작업 전용 인스턴스
- 모니터링 전용 인스턴스
- 구글 계정 2개를 사용해 프리티어 인스턴스를 추가로 확보하여, 비용을 늘리지 않으면서도 작업을 나눠서 실행할 수 있었습니다.
- 스크래핑 작업은 Github Actions Runner를 활용하여 주기적으로 실행되도록 구성했습니다.
결과
- 리소스 경쟁이 줄어들면서 전체 시스템의 성능이 개선되었습니다.
- 서비스 간 장애 전파 가능성을 줄였습니다.
- 수직 확장(서버 스펙 증가)이나 수평 확장(인스턴스 추가)을 하지 않고도 문제를 해결할 수 있었습니다.
- 결과적으로 성능 개선과 비용 절감을 동시에 달성할 수 있었습니다.
트레이드오프
- 배포 대상과 운영 지점이 늘어나 환경 변수, 네트워크, 모니터링 설정을 더 꼼꼼히 관리해야 했습니다.
느낀 점
- 인프라 자원은 정말 비싸다는 것을 느꼈습니다.
- 프리티어 자원을 최대한으로 활용하면 비용 지출 없이도 충분히 서비스를 운영할 수 있다는 것을 배웠습니다.
문제
- 교내 페이지를 HTTP 요청 기반으로 수집했을 때 응답은 정상이어도 실제 데이터가 비어 있는 경우가 있었습니다.
- 동일한 페이지를 브라우저에서는 정상적으로 확인할 수 있었지만, 코드로 수집할 경우 데이터가 누락되는 문제가 발생했습니다.
원인
- 일부 페이지는 서버에서 완성된 HTML을 반환하는 SSR 방식이 아니라, JavaScript 실행 이후 데이터가 렌더링되는 CSR 구조였습니다.
- 따라서 단순 HTTP 요청으로는 초기 HTML만 받아오게 되어 실제 데이터가 포함되지 않았습니다.
- 브라우저에서는 JavaScript가 실행되면서 추가 API 요청을 통해 데이터를 가져오고 화면에 렌더링되기 때문에 차이가 발생했습니다.
![]() SSR |
![]() CSR |
해결
- 페이지 렌더링 방식에 따라 수집 방식을 분리했습니다.
- SSR 페이지 → HTTP 요청 기반 수집 유지
- CSR 페이지 → Selenium 기반 브라우저 렌더링 수집 적용
- 모든 페이지를 Selenium으로 처리하는 대신, 필요한 경우에만 선택적으로 적용하도록 설계했습니다.
- 이를 위해 페이지 구조를 분석하여 CSR 여부를 판단하는 기준을 정리하고, 수집 로직을 분기 처리했습니다.
결과
- 데이터가 비어 오는 문제를 해결하고 수집 정확도를 크게 개선할 수 있었습니다.
- 필요한 페이지에만 Selenium을 적용하여 실행 시간과 리소스 사용량을 최소화할 수 있었습니다.
느낀 점
- 데이터를 잘 수집하기 위해서는 웹 통신 과정에 대한 이해가 꼭 필요하다는 것을 느꼈습니다.
- 특히 네트워크 탭을 통해 실제 API 요청 흐름을 분석하면서 문제를 해결할 수 있었던 경험이 인상 깊었습니다.
- 이 경험을 통해 기능 구현 전에 동작 원리를 먼저 이해하려는 습관이 중요하다는 것을 배웠습니다.




