tech.inflab.com 최신 글
tech.inflab.com에서 발행한 기술 글을 테크곳간이 수집해 최신순으로 보여드립니다.
최신 글
Jenkins 배치 220개를 Temporal로 옮기며 고민한 것들
어느 날 한 개발자분께 메시지가 왔습니다. 배치가 실행되지 않아 확인해 보니 Jenkins의 디스크가 가득 차 멈춰 있었습니다. 빌드 이력 보존 설정(logRotator…
2026년 10월 20일
인프런은 영상 화면 속 글자도 번역합니다
여러분들은 예상치 못한 먼 타지의 영상에서 의외의 아이디어를 얻어본 적, 있으신가요? 인프런은 언어의 장벽을 넘어 폭 넓은 지식을 전달해드리기 위해 자막, 더빙에 이어 영상 화면에 있는 글자까지 번역하는 영상 화면 번역 기능을 준비하고 있습니다. 최근에 인프런에서 공개한 DeNA × AI Day 2026를 포함해, 앞으로도 인프런에서 각국의 컨퍼런스 영상과 강의들을 보실 수 있을 텐데요. 이미 몇 강의에는 화면 번역 기능이 적용되어 있고 새로 추가되는 일부 강의에도 실험적으로 적용될 예정입니다. 그러하여 이번 글에서는 이 기능을 구현하며 고민한 과정을 공유드리려 합니다. 결과 예시 적용 전 적용 후 원본: DeNA × AI Day 2026, 'AI × 업무개혁: AI Workspace 프로젝트의 에이전트 활용 최전선' 번역된 영상을 보여주는 방법 영상 화면 속 글자를 번역하는 기능을 어떻게 구현할 수 있을까요? 저희는 영상의 글자를 번역본으로 바꿔 보여주기 위해 미리 준비한 번역을 재생 중인 화면 위에 겹쳐 그리는 오버레이 방식을 선택했습니다. 원본 영상은 기존 방식 그대로 재생하면서 글자 뒤쪽 영역만 주변 배경과 같은 색의 박스로 덮어 원문을 가리고, 그 위에 번역된 글자를 같은 자리에 얹는 것입니다. 원본 영상 위에 배경색 박스와 번역 텍스트, 두 레이어를 겹쳐 그립니다. 이 방식이라면 영상 인코딩과 상관없이 새로운 언어를 쉽게 추가할 수 있고, 오역을 발견하거나 번역 파이프라인을 개선했을 때도 데이터만 수정하면 바로 반영되니 적은 비용으로 품질을 유연하게 다듬어 갈 수 있습니다. 번역된 텍스트 데이터만 별도로 응답하기에 서버, 클라이언트 모두 네트워크 전송 용량 부담이 적다는 점 또한 큰 이점이었습니다. 구글 렌즈에서도 원문 글자가 배경색으로 가려지고 그 자리에 번역된 글자를 덮어 보여줍니다. 인프런의 화면 번역도 이와 같은 원리입니다. 다만 비추는 즉시 답해야 하는 렌즈와 달리 영상을 미리 분석해 두기 때문에 처리 시간의 제약이 적고, 품질을 더 최적화하거나 잘못된 부분을 사전에 보정할 수 있다는 차이점이 있습니다. 출처: https://blog.google 내부적인 산출물은 JSON 형식의 데이터입니다. { "events": [ { "start": 12.0, "end": 47.0, "texts": [ { "text": "Part 2. From RNN to Transformer", "translations": { "ko": "제2부 RNN에서 Transformer까지" }, "bbox": [104, 216, 632, 258], "bg_color": [16, 42, 67], "text_color": [236, 240, 244] } ] } ] } events는 화면 구성이 유지되는 시간 구간이고, 각 텍스트는 위치(bbox), 원문, 언어별 번역, 그리고 배경색과 글자색을 가집니다. 텍스트를 하나의 평평한 배열로 두는 방법도 있지만 시간 구간(event)으로 묶은 것은 플레이어의 탐색 최적화를 위함입니다. 영상의 특성상, 화면이 전환될 때 모든 텍스트가 한 번에 바뀌는 경우가 잦기 때문입니다. 플레이어는 재생 시각에 해당하는 이벤트를 찾아서, 영상 위에 bbox 위치대로 bg_color로 채운 박스를 깔고 그 위에 text_color로 번역 텍스트를 그립니다. 원문 글자는 배경색 박스에 가려지고, 같은 자리에 번역이 나타나는 구조입니다. 여기까지가 번역된 영상을 보여주는 방법이었습니다. 이제 남은 문제는 이 JSON 데이터를 어떻게 만들어내느냐입니다. 텍스트 추출 영상은 결국 정지된 화면, 즉 프레임의 연속이므로, 먼저 화면 한 장을 번역하는 것부터 생각해 보겠습니다. 초기 구상은 단순하게 떠올릴 수 있습니다. 화면 속 텍스트를 OCR(광학 문자 인식)로 읽는다. 읽은 텍스트를 번역한다. 번역 결과를 원래 텍스트와 같은 위치에 오버레이한다. 세 단계만으로도 화면 번역 자체는 동작합니다. 가. 번역이 불필요한 텍스트는 제외한다 PPT나 문서 같은 일반적인 번역 대상과 달리, 강의 영상 도메인에는 특유의 어려움이
2026년 9월 10일
사내 LLM 요청을 한곳으로 모은 AI Gateway 구축기
안녕하세요, 데브옵스 엔지니어 포카입니다. 인프랩에서는 자막 번역, 강의 상세 생성, 학습 에이전트, 커뮤니티 답변 등 꽤 많은 곳에서 LLM API를 쓰고 있습니다. 처음에는 각 서비스가 필요한 제공 업체에서 API…
2026년 9월 8일
학습에이전트 - Building the Brain
Series 학습에이전트 - The Evolving Shape of Learning 학습에이전트 - Building the Brain 안녕하세요. 인프랩의 BE 개발자 약풍, 로켓입니다. 지난 글에서는 학습 에이전트가 왜 필요했는지, 어떤 기능이 있는지, 제품 측면에서 어떤 고민을 했는지 살펴보았습니다. 이번 글부터는 학습 에이전트의 각각의 영역을 구체적으로 살펴보려 합니다. 첫 번째로, 학습 에이전트의 백엔드 시스템을 어떻게 설계하고 구현했는지 이야기해 보겠습니다. 기술스택 선정 학습 에이전트의 트래픽을 서빙할 서버는 다음과 같은 요구사항들을 충족해야 했습니다. 1.다량의 채팅 트래픽을 서빙해야 한다 에이전트의 기능은 LLM 채팅 플랫폼 위에서 동작해야 합니다. 따라서 학습 에이전트 서버는 대량의 채팅 트래픽을 감당해야 합니다. 서버 관점에서 보면, 하나의 요청이 오랜 시간 동안 쓰레드를 점유해서는 안 됩니다. LLM 응답은 SSE(Server-Sent Events)를 통해 토큰 단위로 실시간 스트리밍되며, 하나의 대화 요청이 수십 초간 연결을 유지합니다. 다수의 유저들이 동시에 대화할 때도 안정적으로 응답해야 하므로, 전통적인 쓰레드 풀 모델로는 쓰레드 고갈이 발생할 수 있습니다. 2.복잡한 AI 파이프라인을 설정할 수 있어야 한다 학습 에이전트는 채팅 기능 뿐만 아니라 다양한 방식으로 확장될 수 있습니다. 복잡한 AI 파이프라인을 설정할 수 있고, 관리하기 쉬운 형태로 코드를 작성할 수 있어야 합니다. 구체적으로 사용자 질문 → 의도 분류 → 관련 강의 검색(RAG) → LLM 응답 생성 → 후처리 같은 다단계 파이프라인을 구성해야 합니다. 각 단계에서 조건 분기, 에러 핸들링, 재시도 등이 필요할 수 있습니다. 이러한 목적을 이룰 수 있는 백엔드 기술 스택을 조사하기 시작했습니다. Spring MVC + Virtual Thread 먼저 팀원들이 친숙하게 사용하고 있는 프레임워크인 Spring MVC를 후보에 올렸습니다. 단, 일반적인 Spring MVC로는 “다량의 채팅 트래픽을 서빙해야 한다”는 요구사항을 충족할 수 없습니다. Spring MVC는 기본적으로 요청당 쓰레드를 1개씩 할당하기 때문에, LLM 트래픽 처리 시 쓰레드 고갈이 금방 발생할 수 있습니다. 이러한 문제를 해결하기 위해 Virtual Thread 사용을 고려할 수 있습니다. Virtual Thread는 블로킹 I/O(DB 쿼리, HTTP 호출, 파일 읽기 등)를 만나면 캐리어 쓰레드(플랫폼 쓰레드)에서 자동으로 분리(unmount)됩니다. 그러면 캐리어 쓰레드는 다른 Virtual Thread를 처리할 수 있습니다. 덕분에 동기적, 순차적 코드 스타일을 유지하면서도, 리액티브 프로그래밍 없이 높은 동시성을 확보할 수 있습니다. JAVA 24 버전까지는 Virtual Thread synchronized 메서드 사용 시 캐리어 스레드가 블록되는 이슈가 있었지만, JAVA 25부터는 이러한 pinning 이슈가 상당 부분 해결되었습니다. pinning이란 ? 장점 단점 안정적인 Spring 생태계 검증된 라이브러리를 변경 없이 사용 가능 Virtual Thread는 비교적 새로운 기술 라이브러리 호환성(pinning 등)에 대한 검증 필요 낮은 추가 학습 비용 기존 Spring MVC 개발 경험을 그대로 활용 SseEmitter API의 번거로움 Webflux의 Flux<ServerSentEvent> 대비 많은 보일러플레이트 Spring Webflux Spring Webflux는 동시에 많은 연결을 최소한의 리소스로 유지해야 할 때 강점을 발휘합니다. Webflux는 Netty 기반 이벤트 루프 위에서 동작하며, Project Reactor의 Mono/Flux 타입을 통해 비동기, 논블로킹 스트림을 처리합니다. 요청마다 쓰레드를 1개 할당하는 전통적 서블릿 모델과 달리, Webflux는 소수의 이벤트 루프 쓰레드가 수천~수만 개의 동시 연결을 번갈아 처리합니다. 장점 단점 SSE/스트리밍에 최적화된 설계 이벤트 스트림을 프레임워크에서 지원
2026년 6월 30일
학습에이전트 - The Evolving Shape of Learning
Series 학습에이전트 - The Evolving Shape of Learning 학습에이전트 - Building the Brain 진화하는 학습의 형태 Series 학습에이전트 - The Evolving Shape of Learning 학습에이전트 - Building the Brain 약 7만 년 전 — 언어, 그리고 구전 학습의 시작 약 7만 년 전, 호모 사피엔스에게 ‘인지 혁명’이라 불리는 도약이 일어났습니다. 인류가 다른 동물과 갈라선 첫 분기점이었습니다. 눈앞에 없는 것, 추상적인 개념, 아직 일어나지 않은 일까지 표현하는 상징적 언어가 등장한 것입니다. 그 언어가 구체적으로 어떤 모습이었는지는 기록이 없어 알 수 없습니다. 다만 분명한 건, 이때부터 지식이 입에서 입으로, 세대에서 세대로 전해지기 시작했다는 사실입니다. 약 5천 년 전 — 문자의 시대 인류 학습 역사상 가장 거대한 도약이 찾아왔습니다. 수메르의 설형문자, 이집트의 상형문자. 사람의 기억에만 의존하던 지식이 비로소 기록되기 시작했습니다. 지식을 가진 이가 세상을 떠나도, 그 지식은 남았습니다. 문자는 지식을 시간과 공간의 제약에서 해방시켰습니다. 수천 년 전 생을 마감한 학자의 생각을, 우리는 오늘 읽고 배웁니다. 15세기 — 인쇄술과 지식의 민주화 왕족과 성직자 같은 극소수 권력층의 전유물이던 고급 지식이, 인쇄술의 대량 생산을 통해 모두의 것이 되었습니다. 누구나 글을 배워 성경을 읽고, 누구나 글을 읽어 과학을 익히게 되었습니다. 지식이 처음으로 ‘대중’을 만난 순간이었습니다. 20세기 말 — 인터넷, 그리고 초연결의 시대 월드 와이드 웹의 보급, 그리고 뒤이은 스마트폰의 대중화는 지식을 얻는 방식을 통째로 바꿔놓았습니다. 과거의 학습이 ‘지식을 머릿속에 담아두는 것’이었다면, 이제 학습은 ‘어디서 정보를 찾고(Search), 어떻게 활용하는가’의 문제가 되었습니다. 지식의 권위는 학교와 교사에서 인터넷 공간 전체로 흩어졌습니다. 그리고 지금, 우리는 또 하나의 변곡점에 서 있습니다. 인터넷이 지식의 거대한 도서관을 열어주었다면, AI는 그 도서관 안에서 인간과 함께 생각하고 대화합니다. 학습은 이제 정보를 검색(Search)하는 것을 넘어, AI와 대화하고 상호작용하며 답을 만들어가는 과정으로 진화하고 있습니다. 수천 명의 학생이 똑같은 교과서로 배우던 시대는 끝나가고 있습니다. AI는 나의 이해도와 흥미, 학습 속도를 파악해 오직 나만을 위한 학습 여정을 설계합니다. 단순한 암기와 요약은 이제 AI가 더 잘합니다. 그래서 현대의 학습은 ‘얼마나 많이 아는가’가 아니라, ‘AI에게 얼마나 날카로운 질문을 던질 수 있는가’, 그리고 ‘AI의 답을 어떻게 비판적으로 검증하고 창의적으로 융합하는가’로 다시 정의되고 있습니다. 학습 플랫폼 많은 학습 플랫폼이 사실 인터넷 시대가 열어준 가능성 위에서 태어났습니다. 학교의 물리적 한계도, 시간의 제약 없이 누구나 원하는 지식에 접속할 수 있게 된 것입니다. 인프런 역시 이 거대한 흐름 위에서 성장의 기회를 평등하게라는 비전을 들고, 지식의 대중화에 한 걸음을 보태왔습니다. 좋은 지식이 더 많은 사람에게 닿도록. 그리고 이제, 새로운 시대에 응답해야 할 때입니다. 인프런은 단지 강의라는 상품을 판매하고 끝내는 곳이 아닙니다. 학습을 위한 플랫폼입니다. 사람들이 더 잘 배우도록 도와야만 합니다. 그리고 학습의 형태가 바뀌고 있다면 우리가 그 변화를 맞이해야 합니다. 학습 에이전트 서론이 조금 길었지만, 이제 인프런에서는 에이전트와 함께 학습할 수 있습니다. Series 학습에이전트 - The Evolving Shape of Learning 학습에이전트 - Building the Brain 인프런 학습 에이전트, 강의 영상을 혼자 시청하는 것을 넘어, 지금 보고 있는 화면과 자막, 커리큘럼과 퀴즈까지 모두 아는 짝꿍이 곁에 있습니다. 막히는 순간 바로 물어보고, 이해한 만큼 다음으로 나아가는, 막힘없는 학습 여정을 함께 만들어갑니다. Series 학습에이전트 - The Evolving Shap
2026년 6월 26일
소프트웨어 공급망 공격, pnpm으로 1차 방어선 구축하기
안녕하세요. 인프랩 백엔드 개발자 후니입니다. 최근 공급망 공격(Supply-Chain-Attack)에 대한 보안 사고가 연이어 발생하고 있습니다. 올해 초 가장 논란이 되었던 LiteLLM…
2026년 6월 18일
AI로 디자인시스템 마이그레이션하기 (1): 여정의 시작
안녕하세요. 인프랩 프론트엔드 개발자 고슈입니다. 현재 인프랩 프론트엔드 팀은 20여 개의 애플리케이션에서 사용하고 있는 디자인시스템을 최신 버전으로 마이그레이션하는 작업을 진행하고 있습니다. 정확히는 인프랩의 디자인시스템은 오픈 소스 Mantine…
2026년 6월 6일
MFE 환경에서 공통 헤더(GNB) 개편하기
안녕하세요. 인프랩 프론트엔드 개발자 고슈입니다. 작년 8월, 인프런의 헤더(GNB)가 새롭게 단장했습니다! 🎉 인프런의 헤더는 여러 프론트엔드 환경에서 동작해야 하는 복잡한 구조로 이루어져 있어, 기존에도 여러 문제가 있었는데요. 1. 헤더를 왜 개편하게 되었는가 기존 헤더 개편된 헤더 인프런의 헤더는 런칭 이래로 변경된 적이 거의 없었는데요. 현재 인프런은 강의, 챌린지, 멘토링, 클립, 커뮤니티를 핵심 서비스로 정의하고 있습니다. 디자인 개편 자체는 이미 완료되었지만, 그 과정이 단순하지만은 않았는데요. 2. MFE 아키텍처와 Module Federation 2-1. 인프랩 팀의 프론트엔드 아키텍처 모놀리식 아키텍처에서 도메인 별로 프로젝트 분리 모놀리식에서 도메인 분리로 초기 인프런은 내부에서 Antman이라 부르는, 자체 자바스크립트 프레임워크 기반의 모놀리식 프로젝트로 서비스를 운영하고 있었는데요. 서비스 규모가 커지면서 개발자 수도 늘고, 빌드 속도도 느려지기 시작했습니다. 가장 큰 문제는 하나의 서버에 프론트엔드와 백엔드가 함께 올라가 있다 보니, 한쪽에서 장애가 발생하면 서비스 전체가 마비된다는 점이었습니다. 이를 해결하기 위해 리액트로 마이그레이션을 진행하면서, 도메인별로 프로젝트를 분리하게 되었습니다. 프론트엔드 기준으로 모노레포도 있고, 폴리레포(싱글레포)도 있고, 형태는 다양하지만 각 도메인이 독립적으로 운영되는 마이크로프론트엔드(MFE)구조입니다. 이렇게 분리된 애플리케이션들은 리버스 프록시를 통해 하나의 서비스로 동작하고 있습니다. 사용자가 접속한 URL 경로에 따라 프록시가 적절한 애플리케이션으로 요청을 라우팅해 주는 방식인데요. 리버스 프록시란, 클라이언트의 요청을 받아 내부의 여러 서버 중 적절한 곳으로 전달해 주는 중간 서버입니다. 사용자는 하나의 도메인에 접속하지만, 실제로는 뒤에 있는 여러 애플리케이션 중 하나가 응답하게 됩니다. 백엔드 역시 서버가 분리되면서 한쪽 장애가 전체로 퍼지는 문제는 해소되었고, 프론트엔드 관점에서도 각 애플리케이션이 독립적으로 빌드·배포되기 때문에 런타임 오류나 배포 실패가 다른 서비스에 영향을 주지 않게 되었습니다. 패키지별로 다양한 환경을 구성할 수 있고, 코드 충돌도 줄어들었습니다. 분리했더니, 더 바빠졌다 하지만 공통으로 사용하는 컴포넌트가 있는 경우엔, 변경할 때마다 모든 패키지에 반영하고 배포해야 한다는 번거로움이 있었는데요. 특히 헤더가 대표적이었습니다. 헤더는 여러 서비스가 추가되고 실험이 이루어지면서 변경이 빈번했습니다. 헤더를 한 번 수정하면 다음과 같은 과정을 거칩니다. 레거시 Antman 쪽 헤더 코드 수정 및 배포 리액트 헤더 공통 모듈 코드 수정 및 배포 공통 모듈을 사용하는 각 도메인 패키지 업데이트 및 배포 즉, 공통 모듈을 사용하는 패키지가 10개라면, 10개 모두 배포해야 하는 셈이었습니다. 이 문제를 해결하기 위해 몇 가지 방법을 검토했습니다. 방법 1: 서버 사이드 컴포지션 프론트엔드 전용 서버를 두고, 그곳에서 헤더와 각 애플리케이션의 결과물을 조합하여 하나의 HTML로 렌더링해 내려주는 방식입니다. 하지만 모든 트래픽이 한 서버에 집중되고, 해당 서버에 장애가 생기면 모든 페이지가 마비된다는 단점이 있었습니다. 방법 2: CI/CD 디스패치를 통한 자동 배포 공통 모듈에 변경이 생기면, 워크플로우로 각 패키지에 디스패치하여 자동 배포하는 방식입니다. 방법 3: 런타임 로딩 (Module Federation) 번들링된 JS를 S3에 올려두고, 각 애플리케이션이 런타임에 이를 가져와 실행하는 구조입니다. Webpack의 Module Federation을 활용한 방식인데요. 헤더 모듈만 배포하면 모든 애플리케이션에 즉시 반영되고, 롤백도 S3 파일 경로만 변경하면 되어 유지보수가 용이합니다. 물론 트레이드오프도 있었습니다. 런타임에 JS를 불러오는 만큼 네트워크 상태에 의존하게 되고, CSR 기반이라 SEO에 불리하며, 공유 의존성 관리 등 러닝 커브가 늘어난다는 점이 있었는데요. 그럼에도 저희는 독립적인 환경에서
2026년 3월 5일
strace 추적으로 8μs 차이의 동시성 버그 찾기
안녕하세요? 인프랩 DevOps 엔지니어 구피입니다. 최근, 로그만으론 도무지 원인을 알 수 없는 오류를 만났는데요. 이때 strace로 파일 관련 시스템콜을 추적하면서 동시 실행 시 드러나는 에러 처리 버그를 찾아 해결할 수 있었습니다. 추적하는 과정이 꽤 재미있었기에 그 디버깅 과정을 공유하고자 짧은 글을 작성했습니다. shaka-packager를 사용하며 겪은 내용이지만 이 글에선 관련 지식을 다루진 않습니다. 원인 탐색 과정을 따라가며 편하게 읽어주시면 좋을 것 같습니다. 개요 개요를 간단하게 설명해보겠습니다. 인프런에서는 shaka-packager를 사용하여 영상을 DASH나 HLS 같은 스트리밍 포맷으로 패키징하고 있습니다. 플레이어에서 원하는 해상도를 선택해 재생하도록 하려면 여러 해상도의 영상을 하나의 매니페스트로 묶어야 합니다. 이를 위해 해상도별 영상들을 한 번에 전달하고, shaka-packager는 각 입력 파일을 별도 스레드에서 병렬로 동시 처리합니다. 그런데 이 병렬 처리 과정에서 몇몇 스레드가 간헐적으로 실패하는 버그가 발생했습니다. 처음에는 단순한 환경 문제인가 싶었지만 재현 패턴이 수상했습니다. 대부분 잘 생성되다가 마지막 파일에서 실패하고, 재시도하면 또 성공하기도 하는 버그였습니다. 에러 상황을 단순화하면 이런 식이었습니다: a.mp4 처리 시작 b.mp4 처리 시작 a.mp4 출력 파일 생성 완료 b.mp4 출력 파일 생성 실패, 에러 메시지: Packaging Error: 5 (FILE_FAILURE) - Cannot open file to write 에러 메시지는 Cannot open file to write 한 줄뿐이었습니다. 파일 쓰기에서 뭔가 실패한 건 알겠는데, 그 이상의 정보가 없어서 꽤 골치가 아팠습니다. 일단 떠오르는 대로 가설을 세우고 하나씩 확인해봤습니다. 디스크/메모리 부족: 리소스를 2배 이상 여유 있게 확보한 환경에서도 동일하게 발생했습니다. 파일 디스크립터 부족: 패키징 시 fd 사용량이 한계치에 한참 못 미쳤습니다. 특정 영상 문제: 같은 영상을 다시 돌리면 성공하는 경우가 있었습니다. 영상 자체의 문제는 아니었습니다. 패키저 버전 문제: 파일 쓰기 관련 변경사항이 있는 여러 버전을 시도해봤지만 동일하게 재현되었습니다. 입력 파일 개수: 입력이 1개일 때는 문제가 없었습니다. 여러 파일을 동시에 처리할 때만 실패했습니다. 여러 파일을 동시에 처리할 때만 실패한다는 점에서 동시성 문제를 의심할 수 있었습니다. 하지만 로그에는 파일 에러 한 줄만 있을 뿐, 내부에서 정확히 어떤 동작이 실패하는 건지 알 수가 없었습니다. strace 실패하는 정확한 지점을 찾기 위해 본격! 시스템콜을 직접 들여다보기로 했습니다. strace란? 프로세스가 호출하는 시스템콜을 추적하는 Linux 도구입니다. 프로세스가 커널에 요청하는 모든 작업(파일 열기, 읽기, 쓰기, 네트워크 통신 등)을 실시간으로 볼 수 있어서, 애플리케이션이 어떤 동작을 하는지 저수준에서 파악할 때 유용합니다. strace -ff -tt -y -o /tmp/strace ./packager <패키저 인자...> 사용한 strace 옵션은 아래와 같습니다. -ff: fork/clone된 자식 프로세스·스레드를 추적하고, 각 PID/TID별로 별도 파일에 저장 -tt: 각 시스템콜에 마이크로초 단위 타임스탬프 출력 -y: fd 번호 대신 대응되는 파일 경로 표시 (예: write(3</tmp/foo.mp4>, ...)) -o /tmp/strace: 출력 파일 경로. -ff와 함께 쓰면 strace.<PID> 형태로 분리 저장 중요한 옵션은 -ff(스레드별로 별도 파일 저장)와 -tt(마이크로초 단위 타임스탬프)입니다. 동시성 문제를 의심하고 있었으니 스레드별 로그를 시간순으로 비교할 수 있어야 했습니다. 성공 케이스와 실패 케이스 둘 다 strace를 떠놓고 비교해보기로 했습니다. 에러 찾기 이제 strace 실행 결과를 보고 정확히 어떤 시스템콜이 어떤 인자로 실패하는지를 찾을 차례입니다. 전체 로그는
2026년 2월 19일
Argo CD, Linkerd 기반 QA 병목 제거: PR Preview 환경 구축
안녕하세요. 인프랩 데브옵스 엔지니어 선비입니다. 올해 저희 팀은 2023년 Pulumi IaC 시스템 정착, 2024년 AWS ECS 환경의 고도화를 지나 Kubernetes…
2025년 11월 25일
Amazon S3 보관 비용과 이미지 CDN 트래픽 최적화
안녕하세요, 인프랩 DevOps 엔지니어 제이크, 구피입니다. 인프랩은 최근 Amazon S3 보관 비용과 CDN 트래픽을 최적화하는 성과를 달성했습니다. S3 Intelligent-Tiering 스토리지 클래스 도입과, CDN 응답 이미지 AVIF…
2025년 10월 29일
강의실 모달 지옥에서 탈출하기: React useFunnel hook 설계
안녕하세요, 인프랩의 프론트엔드 개발자 럭끼입니다. 인프런 강의실에서는 특정 시점에 다양한 함수 및 모달 노출 로직이 실행됩니다. 예를 들어, 강의 수강 중 특정 진도율을 달성하면 수업 영상 일시정지, 수강평 작성 모달 노출, 축하 메시지 모달 노출 로직이 순서대로 실행됩니다. 하지만 이러한 로직들의 복잡한 실행 순서와 조건 분기 때문에 코드가 점점 복잡해지고 유지보수가 어려워지는 문제를 겪었습니다. 이 문제를 퍼널(Funnel) 패턴을 활용한 useFunnel hook으로 해결한 경험을 공유드리겠습니다. 문제 상황: 모달들의 꼬리에 꼬리를 무는 지옥 기존 구조의 문제점 인프런 강의실에서는 상황에 따라 다양한 모달과 팝오버 UI가 연쇄적으로 나타납니다: 강의 시작 시: 시작 축하 메시지 → 강의실 튜토리얼 팝오버 수업 완료 시: (지식공유자가 설정한) 수업 완료 메시지 → 다음 수업 안내 특정 진도율 달성 시: 진도율 달성 축하 메시지 → 수강평 작성 모달 → 수업 종료 안내 강의(Course): 여러 개의 ‘수업’이 모여 하나의 커리큘럼을 이루는 단위입니다. 수업(Unit): ‘강의’를 구성하는 각 개별 영상, 자료, 또는 학습 콘텐츠를 의미합니다. 기존에는 각 모달 컴포넌트의 handleClose 함수에서 다음 모달을 직접 호출하는 방식을 사용했습니다: // 수강평 모달 컴포넌트 const ReviewModal = () => { // 수강평 작성 완료시 const handleConfirm = () => { ... // 수강평 작성 API 호출 handleClose(); }; // 모달 닫기 버튼 클릭시 const handleClose = () => { closeReviewModal(); // 수강평 모달 닫기 if (isFromComplete) { // 수강평 모달이 강의가 완강되어서 노출된 경우 setIsFromCompleteModal(false); // 완강 관련 전역 상태 수정 findAndNavigateToNextCourse(); // 완강 후 다음 강의를 탐색하는 로직 실행 } }; }; 위의 코드는 다음과 같은 문제를 야기합니다. 1. 강한 결합도 각 모달이 다음에 실행될 모달을 직접 알고 호출해야합니다. 모달 노출 순서를 바꾸거나 새로운 모달을 중간에 삽입하려면 관련된 컴포넌트를 모두 수정해야합니다. 예를 들어 A → B → C 순서로 모달을 노출하다가 A → C → B 순서로 바꿔야한다면, A는 B 대신 C를 열도록 수정하고, B는 C를 열지 않도록 코드를 삭제하고, C는 B를 열도록 코드를 추가해야합니다. 2. 조건 분기의 확산 isKo(한국어 페이지에서만 노출되어야하는지), isReviewable(수강평 작성이 가능한 상태인지), isFromComplete(방금 완강을 했는지) 등 특정 조건에 따라 모달을 노출하지 않고 넘어가야하는 경우가 있습니다. 기존 방식은 조건 분기 로직이 각 모달 컴포넌트에 흩어져 있어 정책이 바뀔 때마다 관련된 컴포넌트들을 찾아 수정해야합니다. 특히 저희는 기능 플래그를 사용하기 때문에 조건 분기 로직이 추가되거나 삭제될 일이 잦았습니다. 복잡성 예시 실제로 다음과 같은 간단한 요구 사항에도 코드 작업량은 생각보다 많아집니다. AS-IS: 진도율 n% 달성 축하 메시지 모달 → 수강평 작성 모달 TO-BE: 진도율 n% 달성 축하 메시지 모달 → 수강평 작성 모달 → (진도율 100% 달성(완강)일 경우) 다음 학습 강의 추천 모달 추가 노출 이 요구사항을 구현하려면: 진도율 100% 달성 시(progress가 최초로 100이 되었을 때) isFromComplete 변수를 true로 설정 수강평 작성 모달에서 isFromComplete가 true면 강의 추천 모달 open 수강평 작성 모달에서 isFromComplete가 false면 바로 return // 수강평 작성 모달 const ReviewModal = () => { const handleClose = () => { closeReviewModal(); if (isFromComplete) { openRecomme
2025년 10월 27일
EKS Bottlerocket AMI에서 DCGM 오류로 GPU 노드 반복 교체 문제 해결기
안녕하세요, 데브옵스 엔지니어 포카입니다. 최근 인프런의 ”클린 코더스: 실전 객체 지향 프로그래밍과 TDD 마스터 클래스” 강의 영상 업스케일링 작업을 위해 GPU 클러스터를 운영하던 중, 예상치 못한 문제에 직면했습니다. GPU…
2025년 8월 27일
사이드 프로젝트로 업무 효율화하기
안녕하세요, 인프랩 프론트엔드 개발자 빠삐코입니다. 데이터 기반 의사결정에서 정확한 사용자 행동 데이터 수집은 무엇보다 중요합니다. 저희 팀도 Google Tag Manager(GTM) 를 통해 Google Analytics, Mixpanel…
2025년 7월 3일
EKS Bottlerocket에서 이미지 캐싱으로 Pull 시간 줄이기
안녕하세요. 인프랩 데브옵스 엔지니어 구피입니다! 인프랩에서는 ML, 데이터 처리 목적으로 몇 파이프라인에서 GB 단위의 큰 용량 이미지를 사용하고 있는데요. 이런 작업의 컨테이너를 띄우기 위해선 이미지 pull…
2025년 4월 21일
코드 리뷰 요정, CodeRabbit이 나타났다 🐰
안녕하세요. 인프랩의 백엔드 개발자 약풍입니다. 코드 리뷰는 버그를 예방하고, 팀의 코드 품질을 유지시키는 중요한 장치입니다. 많은 개발자들이 서로의 코드에 대해 리뷰하고 피드백하는 시간은 꼭 필요하지만, 팀의 규모나 상황에 따라 코드 리뷰가 꼼꼼하게 이루어지지 않는 경우들도 종종 생깁니다. CodeRabbit은 개발팀이 더 활발한 코드리뷰를 할 수 있도록 도와줍니다. CodeRabbit은 AI 기술을 활용하여 코드 변경 사항을 자동으로 분석하고, 코드에 대해 리뷰합니다. 개발자들은 CodeRabbit을 통해 Pull Request(PR) 생성과 동시에 코드 스타일, 버그 가능성, 성능 개선 포인트 등을 자동으로 리뷰받을 수 있습니다. 이 글에서는 CodeRabbit이 어떤 기능을 제공하는지, 인프랩에서는 어떻게 CodeRabbit 을 활용하여 코드리뷰를 하고있는지 살펴보겠습니다. 🚀 예시 프로젝트 구체적인 이해를 돕기 위하여, 예시 프로젝트를 먼저 소개하겠습니다. 시연용 데모 프로젝트로 자주 사용되는 spring-petclinic 프로젝트에 아주 간단한 피처 기능을 구현하며, CodeRabbit 의 코드리뷰를 받아보겠습니다. 동물병원에 반려동물용 강의 관리 기능을 추가해 보겠습니다. API Endpoint 를 하나 추가하고, 반려동물용 강의를 입력하는 기능을 구현해야 합니다. 구체적인 코드 내용은 Pull Request 를 참고해 주세요. CodeRabbit 의 기능을 소개하기 위해, 코드는 최대한 간단한 구조로 구성해 보았습니다. CodeRabbit 의 기능 소개 예제 Pull Request 를 바탕으로, CodeRabbit 의 기능을 설명해 보겠습니다. CodeRabbit 에서 제공하는 모든 기능을 소개하지는 않고, 인프랩 개발팀에서 주로 사용하는 기능들을 위주로 설명하겠습니다. CodeRabbit 의 구체적인 기능들은 웹사이트 를 참고해 주세요. 1. 코드 변경사항 요약 코드를 리뷰하는 개발자가 처음 Pull Request 를 열었을 때 읽는 것은 PR Summary 입니다. 리뷰하려는 코드나 Pull Request에 대한 배경지식이 많지 않은 경우, Pull Request 에 대한 문맥을 파악하는 것이 중요합니다. CodeRabbit은 코드 변경사항의 요약본을 보여주고, 리뷰어가 코드 변경사항을 빠르게 파악할 수 있도록 도와줍니다. Pull Request Summary CodeRabbit 은 Pull Request 가 열릴 때, 코드의 변경사항을 요약하여 댓글로 달아줍니다. Pull Request 를 리뷰할 다른 개발자들은 이 요약을 읽고, 코드 변경사항에 대한 전반적인 이해를 얻을 수 있습니다. 단순히 요약만 적어줄 뿐만 아니라, 코드의 변경사항을 쉽게 파악할 수 있는 Workthrough를 작성해 줍니다. Pull Request Workthrough 또한 개발자들이 이해하기 쉽도록, 코드의 변경사항을 한 눈에 이해할 수 있는 Sequence Diagram 도 생성해 줍니다. Layered Architecture 가 적용되어 있거나, 팀의 코드 컨벤션이 정착되어 있다면 다이어그램 만으로 코드의 구현 내용을 쉽게 예상해 볼 수 있습니다. Pull Request 의 Sequence Diagram 리뷰어들은 이러한 배경사항을 읽고, PR 의 문맥을 충분히 이해한 후에 코드리뷰를 시작할 수 있습니다. 2. 코드 리뷰 및 피드백 이제부터는 본격적으로 CodeRabbit 의 코드 리뷰를 살펴보겠습니다. CodeRabbit 을 도입한 후에 리뷰 과정에서 많은 도움을 받았는데, 특히 인간이라면 자주 하게되는 실수들을 CodeRabbit 이 잡아준 적이 많습니다. 오타에 대한 피드백 먼저 타이핑을 하는 과정에서 자주 발생하는 오타를 잘 잡아줍니다. 예를 들어, 작업을 하다 보면 다음과 같이 파일명이나 함수명에 오타를 내는 경우가 종종 있습니다. package org.springframework.samples.petclinic.course; import org.springframework.data.jpa.reposito
2025년 3월 26일
기존 서비스 국제화(i18n) 작업 쉽게 덜어내기: t 함수 자동 래핑 스크립트 만들기
안녕하세요. 랠릿 셀 프론트엔드 개발자 고슈, 도니, 루시입니다. 2024년 초, 연간 플래닝에서 CTO인 향로에게서 예상치 못한 발표를 듣게 됩니다. 올해 인프런 글로벌 서비스를 출시할 예정이에요. 랠릿 셀도 함께 투입됩니다. 네? 저희도요? 이렇게 랠릿을 포함한 인프랩 개발파트는 레거시 코드 정리부터 국제화 작업까지, 인프런의 글로벌 서비스 출시를 위한 새로운 도전을 시작하게 되었습니다. 프롤로그 i18n을 서비스에 적용하는 전체 과정보다는, 기존 서비스를 다국어 서비스로 전환하는 과정에서 i18n 을 효율적으로 적용하는 방법에 대해 공유하고자 합니다. i18n은 internationalization의 줄임말입니다. ‘i’와 ‘n’ 사이에 18개의 글자가 있어서 i18n이라고 부릅니다. Next.js 다국어 처리 들어가기 앞서, Next.js 기반의 프로젝트에서 다국어 지원을 위해 일반적으로 사용되는 next-i18next 라이브러리에 대해 먼저 살펴보겠습니다. next-i18next는 Next.js 애플리케이션에서 i18n 기능을 구현하는 라이브러리입니다. 주요 특징 useTranslation hook을 통해 번역 함수(t) 제공 언어 파일을 JSON으로 관리 서버사이드 렌더링 지원 동적 번역 및 네임스페이스 지원 간단한 사용 예시를 보면 다음과 같습니다. // public/en/sample.json { "안녕하세요": "hi" } import { useTranslation } from 'next-i18next'; const Component = () => { const { t } = useTranslation(); return ( <p>{t('안녕하세요')}</p> // hi 라고 노출 ); } 이제 이 라이브러리를 활용하여 어떻게 효율적으로 기존 서비스에 국제화를 적용했는지 살펴보도록 하겠습니다. 언어 자원의 키 기존 인프런 담당 셀에서 레거시 코드의 React 마이그레이션이 진행을 진행중에 있어서 랠릿 셀은 이미 React로 전환된 코드들에 대한 i18n 적용을 가장 먼저 담당하게 되었습니다. 따라서 다른 셀과 함께 작업 하면서 사용할 효율적인 컨벤션을 먼저 결정할 필요가 있었습니다. 이 과정에서 가장 중요한 초기 결정 사항 중 하나는 다음과 같았습니다. 언어 자원(번역을 위한 JSON 파일)의 키를 어떤 방식으로 정의할 것인가? 이 문제에 대한 해답을 찾기 위해 다양한 개발자들과의 네트워킹을 하며 인사이트를 얻었습니다. 그 중 기억에 남는 의견이 있었습니다. 다음에 국제화를 진행한다면, 반드시 키값은 한글로 설정할 거예요. 이 의견에 많은 개발자들이 동의했고, 그 이유를 물어보니 다음과 같은 공통된 피드백을 얻을 수 있었습니다. 영문 키를 사용할 경우 대응되는 번역 키를 찾는 과정이 번거로움 이로 인한 개발 생산성 저하 발생 이러한 인사이트를 바탕으로, 랠릿셀은 함께 언어 자원의 키 정의에 대한 세 가지 컨벤션을 수립하고, 각각의 장단점을 분석하여 PM과 PD에게 제안하게 되었습니다. 컨벤션 종류 서비스의 국제화를 위한 언어 자원 키 정의 방식을 세 가지로 분류하여 분석했습니다. 1. 한글 키 방식 한국어 텍스트를 그대로 키로 사용하는 방식입니다. { "MY 카테고리": "MY 카테고리", "나만의 강의 카테고리를 구성해보세요!": "나만의 강의 카테고리를 구성해보세요!" } // 구현 예시 <Text>{t("MY 카테고리")}</Text> // MY 카테고리 <Text>{t("나만의 강의 카테고리를 구성해보세요!")}</Text> // 나만의 강의 카테고리를 구성해보세요! 장점 키 정의 과정 생략 가능 빠른 다국어 전환 가능 직관적인 코드 가독성 단점 텍스트 중복 가능성 원본 텍스트 변경 시 키도 함께 수정 필요 언어별 1:1 매칭이 어려운 경우 존재 (개행 등) 2. 역할군 기반 키 방식 {페이지 | 도메인}.{섹션}.{역할}.{서브} 형태의 구조화된 한글 키 { "계정정보.마이카테고리.제목": "MY 카테고리", "계정정보.마이카테고리.설명": "나만의 강의 카테고리를 구성해보세요!" } // 구
2025년 2월 6일
신입 DevOps 엔지니어의 AI/ML 기반 자동화된 스팸 필터링 도입 경험기
안녕하세요, 인프랩 데브옵스 엔지니어 포카입니다. 어느덧 저의 첫 회사인 인프랩에 합류한 지 3개월이 지나가는데요! 오늘은 전문 직군 (AI/ML) 없는 저희 같은 스타트업에서 머신러닝/AI…
2024년 12월 5일
우리 팀에 맞는 VPN 찾기
안녕하세요. 인프랩 데브옵스 엔지니어 구피입니다! 다들 많이 사용하시다시피, 인프랩에서도 사설망 내부 자원에 대한 접근 관리를 위해 VPN을 쓰고 있습니다. VPN 구축을 위해 AWS Client VPN, Firezone…
2024년 10월 31일
JDBC setReadOnly 호출 이슈 해결기
안녕하세요. 인프랩 백엔드 개발자 인트, 제이입니다. 인프랩의 백엔드 서버는 대부분 Node.JS로 구성되어 있지만 일부 JVM 서버도 함께 운영됩니다. JVM…
2024년 9월 1일