{
  "projects": [
    {
      "id": 1,
      "title": "통합 로그 시스템",
      "url": "unified-log-system",
      "category": "Backend / Data Pipeline",
      "img": "/images/web-project-1.jpg",
      "ProjectHeader": {
        "title": "통합 로그 시스템",
        "publishDate": "2026.01 – 2026.03",
        "tags": "Backend / Infra / Data Pipeline"
      },
      "ProjectImages": [
        { "title": "통합 로그 시스템", "img": "/images/web-project-1.jpg" },
        { "title": "통합 로그 시스템", "img": "/images/web-project-2.jpg" },
        { "title": "통합 로그 시스템", "img": "/images/ui-project-1.jpg" }
      ],
      "ProjectInfo": {
        "ClientHeading": "About Project",
        "CompanyInfo": [
          { "title": "프로젝트 성격", "details": "개인 프로젝트 (기여도 100%)" },
          { "title": "팀 구성", "details": "1명" },
          { "title": "담당", "details": "설계 · 구현 · 사내 배포" },
          { "title": "기간", "details": "2026.01 ~ 2026.03" },
          { "title": "배포", "details": "사내 서버, Docker Compose" }
        ],
        "ObjectivesHeading": "Objective",
        "ObjectivesDetails": "AhnLab EPP / EDR / MDS 보안 로그를 공통 스키마로 정규화하고, Loki·Grafana 대시보드, Teams 실시간 알림, MCP 기반 자연어 조회까지 연결해 로그 분석과 대응 속도를 높인 통합 로그 시스템.",
        "Technologies": [
          {
            "title": "Tools & Technologies",
            "techs": ["Node.js", "TypeScript", "NestJS", "syslog-ng", "Vector", "Loki", "Grafana", "Docker Compose", "Teams Webhook", "MCP", "JSON-RPC"]
          }
        ],
        "ProjectDetailsHeading": "Challenge",
        "ProjectDetails": [
          {
            "details": "## 문제 상황\n\n기존에는 보안 로그를 확인하려면 각 솔루션 사이트에 접속해 로그인하고, 조회 조건을 직접 입력한 뒤 결과를 하나씩 해석해야 했습니다. 특히 EPP / EDR / MDS는 로그 형식과 필드 구성이 서로 달라, 서비스 간 비교나 공통 조건 기반 탐색이 어려웠습니다.\n\n이 프로젝트는 단순히 로그를 저장하는 데서 끝나지 않고, 운영자가 **로그를 더 빠르게 찾고, 더 쉽게 해석하고, 바로 대응할 수 있는 흐름**을 만드는 것을 목표로 했습니다. 이를 위해 서로 다른 보안 로그를 하나의 기준으로 정규화하고, 대시보드·실시간 알림·자연어 조회까지 연결하는 통합 로그 시스템을 구축했습니다."
          },
          {
            "details": "## 실제 아키텍처\n\n**로그 입력 경로**\n\n- AhnLab 장비/솔루션(EPP / EDR / MDS) → syslog-ng → Vector → Loki → Grafana\n\n**알림 흐름**\n\n- Vector에서 위험 이벤트를 조건 분기 → NestJS API 호출 → Teams Webhook 전송\n\n**자연어 조회 흐름**\n\n- MCP 서버 → Loki 질의 → 결과 반환\n\n**배포 방식**\n\n- 사내 서버에 Docker Compose로 배포\n- api / mcp / syslog-ng / vector / loki / grafana를 하나의 스택으로 운영"
          },
          {
            "details": "## 공통 스키마 설계\n\n장비별 로그를 실제로 분석한 뒤, 서비스 간 공통으로 활용할 수 있는 기준 필드를 추출해 아래 스키마로 정규화했습니다.\n\n- `service`: 로그가 어느 보안 솔루션(EPP / EDR / MDS)에서 왔는지 식별\n- `risk`: 보안 솔루션이 탐지한 위협 수준\n- `syslog_type`: 장비별 이벤트 유형을 식별하기 위해 파싱 단계에서 추가한 필드\n- `parse_status`: 지정한 서비스 포맷이 아닌 다른 syslog가 들어오거나 부분 파싱에 실패한 경우를 식별하기 위한 상태 필드\n\n이 공통 스키마를 통해 서로 다른 장비 로그라도 동일한 기준으로 필터링·집계·탐색할 수 있도록 구성했습니다."
          },
          {
            "details": "## 핵심 기술적 판단\n\n### 1. syslog-ng + Vector로 수집 안정성과 파싱 유연성을 분리\n\n이 프로젝트에서 가장 중요하게 가져간 설계 판단 중 하나는 **수집 책임과 파싱 책임을 분리하는 것**이었습니다.\n\n- syslog-ng는 syslog 수집과 disk-buffer 기반 안정성 확보를 담당\n- Vector는 라우팅, VRL 기반 파싱, 정규화, Loki 적재, Teams 이벤트 분기를 담당\n\n이렇게 분리한 이유는 다음과 같습니다.\n\n- 수집 안정성은 로그 유실 방지와 버퍼링이 핵심이기 때문에 앞단에서 안정적으로 처리할 필요가 있었고\n- 장비별 로그 형식 차이를 흡수하는 파싱 로직은 VRL 기반으로 유연하게 변경 가능한 구조가 더 적합했기 때문입니다\n\n결과적으로 수집 계층은 안정적으로 유지하면서도, 파싱/정규화 로직은 빠르게 수정·확장할 수 있는 구조를 만들 수 있었습니다.\n\n### 2. MCP를 붙여 로그 파이프라인을 자연어 조회 인터페이스까지 확장\n\n이 프로젝트의 두 번째 핵심 판단은, 로그 파이프라인을 단순 대시보드 조회 도구가 아니라 **운영 자동화 인터페이스**로 확장하는 것이었습니다.\n\n이를 위해 NestJS 기반 MCP 서버를 별도 애플리케이션으로 구현했고, 헥사고날 아키텍처를 적용해 어댑터 레이어에서 **JSON-RPC over HTTP / stdio**를 모두 지원하도록 설계했습니다. MCP 서버는 Loki 질의용 도구를 제공하고, 자연어 요청을 기반으로 필요한 로그 조회 흐름을 빠르게 실행할 수 있도록 구성했습니다.\n\n이를 통해 운영자는 반복적인 조회 조건 입력 과정을 줄이고, 예를 들어 특정 IP 대역, 특정 위험도, 특정 시각 주변 로그를 빠르게 조회·요약하는 흐름으로 접근할 수 있게 되었습니다."
          },
          {
            "details": "## 어려웠던 점과 해결\n\n### 1. 장비별 로그 포맷이 서로 달라 공통 분석이 어려웠던 문제\n\n가장 어려웠던 점은 EPP / EDR / MDS가 모두 syslog로 들어오더라도, 실제 메시지 형식은 완전히 다르다는 점이었습니다.\n\n- EPP는 JSON 기반 이벤트\n- EDR은 접두어가 섞인 JSON 계열 이벤트\n- MDS는 LEEF 기반 문자열 이벤트\n\n뿐만 아니라 같은 장비 안에서도 필드 구성이 일정하지 않거나, timestamp 형식과 메시지 구조가 다를 수 있어 하나의 파서로 처리하기 어려웠습니다.\n\n이를 해결하기 위해 Vector에서:\n\n- 서비스별 라우팅 분기\n- 장비별 전용 VRL 파서 구성\n- event time과 ingest time 분리\n- 부분 파싱 실패 시 `parse_status=partial/invalid`로 상태 보존\n- 원본 메시지와 핵심 필드를 함께 유지하는 방식\n\n을 적용했습니다.\n\n이 구조를 통해 파싱 실패 로그까지 완전히 버리지 않고, 운영자가 품질 상태를 함께 확인할 수 있는 형태로 정리할 수 있었습니다.\n\n### 2. 저장 중심 로그를 실제 대응 가능한 운영 데이터로 바꾸는 문제\n\n로그를 Loki에 적재하는 것만으로는 운영 효율이 크게 좋아지지 않았습니다. 운영자 관점에서는 \"무슨 일이 일어났는지 바로 알 수 있는 정보\"가 필요했습니다.\n\n이를 위해:\n\n- Grafana에서 위험도, 서비스, 이벤트 유형 기준으로 대시보드 구성\n- Vector에서 특정 위험 로그를 분기해 NestJS API를 호출\n- NestJS가 직접 Teams Webhook으로 Adaptive Card 전송\n\n하도록 구성했습니다.\n\n알림 메시지에는 사용자 이름, IP 주소, 악성 파일명 등 **어떤 호스트에 어떤 문제가 발생했는지 직관적으로 파악할 수 있는 정보**를 포함시켜, 운영자가 대시보드에 들어가기 전에도 상황을 빠르게 인지할 수 있도록 만들었습니다.\n\n### 3. 반복적인 로그 조회 절차를 줄여야 했던 문제\n\n기존 방식은 솔루션 접속 → 로그인 → 조건 지정 → 조회 → 결과 해석처럼 여러 단계를 거쳐야 했고, 동일한 유형의 로그 확인에도 반복 작업이 많았습니다.\n\n이를 해결하기 위해 MCP 기반 조회 도구를 붙여 자연어 기반 조회 흐름으로 확장했습니다. 이 구조는 특정 서비스의 위험 로그 목록 조회, 위험 상세 로그 조회, 시간 범위 기반 조회 등으로 나누어 구현했고, Loki를 직접 질의해 결과를 반환하도록 구성했습니다.\n\n시나리오 테스트와 특정 예시 질의 기준으로 보면, 기존 10~15분 걸리던 로그 확인 과정을 1~2분 수준으로 줄일 수 있었습니다."
          },
          {
            "details": "## Teams 알림 구성\n\n이 프로젝트의 Teams 알림은 Grafana가 직접 보내는 방식이 아니라, **NestJS API가 직접 Webhook을 전송하는 구조**로 구성했습니다.\n\n실제로 Teams로 전달한 이벤트는 다음과 같은 즉시 대응 대상이었습니다.\n\n- 서버 악성코드 감염 의심 이벤트\n- 이상 네트워크 트래픽 감지\n- 위험도 높은 보안 탐지 이벤트\n\n메시지에는 운영자가 바로 상황을 파악할 수 있도록 다음 정보를 포함했습니다.\n\n- 사용자 이름\n- IP 주소\n- 악성 파일명 또는 탐지명\n- 발생 시각\n- 서비스 / 위험도\n\n## MCP / 자연어 조회 구현 포인트\n\n- NestJS 기반 별도 MCP 앱 구현\n- Loki 질의용 usecase를 도구 단위로 분리\n- HTTP `/rpc`와 stdio transport 모두 지원\n- 헥사고날 아키텍처 적용으로 inbound / application / outbound 레이어 분리\n- Loki를 직접 질의하는 클라이언트를 두고, 자연어 조회 흐름이 LogQL 실행으로 연결되도록 구성"
          },
          {
            "details": "## 성과 및 개선 효과\n\n- 서로 다른 보안 로그를 하나의 기준으로 정규화해 공통 필터링과 탐색이 가능해졌습니다\n- 시나리오 테스트 기준으로 기존 10~15분 걸리던 로그 확인 과정을 1~2분 수준으로 단축했습니다\n- 수작업 5단계에 가까웠던 반복 조회 과정을 자연어 질의 중심 흐름으로 단순화했습니다\n- 대시보드와 Teams 알림을 통해 운영자의 초기 인지 속도를 높였습니다\n- Docker Compose 기반으로 재현 가능한 운영 환경을 구성해 유지보수와 확장성을 높였습니다\n\n## 구현 포인트\n\n- syslog-ng + Vector 조합으로 수집 안정성과 파싱 유연성 분리\n- VRL 기반 장비별 파서 구현 및 공통 스키마 정규화\n- Loki / Grafana 프로비저닝 기반 대시보드·알림 구성\n- NestJS API를 통한 Teams Webhook 연계\n- NestJS 기반 MCP 서버와 Loki 질의 도구 구현\n- Docker Compose 기반 통합 실행 및 운영 환경 구성\n\n## 한 줄 회고\n\n이 프로젝트를 통해 단순 로그 수집을 넘어, **수집 안정성, 파싱 정규화, 대시보드, 실시간 알림, 자연어 조회까지 연결하는 운영 자동화형 로그 시스템**을 설계하고 실제 환경에 적용해본 경험을 쌓을 수 있었습니다."
          }
        ],
        "SocialSharingHeading": "Share This Project"
      }
    },
    {
      "id": 2,
      "title": "클래스업 - 사전 상담 기능",
      "url": "classup-pre-consultation",
      "category": "Backend",
      "img": "/images/web-project-2.jpg",
      "ProjectHeader": {
        "title": "클래스업 - 사전 상담 기능",
        "publishDate": "2025.10 – 2025.12",
        "tags": "Backend / AI / RAG"
      },
      "ProjectImages": [
        { "title": "클래스업 사전 상담", "img": "/images/web-project-2.jpg" },
        { "title": "클래스업 사전 상담", "img": "/images/ui-project-2.jpg" },
        { "title": "클래스업 사전 상담", "img": "/images/web-project-1.jpg" }
      ],
      "ProjectInfo": {
        "ClientHeading": "About Project",
        "CompanyInfo": [
          { "title": "서비스", "details": "클래스업 (학원 SaaS)" },
          { "title": "팀 구성", "details": "2명 (FE 1, BE 1)" },
          { "title": "담당", "details": "백엔드 전반 (API · DB · RAG · OpenAI 연동)" },
          { "title": "기간", "details": "2025.10 ~ 2025.12" }
        ],
        "ObjectivesHeading": "Objective",
        "ObjectivesDetails": "학원 상담 전에 학생·학부모 정보를 수집하고, RAG 기반으로 학원 내부 수업·교재 데이터를 검색해 상담 요약과 추천 결과를 생성하는 백엔드 기능.",
        "Technologies": [
          {
            "title": "Tools & Technologies",
            "techs": ["Express.js", "TypeScript", "MySQL", "Sequelize", "MongoDB", "MongoDB Vector Search", "OpenAI API", "Swagger"]
          }
        ],
        "ProjectDetailsHeading": "Challenge",
        "ProjectDetails": [
          {
            "details": "## 문제 상황\n\n기존 상담 프로세스에서는 상담 전에 학생의 성적, 희망 과목, 학부모 의견 등을 체계적으로 파악하기 어려웠고, 상담 품질도 담당자마다 달라질 수 있었습니다. 또한 상담 후 정식 등록으로 이어질 경우, 이미 입력한 정보를 다시 활용하기 어려운 비효율이 있었습니다.\n\n이를 해결하기 위해 사전 상담 단계에서 학생·학부모 정보를 구조화해 수집하고, 상담 전에 핵심 내용을 자동 요약하며, 학원 내부에 등록된 실제 수업·교재 정보까지 연결해 상담 지원이 가능한 기능을 구현했습니다."
          },
          {
            "details": "## 서비스 흐름\n\n1. 학부모/학생이 사전 상담 링크를 통해 상담지를 작성합니다.\n2. 백엔드에서 학생 정보, 성적, 상담 응답을 저장합니다.\n3. 상담 내용을 바탕으로 RAG 검색을 수행해 학원 내부 수업·교재 후보를 조회합니다.\n4. OpenAI API를 통해 다음 정보를 생성합니다.\n   - 학생 요약\n   - 학부모 의견 요약\n   - 추천 수업\n   - 추천 교재\n   - 상담 메모 초안\n5. 관리자 화면에서 결과를 확인하고 실제 상담에 활용합니다.\n6. 이후 정식 등록 단계와도 연계할 수 있도록 구조를 설계했습니다."
          },
          {
            "details": "## 핵심 기술적 판단\n\n### 1. 사전 상담 단계를 위한 temp_user 구조 설계\n\n사전 상담 단계의 사용자는 아직 정식 등록 전 상태이기 때문에, 기존 회원 테이블에 바로 귀속시키기에는 정보가 불완전했습니다. 반면 상담 이후 실제 등록으로 이어질 수 있으므로, 일회성 데이터처럼 버리기에도 아쉬움이 있었습니다.\n\n이를 해결하기 위해 정식 회원과 분리된 `temp_user` 구조를 두고, 사전 상담 단계의 성적·상담지·추천 결과를 이 임시 엔터티에 연결했습니다.\n\n이 구조를 통해 다음과 같은 장점을 확보했습니다.\n\n- 상담 단계와 등록 단계를 자연스럽게 분리\n- 아직 회원이 아닌 사용자도 안정적으로 관리\n- 등록 시 임시 데이터를 정식 프로세스로 연계할 수 있는 확장성 확보\n\n> 단순 입력 저장용 테이블이 아니라, 사전 상담에서 정식 등록까지 이어지는 흐름을 고려해 도메인 구조를 설계했습니다."
          },
          {
            "details": "## 어려웠던 점과 해결\n\n### 1. 교재 / 수업 추천이 제대로 작동하지 않던 문제\n\n초기에는 MySQL의 `ngram full-text parser`를 사용해 상담 정보와 유사한 학원 측 수업·교재 정보를 검색하고, 이를 LLM에 전달해 추천하는 방식을 시도했습니다. 하지만 이 방식은 유사 단어가 많이 포함된 문서, 즉 정보량이 많은 문서가 계속 우선 노출되는 문제가 있었습니다. 그 결과 학생의 실제 상황과 맞는 수업보다 단순히 텍스트 양이 많은 문서가 반복적으로 추천되는 한계가 있었습니다.\n\n이를 해결하기 위해 기존에 사용하던 MongoDB의 **Vector Search** 기능을 활용해 RAG 구조로 개선했습니다. 학생 상담 정보와 학원 측이 등록한 수업·교재 정보를 벡터 검색으로 연결하고, 검색된 결과를 OpenAI API의 컨텍스트로 전달해 추천을 생성하도록 구성했습니다.\n\n이 개선을 통해:\n\n- 단순 키워드 매칭보다 문맥 기반 검색이 가능해졌고\n- 학생의 희망 과목, 부족한 과목, 목표 전형 등을 더 자연스럽게 반영할 수 있었으며\n- 추천 정확도를 체감할 수 있을 정도로 높일 수 있었습니다\n\n> 검색 품질이 AI 추천 결과에 직접적인 영향을 준다는 점을 경험했고, 단순 LLM 호출보다 검색 계층 설계가 서비스 품질에 더 중요할 수 있다는 점을 배웠습니다.\n\n### 2. LLM 응답 형식이 일정하지 않았던 문제\n\n초기에는 LLM이 추천 결과를 자유 서술형으로 반환하도록 구성했는데, 응답 형식이 매번 달라 관리자 화면에서 후처리하기 어려웠습니다. 어떤 응답은 수업 추천만 길게 나오고, 어떤 응답은 교재 정보가 빠지거나 구조가 불안정해 실제 기능으로 사용하기에 한계가 있었습니다.\n\n이를 해결하기 위해 프롬프트를 개선해 출력 항목을 명확히 구분하고, 가능한 한 고정된 섹션 구조로 응답하도록 유도했습니다.\n\n출력 항목은 다음과 같이 정리했습니다.\n\n- 학생 요약\n- 학부모 의견 요약\n- 추천 수업\n- 추천 교재\n- 상담 메모 초안\n\n또한 서버 측에서 응답 유효성을 검증하고,\n\n- 필수 항목 누락 여부 확인\n- 누락 시 재시도\n- 실패 시 fallback 처리\n\n가 가능하도록 개선했습니다.\n\n이를 통해:\n\n- 관리자 화면에서 후처리 가능한 구조 확보\n- 응답 품질 편차 감소\n- AI 결과를 실제 서비스 기능으로 안정적으로 연결 가능\n\n> 단순히 LLM을 붙이는 데서 끝나지 않고, 실제 서비스에서 활용 가능한 형태로 응답을 안정화했습니다."
          },
          {
            "details": "## 구현 포인트\n\n- 학생 상담 정보와 학원 내부 수업·교재 데이터를 연결하는 RAG 파이프라인 구현\n- 학원 내부 데이터만 추천 근거로 사용하도록 구성해 운영 가능성과 정합성 확보\n- 상담 원문, 성적, 추천 결과를 분리 저장해 이후 활용성과 확장성 고려\n- Swagger를 사용해 프론트엔드와 API 명세를 공유하고 협업 효율 향상\n\n## ERD 핵심 설계 포인트\n\n- `tb_temp_user`를 중심으로 정식 등록 전 상담 데이터를 관리\n- 성적 정보는 `tb_temp_user_score`로 분리해 다과목·다시험 기록을 유연하게 저장\n- 상담 원문은 `tb_temp_user_intake`, AI 기반 요약·추천 결과는 `tb_temp_user_intake_summary`로 분리해 저장\n- 이후 정식 등록 프로세스와 연결할 수 있도록 확장성 있는 구조를 고려"
          },
          {
            "details": "## 성과 및 배운 점\n\n- 상담 전 수집 정보를 구조화해 상담 준비 흐름을 표준화할 수 있었습니다\n- 단순 키워드 검색의 한계를 벡터 검색 기반 RAG로 개선하며 검색 품질을 높였습니다\n- LLM 결과를 자유 생성에 맡기지 않고, 응답 구조 고정과 서버 검증을 통해 실제 서비스에 사용할 수 있는 형태로 안정화했습니다\n- 백엔드 개발자로서 DB 모델링, API 설계, 검색 구조 개선, AI 연동을 하나의 기능 안에서 통합적으로 설계해본 경험이었습니다\n\n## 한 줄 회고\n\n이 프로젝트를 통해 단순 기능 구현을 넘어, 실제 서비스 흐름 안에서 데이터 구조, 검색 품질, AI 응답 안정성까지 함께 설계하는 백엔드 개발의 중요성을 배웠습니다."
          }
        ],
        "SocialSharingHeading": "Share This Project"
      }
    },
    {
      "id": 3,
      "title": "클래스업 학생앱 - 타이머 서비스",
      "url": "classup-student-timer",
      "category": "Backend",
      "img": "/images/mobile-project-1.jpg",
      "ProjectHeader": {
        "title": "클래스업 학생앱 - 타이머 서비스",
        "publishDate": "2025.08 – 2025.10",
        "tags": "Backend / Performance / Migration"
      },
      "ProjectImages": [
        { "title": "클래스업 타이머", "img": "/images/mobile-project-1.jpg" },
        { "title": "클래스업 타이머", "img": "/images/mobile-project-2.jpg" },
        { "title": "클래스업 타이머", "img": "/images/ui-project-1.jpg" }
      ],
      "ProjectInfo": {
        "ClientHeading": "About Project",
        "CompanyInfo": [
          { "title": "서비스", "details": "클래스업 학생앱" },
          { "title": "팀 구성", "details": "3명 (FE 2, BE 1)" },
          { "title": "담당", "details": "백엔드 전반 (타이머 API · DB 설계 · Mongo↔DynamoDB 마이그레이션)" },
          { "title": "기간", "details": "2025.08 ~ 2025.10" }
        ],
        "ObjectivesHeading": "Objective",
        "ObjectivesDetails": "앱이 종료되거나 백그라운드로 전환되어도 학습 시간이 안정적으로 누적되도록, 서버 중심으로 타이머 상태를 관리한 학생앱 타이머 기능.",
        "Technologies": [
          {
            "title": "Tools & Technologies",
            "techs": ["Express.js", "TypeScript", "MySQL", "Sequelize", "MongoDB", "DynamoDB", "Joi", "Swagger"]
          }
        ],
        "ProjectDetailsHeading": "Challenge",
        "ProjectDetails": [
          {
            "details": "## 문제 상황\n\n학생앱의 타이머 기능은 단순히 화면에서 시간을 재는 기능이 아니라, 실제 사용자가 앱을 종료하거나 백그라운드로 전환하더라도 학습 시간이 끊기지 않고 안정적으로 누적되어야 했습니다. 또한 타이머 기록이 누적될수록 공부 기록 조회가 느려졌고, 개발 서버와 운영 환경 간 시간대 차이로 인해 다른 날짜에 기록이 저장되는 문제도 발생했습니다.\n\n이를 해결하기 위해 서버 중심의 타이머 상태 관리 구조를 설계하고, 저장소 마이그레이션, 인덱스 최적화, 페이지네이션 개선, 시간대 변환 자동화까지 함께 적용했습니다."
          },
          {
            "details": "## 서비스 흐름\n\n1. 사용자가 타이머를 시작하면 `startedAt`을 저장하고 활성 타이머 상태를 생성합니다.\n2. 서버는 `tb_user_timer_status`를 기준으로 사용자별 활성 타이머를 관리합니다.\n3. 타이머 조회 시에는 `현재 시각 - startedAt`으로 경과 시간을 계산합니다.\n4. 타이머 종료 시 실제 학습 시간을 확정하고 `durationSec`을 저장합니다.\n5. 확정된 기록은 일자별 누적 학습 시간 및 공부 기록 조회에 활용됩니다."
          },
          {
            "details": "## 핵심 기술적 판단\n\n### 1. 서버 중심 타이머 상태 관리 구조 설계\n\n앱 환경에서는 사용자가 화면을 닫거나 앱을 종료할 수 있기 때문에, 클라이언트에서만 타이머를 관리하면 학습 시간의 신뢰성이 떨어질 수 있었습니다. 이를 해결하기 위해 타이머 시작 시점을 저장하고, 서버가 경과 시간을 계산하는 구조로 설계했습니다.\n\n핵심 구조는 다음과 같습니다.\n\n- 타이머 시작 시 `startedAt` 저장\n- 활성 타이머 상태는 `tb_user_timer_status`에서 관리\n- 조회 시 `현재 시각 - startedAt` 계산\n- 종료 시 `durationSec` 확정 저장\n\n이 구조를 통해 클라이언트 상태와 무관하게 서버 기준으로 학습 시간을 계산할 수 있었고, 실제 사용자 환경에서도 안정적인 타이머 기능을 제공할 수 있었습니다.\n\n### 2. 한 사용자당 하나의 활성 타이머만 허용하는 제약 설계\n\n타이머 서비스에서는 같은 사용자가 동시에 여러 개의 타이머를 활성화하면 학습 시간이 중복 계산될 수 있었습니다. 이를 방지하기 위해 `tb_user_timer_status.userId`에 유니크 인덱스를 적용해 한 사용자당 하나의 활성 타이머만 가질 수 있도록 제약을 걸었습니다.\n\n이를 통해:\n\n- 중복 시작 방지\n- 상태 정합성 확보\n- 활성 타이머 조회 로직 단순화\n\n가 가능해졌습니다.\n\n### 3. MongoDB → DynamoDB 마이그레이션 판단\n\n초기에는 개발 서버의 MongoDB를 기반으로 기능을 구현했습니다. 다만 실제 서비스 단계에서는 AWS 생태계와의 연동, 기존 운영 설정과의 호환성, 고가용성을 함께 고려해야 했습니다. 이에 따라 운영 환경에서는 DynamoDB로 마이그레이션하는 방향으로 구조를 전환했습니다.\n\n이 경험을 통해 단순히 기능 구현에 그치지 않고, 운영 환경에 적합한 저장소 선택과 마이그레이션까지 고려하는 백엔드 설계의 중요성을 배웠습니다."
          },
          {
            "details": "## 어려웠던 점과 해결\n\n### 1. 공부 기록 조회가 느려지던 문제\n\nMongoDB에 타이머 로그, 즉 사용자가 언제 어떤 과목을 얼마나 공부했는지를 지속적으로 저장하고 있었는데, 데이터가 많이 쌓이면서 공부 기록 조회 성능이 크게 저하되었습니다.\n\n초기에는 `userId` 단일 인덱스만 사용하고 있었지만, 실제 조회 패턴은 사용자별 기록을 `startedAt` 기준으로 정렬해 가져오는 형태였습니다. 이를 반영해 `userId + startedAt` 복합 인덱스를 추가했고, `explain` 기준으로 주요 쿼리 실행 시간을 **77% 단축**했습니다.\n\n이 경험을 통해 조회 조건과 정렬 기준까지 함께 반영한 인덱스 설계가 중요하다는 점을 확인할 수 있었습니다.\n\n### 2. 무한 스크롤 API가 아래로 갈수록 느려지던 문제\n\n타이머를 사용하는 전체 사용자 목록을 조회하는 API는 무한 스크롤 방식으로 동작했는데, 기존에는 offset 기반 페이지네이션을 사용하고 있어 아래로 스크롤할수록 성능이 저하되는 문제가 있었습니다.\n\n이를 해결하기 위해 `startedAt` 기준의 키셋 페이지네이션을 적용했습니다. 가장 오래 공부한 사용자가 상위에 노출되도록 정렬 기준을 맞추고, 다음 페이지 조회 시에도 offset 누적 없이 안정적으로 데이터를 가져올 수 있도록 개선했습니다. 그 결과 `explain` 기준으로 기존 대비 **30% 성능 개선**을 확인할 수 있었습니다.\n\n### 3. 개발/운영 환경 간 시간대 차이로 기록 날짜가 어긋나던 문제\n\n개발 서버의 MongoDB는 KST 기준으로 동작했고, 운영 AWS 환경은 UTC 기준이어서 시간대 차이로 인해 공부 기록이 다른 날짜에 저장되는 문제가 실제로 발생했습니다.\n\n이를 해결하기 위해 Mongoose Hook을 활용해 KST 기준 입력이 들어올 경우 저장/조회 전 UTC 변환이 일관되게 수행되도록 구성했고, 이를 MongoDB 문서 전반에 공통 적용했습니다. 이를 통해 날짜 단위 학습 기록의 정합성을 확보할 수 있었습니다.\n\n### 4. Express 레거시 구조에서 비동기 에러 처리와 응답 형식이 불안정하던 문제\n\n기존 Express 구조에서는 `try-catch` 반복이 많았고, 누락된 비동기 에러가 전역 처리로 전달되지 않는 문제가 있었습니다. 또한 API마다 응답 형식이 달라 유지보수와 협업 비용이 발생했습니다.\n\n이를 해결하기 위해:\n\n- `async handler`를 도입해 반복적인 `try-catch`를 제거하고 비동기 에러 전파를 일관화\n- `success / data / error` 구조의 응답 미들웨어를 설계해 응답 형식을 통일\n\n했습니다. 이 구조는 제가 먼저 학생앱 서버에 직접 적용했고, 이후 문제 없음을 확인한 뒤 웹서버와 관리자 서버에도 동일한 방식이 확산되었습니다.\n\n### 5. 프론트-백 간 API 스펙 불일치 문제\n\n기존에는 요청/응답 검증 누락이 자주 발생해 프론트와 백엔드 간 소통 비용이 컸고, 연결 과정에서 타입 불일치 문제가 반복적으로 생겼습니다.\n\n이를 해결하기 위해 Joi 기반으로 요청/응답 검증을 모두 적용하고, `Joi to Swagger`를 사용해 Joi 스키마 기반으로 Swagger 명세를 자동화했습니다. 이 체계를 적용한 이후에는 프론트-백 연결 과정에서 타입 검증 문제는 한 번도 발생하지 않았습니다."
          },
          {
            "details": "## 구현 포인트\n\n- 서버가 타이머의 시작 시각과 활성 상태를 직접 관리하는 구조 설계\n- `tb_user_timer_status`의 유니크 제약으로 사용자별 단일 활성 타이머 보장\n- 조회 패턴에 맞춘 복합 인덱스 설계로 기록 조회 성능 개선\n- `startedAt` 기준 키셋 페이지네이션으로 무한 스크롤 API 최적화\n- Mongoose Hook을 활용한 UTC/KST 변환 자동화\n- Joi 기반 검증 + Swagger 자동화로 API 스펙 일관성 확보\n- MongoDB 기반 개발 환경에서 DynamoDB 기반 운영 환경으로 마이그레이션\n\n## 성과 및 배운 점\n\n- 실제 학생앱에 적용되는 기능으로, 사용자 환경을 고려한 서버 중심 타이머 구조를 설계했습니다\n- 데이터 누적에 따른 조회 성능 문제를 인덱스 최적화와 페이지네이션 개선으로 해결했습니다\n- 시간대 차이, 비동기 에러 처리, API 검증 누락처럼 운영 단계에서 발생하는 문제를 구조적으로 개선했습니다\n- 백엔드 개발자로서 상태 관리, 저장소 선택, 성능 최적화, 레거시 구조 개선, 협업 명세 자동화를 한 기능 안에서 통합적으로 경험했습니다\n\n## 한 줄 회고\n\n이 프로젝트를 통해 단순 타이머 기능 구현을 넘어, 실제 사용자 환경에서 동작하는 서버 상태 관리, 조회 성능 최적화, 운영 환경 대응까지 함께 설계하는 백엔드 개발의 중요성을 배웠습니다."
          }
        ],
        "SocialSharingHeading": "Share This Project"
      }
    },
    {
      "id": 4,
      "title": "베팅덕 (실시간 베팅 서비스)",
      "url": "betting-duck",
      "category": "Web Application",
      "img": "/images/web-project-2.jpg",
      "ProjectHeader": {
        "title": "🐤 베팅덕 - 승자만이 오리를 갖는다",
        "publishDate": "2024.10 – 2025.02",
        "tags": "Backend / Realtime / Redis"
      },
      "ProjectImages": [
        { "title": "베팅덕", "img": "/images/web-project-2.jpg" },
        { "title": "베팅덕", "img": "/images/ui-project-2.jpg" },
        { "title": "베팅덕", "img": "/images/web-project-1.jpg" }
      ],
      "ProjectInfo": {
        "ClientHeading": "About Project",
        "CompanyInfo": [
          { "title": "서비스", "details": "실시간 베팅 서비스" },
          { "title": "팀 구성", "details": "4명 (FE 2, BE 2)" },
          { "title": "담당", "details": "아키텍처 기획 · 인증/실시간 채팅/베팅 · Redis 메시지 큐 (기여도 30%)" },
          { "title": "기간", "details": "2024.10 ~ 2025.02" },
          { "title": "Repository", "details": "https://github.com/boostcampwm-2024/web14-betting-duck" }
        ],
        "ObjectivesHeading": "Objective",
        "ObjectivesDetails": "실시간 베팅의 짜릿함과 채팅의 소통 재미를 결합한 새로운 경험을 제공하는 웹 서비스. 코인으로 베팅하고, 실시간 채팅하며, 획득한 코인으로 오리를 구매해 마이페이지를 꾸미는 구조.",
        "Technologies": [
          {
            "title": "Tools & Technologies",
            "techs": ["TypeScript", "Node.js", "Redis", "WebSocket", "HINCRBY", "Pub/Sub", "MessageQueue"]
          }
        ],
        "ProjectDetailsHeading": "Challenge",
        "ProjectDetails": [
          {
            "details": "## 프로젝트를 시작하게 된 계기\n\n트위치(Twitch)는 전 세계적으로 인기 있는 실시간 스트리밍 플랫폼으로, 한국에서도 많은 사람들이 포인트 예측 시스템을 즐겨 사용했지만 트위치가 한국에서 철수하면서 더 이상 해당 기능을 사용할 수 없게 되었습니다. 대안으로 많은 사람들이 **치지직**이라는 스트리밍 플랫폼을 이용하기 시작했지만, 치지직에는 **베팅 서비스가 없는 점이 아쉽다**는 의견이 많았습니다.\n\n이러한 피드백을 바탕으로 **\"우리가 직접 베팅 시스템을 만들어보자!\"** 하는 생각을 계기로 프로젝트를 진행하게 되었습니다.\n\n## 서비스 소개\n\n베팅덕에서의 베팅은 방장이 정한 **두 가지 옵션 중 승산이 더 높아보이는 것에 덕(duck)코인을 거는 것**을 의미합니다. 베팅이 끝나면 코인을 잃거나, 배율만큼의 코인을 획득할 수 있습니다.\n\n- 코인으로 **베팅**하며 예측의 재미\n- **채팅으로 자유롭게 소통**\n- 베팅으로 얻은 코인으로 **귀여운 오리를 구매**해 나만의 마이페이지 꾸미기\n\n### 작은 웹 사이트의 베팅 시스템\n\n사용자가 원하는 컨텐츠(유튜브, 치지직, 생방송 스포츠 등)를 시청하며 베팅을 할 수 있도록, 별도의 컨텐츠를 제공하지 않고 작은 창으로 띄워두고 베팅만을 즐길 수 있도록 구현했습니다."
          },
          {
            "details": "## 서비스 둘러보기\n\n### 체험용 비회원 로그인 시스템\n\n비회원 로그인을 통해 사용자가 더 쉽게 서비스를 체험할 수 있도록 설계하였으며, IP 기반 제한을 통해 비회원 로그인 시스템 남용을 제한합니다.\n\n- 비회원이 로그인할 경우, 일반 회원과 구분하기 위해 \"익명의\"라는 이름이 자동 추가되며, 간단한 닉네임만으로 서비스를 이용할 수 있습니다.\n- 한 사람이 여러 아이디를 생성해 부당하게 베팅에 참여하는 것을 방지하기 위해, **IP 기반 제한을 구현하여 동일 IP에서는 이전에 생성된 닉네임으로만 접근이 가능**합니다.\n\n### 베팅 생성 페이지\n\n사용자가 직접 제목, 케이스1, 케이스2, 타이머, 최소 금액을 설정할 수 있도록 구성되었고, **유효한 입력 값이 입력된 경우에만** 방을 생성할 수 있습니다.\n\n### 실시간 베팅 시스템\n\n참여자는 소유한 코인의 일부를 원하는 옵션에 실시간으로 베팅할 수 있습니다. 서버와 클라이언트 간의 빠르고 안정적인 양방향 통신을 통해 **실시간 데이터 업데이트와 사용자 간의 동기화**를 제공합니다.\n\n### 실시간 채팅 기능\n\n방에 참여한 모든 사용자가 실시간으로 채팅에 참여할 수 있도록 설계되었습니다. 안정적인 양방향 통신으로 **메시지가 지연 없이 전달**됩니다."
          },
          {
            "details": "## 본인이 시도한 기술적 도전\n\n### 1. Redis를 이용한 메시지 큐 구현\n\nRedis의 List와 Pub/Sub을 활용하여 **메시지 큐를 구현**하였습니다.\n\nList를 사용해 메시지의 **순차적 처리를 보장**하고, Pub/Sub을 통해 **특정 시점에서 메시지를 소비**할 수 있도록 합니다.\n\n또한, ACK Timeout을 통해 **처리되지 않은 메시지를 감지하고 재처리**하는 로직을 구성하였습니다.\n\n이 메시지 큐는 베팅 종료 후, 사용자들의 오리 코인 업데이트 작업을 처리하는 데 사용되었습니다.\n\n### 2. 베팅 종료 API 응답 시간 개선\n\n베팅 종료 API의 성능 이슈로 인해 **사용자 경험이 저하된다는 피드백**을 받았습니다.\n\n해당 API의 Redis **탐색 알고리즘을 개선**하였습니다. 전체 키를 탐색하는 기존 방식 대신, 큐에 필요한 정보를 저장하여 즉시 반영할 수 있도록 하였습니다.\n\n또한 **메시지 큐를 이용**하여, 비동기적으로 DB IO가 가능하도록 하였습니다.\n\n결과적으로 API의 **응답 시간이 75% 개선**되었습니다.\n\n### 3. 캐시를 통한 성능 최적화\n\n**시스템 성능 최적화와 데이터 일관성 유지의 균형을 위해 다양한 캐시 전략을 설계하고 구현했습니다.**\n\n프로젝트에서는 **Read Through**, **Write Through**, **Write Back** 등 여러 캐시 전략을 상황에 맞게 적용했습니다.\n\n유저 정보 조회에는 **Read Through**를 사용해 빠른 응답 속도를 확보하고, 베팅 데이터 저장에는 **Write Through**를 적용해 데이터 정합성을 유지했으며, 실시간 베팅 정보 관리에는 **Write Back**을 활용해 쓰기 부하를 줄이면서 실시간성을 보장했습니다.\n\n### 4. Redis를 활용한 동시성 문제 해결\n\n실시간 베팅 처리에서 Redis와 `HINCRBY` 명령어를 활용해 동시성 문제를 해결했습니다. Redis의 단일 스레드 기반 특성과 `HINCRBY`의 원자성을 활용해 데이터 업데이트 과정에서 발생할 수 있는 불일치를 방지했습니다. 이를 검증하기 위해 100만 번의 동시 요청을 처리하는 테스트를 진행하며 안정성을 확인했습니다."
          }
        ],
        "SocialSharingHeading": "Share This Project"
      }
    },
    {
      "id": 5,
      "title": "CaTs ChatBot (챗봇)",
      "url": "cats-chatbot",
      "category": "Backend",
      "img": "/images/mobile-project-2.jpg",
      "ProjectHeader": {
        "title": "😺 CaTs ChatBot - 학술 동아리 챗봇 서비스",
        "publishDate": "2024.01 – 2024.05",
        "tags": "Backend / Chatbot / Automation"
      },
      "ProjectImages": [
        { "title": "CaTs ChatBot", "img": "/images/mobile-project-2.jpg" },
        { "title": "CaTs ChatBot", "img": "/images/mobile-project-1.jpg" },
        { "title": "CaTs ChatBot", "img": "/images/ui-project-2.jpg" }
      ],
      "ProjectInfo": {
        "ClientHeading": "About Project",
        "CompanyInfo": [
          { "title": "서비스", "details": "학술 동아리 CaTs 챗봇 서비스" },
          { "title": "팀 구성", "details": "2명 (FE 1, BE 1)" },
          { "title": "담당", "details": "백엔드 총괄 · 동아리 서버 구축 (기여도 80%)" },
          { "title": "기간", "details": "2024.01 ~ 2024.05 (운영·유지보수 ~2025)" }
        ],
        "ObjectivesHeading": "Objective",
        "ObjectivesDetails": "동아리 가입 신청을 카카오 챗봇으로 간편화한 서비스. 이후 사용자 피드백을 반영해 물품 대여, 학식 정보 제공 기능까지 확장.",
        "Technologies": [
          {
            "title": "Tools & Technologies",
            "techs": ["Python", "Django", "MySQL", "Kakao Chatbot", "cron", "mysqldump", "requests"]
          }
        ],
        "ProjectDetailsHeading": "Challenge",
        "ProjectDetails": [
          {
            "details": "## 프로젝트를 시작하게 된 계기\n\n저희 프로젝트는 기존 동아리 가입 방식에 아쉬움을 느껴 시작하게 되었습니다.\n\n기존에는 구글폼 또는 수기로 동아리 가입 신청을 받았습니다. 이는 타 사이트로의 이동, 많은 가입자들의 정보를 직접 검토 등 비효율적인 문제가 있었습니다.\n\n따라서 저희는 **\"동아리 자체 가입폼을 만들어 보자!\"**라는 목표를 가지고 프로젝트를 시작하게 되었습니다.\n\n## 서비스 소개\n\nCaTs의 챗봇은 동아리 가입 신청을 보다 간편하게 하기 위해 개발된 서비스입니다. 이후 사용자 피드백을 통해 여러 기능들을 추가하였습니다.\n\n- 챗봇을 이용하여 간편하게 CaTs 가입 신청을 할 수 있습니다.\n- CaTs 부원의 경우 동아리 물품 대여를 신청할 수 있습니다.\n- 충북대학교와 기숙사의 학식 정보를 제공받을 수 있습니다."
          },
          {
            "details": "## 서비스 둘러보기\n\n### 카카오 챗봇\n\n많은 사람들이 손쉽게 접근할 수 있도록 하기 위해 **카카오 챗봇 플랫폼**을 사용하였습니다. 사용자는 단순히 카카오톡에서 CaTs 채널을 추가하여, 바로 서비스를 이용할 수 있습니다.\n\n### 동아리 가입 신청\n\n챗봇을 이용하여 몇가지 사항을 입력하면 간편하게 **가입 신청**을 할 수 있습니다. 학번 입력 시, 동아리 면접 결과 또한 확인할 수 있습니다.\n\n### 물품 대여\n\n부원의 경우 **동아리 비품을 손쉽게 대여**할 수 있습니다. 카카오톡 ID를 통해 계정이 관리되기에, 별도의 **인증 과정 없이** 물품 대여가 가능합니다.\n\n### 학식 정보 제공\n\nCaTs 챗봇 서버는 주기적으로 **학식 정보를 크롤링**해 가져옵니다. 식당 및 요일을 선택하면, 해당 날짜의 학식 메뉴를 확인할 수 있습니다."
          },
          {
            "details": "## 본인이 시도한 기술적 도전\n\n### 1. 데이터베이스 시스템 백업 체계\n\n챗봇을 구성한 백엔드 API는 동아리 어플리케이션인 CaTs-App 프로젝트에도 그대로 사용되었습니다.\n\n이때 CaTs-App의 관리자 페이지에서, 동아리 지원자의 **정보를 실수로 삭제**하게 되는 일이 있었습니다. 당시 Django의 모든 로그를 파헤쳐, 해당 학생의 정보를 복구시켰던 헤프닝이 있었습니다.\n\n이후 저는 정기적인 데이터베이스 **백업 체계가 필요**하다고 느꼈고, 파이썬의 `subprocess`를 이용하여 **`mysqldump` 명령**을 실행하고, 생성된 **sql 파일을 NAS에 저장**하는 스크립트를 만들었습니다. 해당 스크립트를 **cron에 등록**하여 주기적으로 백업이 되도록 시스템을 구성하였습니다.\n\n### 2. 학식 정보 크롤링 최적화\n\n기존에는 `selenium`을 이용하여 학식 메뉴를 동적 크롤링하고 있었습니다. 하지만 해당 방식은 **CLI 환경**에서 구동되기에 적합하지 않았고, **자원을 너무 많이 사용**하는 문제가 있었습니다.\n\n따라서 저는 해당 사이트의 **네트워크 패킷을 분석**하였고, 학식 정보를 가져오는 API 주소를 알게 되었습니다. 이를 이용하여 `request` 라이브러리를 통해 크롤링 시간을 2300ms → 170ms로 **93% 가량 최적화**하였습니다.\n\n### 3. 서비스 유지/보수 및 기능 추가\n\n해당 챗봇은 2023.08에 오픈한 뒤 약 **18개월 동안 유지 보수**를 도맡아 하였고, 인수인계하여 현재도 계속해서 서비스가 되는 중입니다.\n\n초기에는 동아리 홍보 링크와 가입 신청 기능만이 존재했지만, 이후 동아리원들의 피드백을 받아 물품 대여 / 학식 정보 제공 등의 **기능을 추가**하게 되었습니다."
          }
        ],
        "SocialSharingHeading": "Share This Project"
      }
    },
    {
      "id": 6,
      "title": "커스텀 웹 프레임워크",
      "url": "custom-web-framework",
      "category": "Web Application",
      "img": "/images/web-project-1.jpg",
      "ProjectHeader": {
        "title": "커스텀 웹 프레임워크",
        "publishDate": "2025.03 – 2025.06",
        "tags": "Backend / Framework / Low-level"
      },
      "ProjectImages": [
        { "title": "Custom Web Framework", "img": "/images/web-project-1.jpg" },
        { "title": "Custom Web Framework", "img": "/images/ui-project-1.jpg" },
        { "title": "Custom Web Framework", "img": "/images/web-project-2.jpg" }
      ],
      "ProjectInfo": {
        "ClientHeading": "About Project",
        "CompanyInfo": [
          { "title": "프로젝트 성격", "details": "개인 프로젝트 (기여도 100%)" },
          { "title": "담당", "details": "프레임워크 코어 · MVC 애플리케이션 · 배포 전반" },
          { "title": "기간", "details": "2025.03 ~ 2025.06" },
          { "title": "배포", "details": "Linux 홈 서버 + Docker Compose + Nginx" },
          { "title": "Repository", "details": "https://github.com/LLagoon3/Custom_Web_Framework" }
        ],
        "ObjectivesHeading": "Objective",
        "ObjectivesDetails": "Node.js `net` 모듈 기반으로 HTTP 요청 사이클을 직접 구현하고, 그 위에 Express 유사 구조의 커스텀 웹 프레임워크와 MVC 웹 애플리케이션을 구축한 개인 프로젝트.",
        "Technologies": [
          {
            "title": "Tools & Technologies",
            "techs": ["TypeScript", "Node.js net module", "HTML", "CSS", "JavaScript", "MySQL", "Redis", "Lua Script", "Docker Compose", "Nginx", "Linux"]
          }
        ],
        "ProjectDetailsHeading": "Challenge",
        "ProjectDetails": [
          {
            "details": "## 문제 의식\n\n이 프로젝트는 단순히 Express를 사용하는 데서 끝나지 않고, **HTTP 요청이 실제로 어떤 흐름으로 파싱되고, 라우터와 미들웨어가 어떤 방식으로 연결되며, 응답이 어떤 구조로 만들어지는지 직접 구현해보기 위해** 시작한 프로젝트입니다.\n\n특히 Express 학습 과정에서 프레임워크의 사용자 관점뿐 아니라 내부 구현 관점까지 이해하고 싶었고, 이를 위해 Node.js `net` 모듈 위에서 HTTP 서버와 프레임워크 코어를 직접 설계했습니다. 이후 그 위에 MVC 구조의 게시판 애플리케이션을 올려, 프레임워크 계층과 서비스 로직 계층을 분리하는 방식까지 검증했습니다."
          },
          {
            "details": "## 구현 범위\n\n### 프레임워크 코어\n\n- HTTP/1.1 요청 파싱\n- 모든 HTTP 메서드 지원\n- Request / Response 객체 구현\n- Express 유사 `Application / Router / Middleware` 구조 구현\n- 전역 / 경로별 미들웨어 지원\n- 동적 라우팅, 와일드카드, 중첩 라우팅 지원\n- `send / json / redirect / status / setHeader` 응답 메서드 지원\n- 정적 파일 서빙 구현\n- Redis 기반 세션 저장소 구현\n- JSON / form-urlencoded body parsing 구현\n- 대용량 파일 스트리밍 처리 지원\n\n### 애플리케이션 계층\n\n- MVC 구조의 게시판 애플리케이션 구현\n- 게시글 CRUD\n- 로그인 및 세션 기반 인증\n- 파일 업로드 기능 구현\n- 개인 서버 배포 및 실제 실행 환경에서 검증"
          },
          {
            "details": "## 핵심 기술적 판단\n\n### 1. `net` 모듈 기반으로 HTTP 요청 처리 흐름을 직접 구현\n\n이 프로젝트에서 가장 중요하게 가져간 목표는 HTTP 요청 사이클을 추상화된 프레임워크 밖에서 직접 이해하는 것이었습니다. 이를 위해 Node.js `http` 모듈이 아니라 더 하위 레벨인 `net` 모듈을 사용해 요청을 직접 파싱하도록 설계했습니다.\n\n이 과정에서 다음을 직접 다뤘습니다.\n\n- 헤더와 바디를 구분하는 요청 파싱 로직\n- 헤더와 바디가 하나의 chunk에 같이 들어오는 경우 처리\n- body가 여러 chunk로 나뉘어 도착하는 경우 처리\n- 스트림 flow 모드 전환 시점 제어\n- `data` / `end` 이벤트 기반 body 수집 및 파싱\n- 대용량 데이터 전송과 파일 스트리밍 처리\n\n이를 통해 단순히 웹 프레임워크를 사용하는 수준을 넘어, **Node.js 런타임과 네트워크 레벨에서 요청이 어떻게 흘러가는지 직접 구현하고 디버깅한 경험**을 만들 수 있었습니다.\n\n### 2. Express 유사 구조의 Application / Router / Middleware 설계\n\n학습 목적 프로젝트였지만 단순 토이 서버로 끝내지 않기 위해, Express와 유사한 사용성을 갖는 구조를 목표로 했습니다. 이에 따라 `Application`, `Router`, `Middleware`, `Request`, `Response` 계층을 나누고, 프레임워크 사용자 입장에서 익숙한 방식으로 사용할 수 있도록 설계했습니다.\n\n주요 설계 포인트는 다음과 같습니다.\n\n- `app.use()` 기반 전역/경로별 미들웨어 체인\n- 동적 라우팅, 와일드카드, 중첩 라우팅 지원\n- `req.params`, `req.body`, `req.session` 등 확장 가능한 Request 구조\n- `send`, `json`, `redirect`, `status`, `setHeader` 등 Response 인터페이스 제공\n- 정적 파일 서빙과 세션 미들웨어를 코어 구조에 자연스럽게 연결\n\n이 프로젝트를 통해 프레임워크 계층과 애플리케이션 계층의 책임을 분리하고, **재사용 가능한 구조를 먼저 설계한 뒤 그 위에 서비스 로직을 얹는 방식**을 직접 경험했습니다."
          },
          {
            "details": "## 어려웠던 점과 해결\n\n### 1. Request / Body 파싱 과정에서 대용량 데이터 전송이 깨지던 문제\n\n실제 서비스 테스트 중 대용량 데이터 파일 전송이 정상적으로 처리되지 않는 문제를 확인했습니다. 원인을 추적한 결과, 요청이 항상 한 번에 오지 않고 여러 chunk로 분리될 수 있으며, 헤더와 바디가 동일 chunk 안에 함께 들어오는 경우도 있다는 점이 핵심이었습니다.\n\n이를 해결하기 위해:\n\n- `\\r\\n\\r\\n` 기준으로 헤더와 바디를 분리\n- 헤더 파싱 직후 남아 있는 body 조각을 별도 버퍼에 보관\n- 이후 들어오는 chunk를 누적해 `Content-Length` 기준으로 수신 완료 여부를 판단\n- `data` / `end` 이벤트 등록 시점과 flow 모드 전환을 제어\n\n하는 방식으로 구조를 수정했습니다.\n\n그 결과 단순한 JSON 요청뿐 아니라 **대용량 파일 전송과 스트리밍 상황에서도 안정적으로 body를 수신**할 수 있었습니다.\n\n### 2. Redis API 캐시를 적용했는데 오히려 기대만큼 빨라지지 않던 문제\n\n게시판 조회 API에 Redis 캐시를 적용했지만, 초기에는 기대한 만큼 성능이 개선되지 않았습니다. 배포 환경에서 평균 응답 시간을 확인해보니 병목이 단순 DB 조회가 아니라 **캐시 키 생성 과정의 `JSON.stringify`와 응답 직렬화/역직렬화 비용**에도 있음을 확인했습니다.\n\n이를 해결하기 위해:\n\n- 캐시 키 생성 로직 단순화\n- 불필요한 `JSON.stringify` 제거\n- 응답 방식 개선으로 `JSON.parse` 비용 감소\n\n를 적용했고, 결과적으로 게시판 조회 API 평균 응답 시간을 **15ms → 6ms**로 줄일 수 있었습니다.\n\n이 경험을 통해 캐시는 단순히 붙인다고 빨라지는 것이 아니라, **직렬화 비용과 키 설계까지 포함해 최적화해야 한다**는 점을 배웠습니다.\n\n### 3. 정적 파일 캐시에서 Redis보다 인메모리 캐시가 더 적합했던 문제\n\n정적 파일 응답에도 Redis 캐시를 적용해봤지만, 실제로는 운영체제의 파일 캐시와 비교했을 때 Redis 접근 및 Buffer-String 변환 비용 때문에 기대 이하의 결과가 나왔습니다.\n\n이를 바탕으로 Redis 대신 `MemoryCache`를 직접 구현해 정적 파일 캐시에 적용했고, 정적 파일 응답 시간을 **64ms → 30ms**로 개선했습니다.\n\n이 과정에서 얻은 결론은 다음과 같습니다.\n\n- API JSON 응답 캐시와 정적 파일 캐시는 같은 전략으로 다루면 안 됨\n- 캐시 계층은 데이터 형태와 접근 비용에 따라 달라져야 함\n- 정적 파일에서는 인메모리 캐시가 더 적합할 수 있음\n\n### 4. 조회수 처리에서 동시성과 DB 반영 시점 문제\n\n게시물 조회수는 업데이트가 빈번하기 때문에 Redis를 이용해 메모리에서 먼저 처리하고, 일정 threshold 이상 차이가 날 때 DB에 반영하는 구조를 설계했습니다. 다만 초기 구현에서는 조회수 초기화와 DB 반영 시점에서 race condition 가능성이 있었습니다.\n\n이를 해결하기 위해:\n\n- 초기화에는 `hSetNX`를 사용해 원자적으로 처리\n- 증가에는 `hIncrBy`를 사용\n- 최종적으로 Lua Script를 적용해 조회수 증가와 조건부 DB 반영 판단을 원자적으로 처리\n\n하도록 개선했습니다.\n\n이를 통해 단순 캐시 적용을 넘어, **동시성 문제와 데이터 반영 전략까지 고려한 조회수 처리 구조**를 설계할 수 있었습니다."
          },
          {
            "details": "## 구현 포인트\n\n- Node.js `net` 모듈 기반 HTTP 서버 및 프레임워크 코어 직접 구현\n- Express 유사 구조(Application / Router / Middleware / Request / Response) 설계\n- 전역/경로별 미들웨어, 와일드카드/중첩 라우팅 지원\n- Redis 기반 세션 저장 및 로그인 인증 기능 구현\n- MVC 게시판 애플리케이션과 프레임워크 계층 분리\n- Redis / 인메모리 캐시 비교를 통한 API 및 정적 파일 응답 최적화\n- Lua Script 기반 조회수 동시성 문제 해결\n- Linux 홈 서버 + Docker Compose + Nginx 리버스 프록시 배포\n\n## 배포 환경\n\n- Linux 기반 홈 서버에 Docker Compose로 배포\n- Nginx 리버스 프록시 구성\n- MySQL / Redis 등 의존 서비스도 컨테이너로 운영\n- 학습용 배포이면서 동시에 멘토 피드백을 받기 위한 검증 환경으로 활용\n\n## 성과 및 배운 점\n\n- 웹 프레임워크를 사용하는 수준을 넘어, 내부 동작을 직접 설계하고 구현하는 경험을 쌓았습니다\n- HTTP 요청 파싱, 스트림 처리, 미들웨어 체인, 응답 객체 설계 등 Node.js 런타임/네트워크 레벨에 대한 이해를 깊게 가져갈 수 있었습니다\n- 캐시와 조회수 처리에서 단순 적용이 아니라 병목 분석, 동시성, 반영 전략까지 고려하는 백엔드 관점을 경험했습니다\n- 프레임워크 계층과 애플리케이션 계층을 분리해 구조적으로 사고하는 습관을 얻었습니다\n\n## 한 줄 회고\n\n이 프로젝트를 통해 웹 프레임워크의 내부 동작을 직접 구현하면서, 단순 기능 개발을 넘어 **Node.js 런타임·네트워크 레벨의 이해, 구조 설계, 성능 최적화 역량**을 함께 키울 수 있었습니다."
          }
        ],
        "SocialSharingHeading": "Share This Project"
      }
    }
  ]
}
