더핑크퐁컴퍼니 기술 블로그 - Medium 최신 글
medium.com에서 발행한 기술 글을 테크곳간이 수집해 최신순으로 보여드립니다.
최신 글
베베핀 타운 AR 도입기: Unity AR Foundation 선택부터 적용까지 (1편)
베베핀 타운에 새로운 놀이 경험을 더하기 위해 AR Foundation을 검토하고 적용한 이야기 안녕하세요. 더핑크퐁컴퍼니에서 소프트웨어 엔지니어로 근무하고 있는 강진영(그루트)입니다. 베베핀 타운에 AR 기능을 도입하며 고민했던 기술적 의사결정과 적용 과정을 공유합니다. 이 글에서는 Unity AR Foundation을 선택하게 된 배경과, 크로스 플랫폼 환경에서 Face Tracking을 안정적으로 적용하기 위한 설계 방향을 다룹니다. AR 도입이나 크로스 플랫폼 AR 구현을 고민하는 분들께 도움이 되기를 바랍니다. 베베핀 타운이란? 베베핀 타운은 아이들이 ‘베베핀’ 캐릭터가 되어 3D 공간 속 집과 다양한 월드를 자유롭게 탐험하며 즐길 수 있는 놀이형 앱입니다. 영유아 및 미취학 아동을 대상으로 설계되어, 안전하고 직관적인 환경에서 아이들이 자연스럽게 탐험하고 놀이에 참여할 수 있도록 구성되어 있습니다. 단순한 시청형 콘텐츠를 넘어, 아이 스스로 공간을 이동하고 캐릭터와 상호작용하는 능동형 경험을 제공하는 것이 특징입니다. https://medium.com/media/89b804a61c19400b95aa4d5f5ef52fc4/href 📲 App Store: https://lnkd.in/gS9gMeJD 📲 Google Play: https://lnkd.in/g6jvT73j 🔎 Learn more: https://lnkd.in/gfBzJcJ3 AR 기능을 검토하게 된 배경 AR은 사용자를 콘텐츠 안으로 자연스럽게 끌어들이고, 얼굴 움직임이나 표정에 반응하는 인터랙션을 통해 아이들에게 보다 직관적이고 몰입감 있는 경험을 제공합니다. 특히 자신의 움직임이 즉각적으로 반영되는 구조는 복잡한 설명 없이도 흥미를 유도하며, 짧은 시간 안에도 콘텐츠에 빠르게 몰입할 수 있도록 돕습니다. 최근 연구에서도 AR은 어린 사용자에게 풍부한 상호작용 경험을 제공하는 방식으로 소개되고 있으며, 행동적·인지적·정서적·사회적 참여 측면에서 긍정적인 반응이 확인되고 있습니다 또한 업계에서도 AR은 사용자의 반응을 빠르게 이끌어내는 효과적인 인터랙션 수단으로 활용되고 있습니다. 이러한 흐름을 바탕으로, 베베핀 타운에서도 아이들이 캐릭터를 더 가깝게 느끼고 직접 참여하는 경험을 확장하기 위해 AR 기능 도입을 검토하게 되었습니다. (참고: Frontiers in Psychology, 2024 / Snap Investor Letter, 2025 Q3) 왜 AR Foundation을 선택했을까? AR 기능을 실제 서비스에 적용하는 과정에서는 단순한 구현 가능 여부를 넘어서, 운영과 유지보수까지 함께 고려할 필요가 있었습니다. 베베핀 타운은 iOS와 Android를 모두 지원하는 만큼, 플랫폼별로 서로 다른 방식으로 AR을 구현할 경우 개발과 운영 부담이 커질 수 있는 구조였습니다. 특히 얼굴 움직임을 추적하는 기능은 플랫폼마다 지원 범위와 동작 방식에 차이가 있어, 초기 설계 단계에서부터 이를 하나의 구조로 통합하는 방향이 중요했습니다. 이에 따라 플랫폼별 개별 구현보다는, 하나의 코드 흐름 안에서 공통으로 관리할 수 있는 구조를 우선적으로 검토했습니다. 그리고 플랫폼 간 차이를 최소화하면서도 안정적으로 확장 가능한 방향을 기준으로 기술을 선택했습니다. 그 결과, Unity의 AR 개발 프레임워크인 Unity AR Foundation을 적용하게 되었습니다. AR Foundation은 iOS의 ARKit과 Android의 ARCore를 공통 인터페이스로 연결해 주는 구조로, Unity 프로젝트 내에서 단일한 흐름으로 AR 기능을 제어할 수 있다는 장점이 있습니다. 이를 통해 플랫폼별 구현 차이를 줄이면서 일관된 개발 흐름을 유지할 수 있었습니다. 또한 Plane Detection, Raycast, Face Tracking과 같은 주요 기능들을 하나의 구조 안에서 함께 활용할 수 있어, 이후 기능 확장이나 유지보수 측면에서도 보다 효율적인 구조를 갖출 수 있었습니다. 초기 테스트 및 기술 검토 초기 테스트 및 기술 검토 단계에서는 Unity A
2026년 5월 21일
Next.js App Router에서 prefetchQuery와 Suspense로 뚜루루뚜루 데이터 스트리밍하기
안녕하세요. 더핑크퐁컴퍼니에서 Software Engineering Intern으로 근무하고 있는 앤디입니다.😆 이번 포스트에서는 인턴으로서 처음 맡았던 업무 중 하나였던 사내 카페 서비스 프로젝트의 마이그레이션 과정에서 React Query를 활용하며 겪었던 경험을 공유드리려고 합니다. 목차 들어가며 기존 방식의 한계와 마이그레이션 여정 React Query v5.40.0 변화 세 가지 접근 방식 실험 v5 streaming 적용 및 성능 비교 결과 배운 점과 결론 1. 들어가며 🎯 프로젝트 배경 인턴십을 시작하고 처음 맡았던 프로젝트는 사내 카페 서비스의 Next.js App Router + React 19 기반 마이그레이션이었습니다. 기존에는 Pages Router를 사용하고 있었는데, 다음과 같은 이유로 마이그레이션을 결정했습니다: Server Components 활용: 서버에서 데이터 페칭을 더 효율적으로 처리 React 18의 Concurrent Features 활용 개발 경험 개선: 더 직관적인 라우팅과 레이아웃 시스템 🤔 마주한 문제 단순한 화면 전환 이상의 문제를 다루게 되었는데, 바로 SSR과 클라이언트 데이터 로딩이 맞물리면서 발생하는 UX 문제였습니다. // 기존 Home 페이지 - SSR로 사용자 정보 prefetch export async function generateMetadata() { const queryClient = new QueryClient(); await queryClient.prefetchQuery( useGetMeQuery.getKey(), useGetMeQuery.fetcher(graphqlRequestClient, undefined, { authorization: Bearer ${getToken() }, }) ); // ... 메타데이터 생성 } // HomeBoard 컴포넌트 - 클라이언트에서 데이터 로딩 function HomeBoard() { return ( <Suspense fallback={<BoardSkeleton />}> <BoardContent /> </Suspense> ); } function BoardContent() { const { data } = useGetHomeBoardQuery(); // 일반 쿼리 사용 return <div>{/* 게시판 내용 렌더링 */}</div>; } 이 구조에서는 Home 페이지 내에서 prefetchQuery + SSR로 빠른 초기 렌더링이 가능합니다. 또한 HomeBoard 클라이언트에서는 Suspense로 자연스러운 로딩을 체감할 수 있습니다. 하지만 두 방식이 일관되지 않고, 복잡한 페이지에서는 어떤 전략을 써야 할지 혼란이 오는 경우가 있었습니다. 2. 기존 방식의 한계와 마이그레이션 여정 📊 Pages Router 시절: 부분적 SSR 전략 기존 사내 카페 서비스에서는 선택적 prefetch 전략을 사용하고 있었습니다. 모든 데이터를 서버에서 미리 가져오는 것이 아니라, 꼭 필요한 인증 정보만 SSR로 처리하고 나머지는 클라이언트에서 로딩하는 방식이었죠. // Pages Router - 인증만 SSR로 prefetch export async function getServerSideProps(context) { const queryClient = new QueryClient(); await queryClient.prefetchQuery( useGetMeQuery.getKey(), () => useGetMeQuery.fetcher(graphqlRequestClient, { authorization: Bearer ${getToken(context)}, })() ); return { props: { dehydratedState: dehydrate(queryClient) } }; } // 클라이언트 컴포넌트는 나머지 데이터를 CSR로 로딩 function HomeBoard() { const { data, isLoading } = useGetHomeBoardQuery(); return isLoading ?
2025년 9월 26일
핑크퐁의 통합 데이터 환경 구축기 (feat. Snowflake)
안녕하세요. 더핑크퐁컴퍼니에서 Software Engineering 업무를 맡고있는 메타입니다.😀 이번 포스트에서는 저희 더핑크퐁컴퍼니에서 Snowflake를 활용하여 어떻게 기존의 복잡하고 비효율적이었던 데이터 처리 업무를 단순화할 수 있었는지에 대해 소개하려 합니다. 목차 핑크퐁이 다루고자 하는 데이터 기존 데이터 아키텍처의 소개 기존 데이터 아키텍처의 문제점: Data Silo Data Silo에서 중앙화된 데이터 플랫폼으로 변화의 시작: 기존 앱 사용자 이벤트성 로그 처리 시스템을 클라우드 기반으로 변경 ▸ 기존 로그 시스템 리뉴얼의 필요성 1: 단일 장애 점(SPOF, Single Point Of Failure) ▸ 기존 로그 시스템 리뉴얼의 필요성 2: 신규 앱에 대한 사용자 활동 분석 니즈 증가 ▸ 신규 로그 시스템: IDC에서 클라우드로, Self-Managed에서 Managed로 ▸ 로그 수집기 (1): AWS Kinesis 도입 ▸ 로그 수집기 (2): 리시버 서버 추가 ▸ 로그 수집기 (3): AWS Data Firehose + Lambda 파이프라인 Elasticsearch와 Kibana를 사용한 분석 환경의 문제점 Data Warehouse의 도입 ▸ Google BigQuery를 고려한 이유 ▸ Google BigQuery를 사용해보니… Snowflake를 메인으로 하는 최종 형태 ▸ Snowflake 도입의 이점 적용 프로젝트 소개 정리 및 요약: Data Silo에서 중앙화된 데이터 환경으로 핑크퐁이 다루고자 하는 데이터 먼저 저희가 다루고자 하는 데이터는 어떤 것들이 있는지, 그리고 각 데이터를 어떻게 활용하고자 하는지 소개해드리겠습니다. 핑크퐁, 아기상어 IP는 국내뿐 아니라 244개국에서 25개 언어로 유튜브를 포함한 다양한 플랫폼에서 서비스하고 있습니다. 그리고 그 다양한 플랫폼에서 발생하는 데이터들을 활용해 콘텐츠를 서비스하면서 필요한 분석 업무를 수행하고 있습니다. 1. 유튜브 매출 및 구독 통계 데이터 자체 운영 중인 채널들에서는 현재 누적 조회수 800억 뷰를 돌파했고, 일 130만 건 이상의 매출 및 구독 통계 데이터가 유튜브 단일 플랫폼에서 발생 되고 있습니다. (2023년 8월 기준) 2. 앱 및 자체 개발 서비스에서 발생하는 사용자 이벤트성 로그 데이터 핑크퐁, 아기상어 IP를 활용한 모바일 앱 시리즈 170여 종에서 발생하는 일 1,300만 건 이상의 사용자 로그를 사내 앱 로그 시스템을 통해 처리하고 분석에 활용하고 있습니다. 3. 광고 및 앱 플랫폼 매출 및 통계 데이터 10여 종이 넘는 서로 다른 광고 및 앱 플랫폼에서 발생하는 매출 및 통계 데이터를 통합해 분석에 활용하고 있습니다. 4. 그 외 다른 데이터 소스 IPTV 와 같은 플랫폼들은 업체로부터 직접 리포트를 엑셀 파일 형태로 받는데요, 해당 데이터도 다른 소스의 데이터들과 함께 분석에 활용하고 있습니다. 기존 데이터 아키텍처의 소개 Before: 기존 핑크퐁의 데이터 아키텍처 앞서 소개해 드린 것 처럼, 자체 개발 앱 서비스 뿐만 아니라 유튜브와 같은 여러 플랫폼에서 저희의 콘텐츠를 서비스하게 되면서 발생하는 데이터 소스가 다양해지고, 처리해야 하는 데이터의 양 또한 많아지게 되었습니다. 그리고 자연스럽게 다양한 플랫폼에서 발생하는 데이터를 기반으로 한 통합 분석 요청 또한 증가하게 되었습니다. “어떤 콘텐츠가 어떤 플랫폼에서 가장 인기 있었는지 한번에 알고 싶어요” “이번에 업데이트한 팝업 배너로 얼마나 구독자가 늘었는지 알고 싶어요” “IP별로 작년 한 해 동안 어느 플랫폼에서 어떤 콘텐츠가 가장 많은 매출을 달성했는지 알고 싶어요” “BEP를 가장 빨리 달성한 프로젝트가 궁금해요” 하지만 기존 저희의 데이터 처리 프로세스는 앱 로그 분석 시스템 이외에 별다른 자동화된 데이터 처리 파이프라인이 없어 수동으로 “사람이 직접” 운용 중인 서비스의 데이터베이스에서 데이터를 추출하거나, 10여 종이 넘는 외부 플랫폼에서 제공하는 웹 대시보드를 통해 데이터를 내려받아 분석에 활용하고 있었습니다. 그러다
2023년 9월 6일
Retool을 활용한 로그 데이터 시각화
안녕하세요, The Pinkfong Company에서 프론트엔드 개발을 하고 있는 페이지입니다. 🙆♀️ Retool을 이용하여 로그 시각화를 도입하는 과정에서 제가 얻게 된 지식과 팁을 공유하고자 합니다. 기존에는 앱에서 생성되는 로그를 Elastic Search에 적재 한 후 그 데이터를 Kibana를 활용하여 시각화 하였습니다. 또한 추가적인 데이터 분석 요구 사항을 반영하기 위해 JavaScript를 활용하여 웹페이지에서도 로그 데이터를 분석할 수 있도록 구현했습니다. 최근 모든 로그 기록을 Snowflake로 이관함에 따라, 통합된 로그 데이터를 시각화 할 수 있는 하나의 효과적인 도구가 필요해졌습니다. 다양한 데이터 시각화 도구 중에서도 저희 시스템과의 호환성을 고려해 Tableau와 Retool이 가장 적합하다고 판단했습니다. Tableau의 장점 시각화 다양성: 다양한 시각화 옵션과 유연한 사용자 인터페이스를 제공하여 데이터를 간편하게 시각화 할 수 있습니다. 대화식 UI: 상호 작용 가능한 대시보드를 만들어 데이터 탐색 및 분석이 가능합니다. 데이터 연결성: 다양한 데이터 원본과 연결할 수 있는 기능을 제공하여 실시간으로 데이터를 시각화하고 갱신할 수 있습니다. 커뮤니티 및 지원: 커뮤니티가 활성화 되어있어서 인사이트를 얻기 편리합니다. Tableau의 단점 비용: 라이선스 및 구독 비용이 상대적으로 높을 수 있습니다. 높은 러닝커브: 복잡한 시각화 및 분석 작업을 위해서는 학습 시간이 필요할 수 있습니다. Retool의 장점 빠른 개발: 로우 코드이며, 사용하기 쉬운 인터페이스를 제공하여 빠르게 데이터 기반 애플리케이션을 개발할 수 있습니다. 데이터 연결성: 다양한 데이터 원본과 통합할 수 있는 연결성을 제공하여 실시간 데이터를 활용할 수 있습니다. 커스텀 기능: JavaScript를 활용하여 커스텀 로직 및 기능을 추가할 수 있어 더 많은 유연성을 제공합니다. 대화식 UI: 사용자와 상호 작용이 가능한 UI 요소를 쉽게 만들 수 있습니다. Retool의 단점 고급 시각화 부재: Tableau만큼 다양한 시각화 옵션이 제공되지는 않을 수 있습니다. 비교적 새로운 도구: Tableau만큼 커뮤니티 크기가 크지 않습니다. 데이터 분석가분들과의 의논을 토대로 Retool을 활용하여 데이터 시각화를 진행하기로 결정했습니다. Kibana를 사용할 때도 추가 요구사항들이 나타났는데, 예를 들어 회사 앱 목록을 드롭다운 형식으로 표시할 때 앱의 아이덴티피케이션, 이름 및 이미지를 함께 보여주는 것이 필요했습니다. 그러나 Kibana에서는 이를 구현하기에제약이 있었습니다. Tableau를 고려했을 때, 데이터 분석가들은 직접 데이터를 활용하여 지표를 분석하고 시각화할 수 있는 장점을 갖고 있었습니다. 그러나 러닝 커브가 높고 모든 요구사항을 반영하는 데 어려움이 있었습니다. 특히 추가적인 커스텀화된 기능이 필요한 경우 직접 프론트엔드 개발을 해야하는데, 이에는 상당한 공수가 필요한 단점이 있습니다. 이에 비해 Retool은 필요한 기능을 충족시키면서도 신속한 데이터 시각화 대시보드 제작을 가능케 하는 강력한 도구였습니다. 특히 Tableau에서 제공되는 기능 이상의 커스텀 기능이 필요한 경우 Retool이 더욱 두드러졌습니다. Retool은 마치 Tableau와 프론트엔드 개발 간의 중간 지점에 위치한 것으로, 직접 프론트엔드 개발보다 빠르게 대시보드를 개발할 수 있었습니다. 이로써 데이터 분석가들은 더 빠르게 필요한 기능을 시각화하여 활용할 수 있었습니다. Retool을 선택함으로써, JavaScript를 기반으로 빠르게 구현하면서도 Tableau에서 제공되는 기능보다 더 커스텀한 대시보드 개발의 이점을 확보했습니다. 결과적으로 저희는 요구사항을 완벽히 반영한 대시보드를 제작할 수 있었습니다. 리소스 연결 다양한 외부 데이터베이스와 API를 연결할 수 있는 유연한 옵션을 제공하기 때문에, 필요에 따라 원하는 데이터 소스를 연결하여 활용할 수 있습니다. 저희는 Snowflake 데이터베이스와 GraphQL
2023년 8월 30일
텍스처 압축으로 유니티 모바일 게임 메모리 최적화하기
해당 게시물은 Unity 2020.3.15f2 버전을 사용했으며, 테스트 기기는 iPhone7(64GB, iOS 16.1.1)을 사용했습니다. 안녕하세요, 더핑크퐁컴퍼니 서비스개발팀에서 Unity를 활용해 게임 컨텐츠를 개발하고있는 벨로(bello)입니다. 이번 글에선 최적화의 필요성과 텍스처의 개념 그리고 텍스처 압축 포맷에 대해 설명하도록 하겠습니다. 최적화가 왜 필요한지? 현재까지 <아기상어 키즈 월드> 앱 프로젝트에서는 간단한 2D 게임을 제작하고 있어 최적화에 대해 깊게 생각해본 적은 없었습니다. 하지만 앞으로 출시할 게임들은 이전 게임들보다는 조금 더 많은 파티클, 모션 그리고 좋은 퀄리티의 이미지 등 게임의 퀄리티를 높이기 위해 더 많은 리소스를 사용할 것입니다. 저희 앱의 주요 대상이 아이들인 만큼 부모의 기기를 사용하는 아이들도 있겠지만, 아이들이 사용하는 기기는 대부분 저사양의 기기로 예상합니다. 따라서 이 저사양 기기에서 게임이 정상적으로 실행되기 위해선 늘어난 리소스만큼 메모리를 최적화하는 작업이 필요하다고 생각됩니다. Unity에서 모바일 게임 성능을 최적화하는 방법은 여러가지가 있습니다. SciptableObjects 사용, 스크립트 최적화, 물리 처리 최적화, 텍스처 최적화, 렌더링 최적화 등 다양한 방법으로 성능을 최적화할 수 있습니다. 이번 글에선 텍스처 최적화 중 텍스처 압축에 대해 알아보겠습니다. 텍스처란? 텍스처란 이미지를 GPU에서 사용하기 위해 렌더링한 것을 텍스처라고 하며, GPU 메모리에 직접 로드되므로 렌더링 중에 매우 효율적으로 액세스할 수 있습니다. Unity에서 텍스처는 게임 오브젝트를 덮거나 래핑하는 이미지 또는 동영상 파일로 시각적 효과를 만들어 냅니다. 텍스처 압축 이란? jpg, png 등 익숙하게 알고 있는 이미지 포맷은 디스크에 저장하는 용량 효율을 위해 제작된 알고리즘으로 GPU에서 바로 읽어올 수가 없습니다. 따라서 GPU에서 읽는 전용 포맷으로 변환을 해줘야 하는데, Unity는 이 기능을 제공하고 있습니다. 이를 모르고 간혹 성능을 위해 jpg, png, psd 등을 고려하는 데 이미지 포맷과 전혀 상관없이 Unity에서 이미지를 사용할 때 GPU에서 사용하는 포맷으로 변경하기 때문에 이미지 포맷은 편한 것을 사용하면 됩니다. 픽셀당 비트(bpp)는 단일 텍스처 픽셀에 필요한 스토리지 용량입니다. bpp 값이 더 낮은 텍스처는 디스크와 메모리를 더 적게 차지합니다. bpp 값이 낮으면 GPU가 텍스처 픽셀을 읽는 데 더 적은 메모리 대역폭을 사용합니다. GPU 메모리 대역폭은 종종 프레임 속도 병목 현상을 유발할 수 있으므로, 텍스처 압축을 사용하면 이러한 문제를 방지할 수 있습니다. 텍스처 에셋의 화질이 높을수록 픽셀당 비트가 더 높으며, 그에 따라 빌드 크기, 로드 시간, 런타임 메모리 사용량이 늘어납니다. 모든 텍스처 압축 포맷에는 어느 정도 손실이 있습니다. 압축되지 않은 텍스처는 최고의 품질을 제공하지만, 픽셀당 비트도 가장 높습니다. 모든 텍스처 압축 포맷은 제각기 서로 다른 장단점을 지니고 있습니다. 텍스처 압축 포맷 - Unity 매뉴얼 텍스처 압축 포맷의 종류 Unity에서 주로 사용하게 되는 텍스처 압축 포맷은 DXT, PVRTC, ETC1, ETC2, ASTC 등이 있습니다. RGBA 무압축 압축을 하지 않는 포맷으로 R,G,B,A각 채널에 8비트를 사용해 픽셀당 32비트를 사용합니다. Alpha채널을 사용하지 않는 경우 24비트를 사용합니다. 원본 이미지의 크기와 동일합니다. DXT1 (DirectX Texture 1) Direct X10 이상에선 BC1이라고도 불립니다. 두 가지의 경우가 있는데, Alpha채널 사용 여부에 따라 A1R3G3B3인 경우와 R4G4B4인 경우가 있습니다. 가장 기본적인 형식으로, 16픽셀 데이터를 64비트 블록으로 변환합니다. 각 블록은 두 개의 16비트 RGB 5:6:5 색상 극단 값과 각 픽셀당 2비트씩, 총 32비트의 테이블을 가져 총 4개의 색상을 이용할 수 있습니다. 압축 효율은 높지만
2023년 1월 2일
Next.js 프로젝트 Migration과 Refactoring 과정을 공유합니다.
안녕하세요. 웹개발팀 밸리입니다. 이번 포스팅은 사내 version 9 Next.js 그리고 기타 지원이 중단된 라이브러리들로 구성된 프로젝트를 Migration하고 Refactoring 하는 과정을 담았습니다. 레거시에 대한 이야기 업무 일정에 맞추기 위해 바쁘게 일을 하다 보면, 시간이 지난 프로젝트들은 금방 레거시가 되어 있습니다. 특히 빠르게 업데이트되고 변화하는 프론트엔드 세계에서는 더욱 그러한 것 같습니다. 당장 문제를 일으키지 않기 때문에 묻어가는 경우도 많습니다. 그러다 보면, 꽤 오래된 레거시 코드들은 폭탄이 되어 있어 최대한 건드리지 않는 방향으로 가게 됩니다. // 이 코드는 함부로 건들이지 말 것... function legacyFunction() { return '아주 중요한 데이터'; } 저 또한 오래된 코드들을 보고도 당장 해야 할 일이 많고 해당 코드들로 기능이 큰 버그를 일으키지 않는다면 눈감고 코드를 지나친 적이 많습니다. 하지만 잘못했다고 하는 것은 아닙니다. 현재 레거시가 되어 버린 그 코드는 작성할 당시에는 나름대로 최고의 방법을 선택한 코드였을테니까요. 어느 날 갑자기 리팩터링 어느 날 운영자분께 아주 간단한 기능 업데이트 요구사항을 받고, 오랜만에 프로젝트를 열어봤습니다. 해당 요구사항은 20분도 채 걸리지 않아 구현되었지만, 이미 12 버전을 달리고 있는 Next.js의 버전은 9버전에 멈춰있고, 각종 This package has been deprecated를 내뿜는 패키지들 그리고 눈에 보이는 과거의 내가 짠 코드들을 마주하니 지나치기가 어려웠습니다. 해당 프로젝트는 외부 사용자가 사용하는 서비스가 아닌, 사내 서비스 운영자분들이 관리하시는 사내 서비스였기에 이슈가 생겨도 바로 공유할 수 있어 비교적 큰 부담 없이 진행을 결정했습니다. Analyzer 도입하기 리팩터링을 진행하면서 번들 크기도 줄이고 싶어, @next/bundle-analyzer를 도입해 최종 번들된 프로젝트 사이즈를 시각화해서 볼 수 있도록 했습니다. // next.conofig.js const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: process.env.ANALYZE === 'true', openAnalyzer: false, }) module.exports = withBundleAnalyzer({}) parsed 값을 보면 됩니다. 전체 2.4MB 정도라 아주 큰 정도는 아니지만, 충분히 줄여볼 만한 것들이 보입니다. Next.js, React 버전 올리기 우선 버전 9 에 머물러 있는 Next.js를 버전 12로 올렸습니다. React는 버전 16이었기에 18로 올릴까 하다가 아직 완벽 지원되지 않는 라이브러리가 있을 수 있음을 고려해 17로 올렸습니다. (아마 조만간 18로 올리게 될 것 같습니다.) import React 제거 react 17로 버전업을 하면서 새로운 JSX Transform덕에 더이상 import React from ‘react’ 구문을 정의해줄 필요가 없어졌습니다. 따라서 모든 파일의 import React from ‘react’ 문을 제거했습니다. Babel을 제거하고 SWC 도입 SWC는 Speedy Web Compiler의 약자로, 빠른 웹 컴파일러의 기능을 제공하는 툴입니다. Next v12부터는 Babel을 사용하지 않고, Rust기반의 SWC를 완전 도입했기에 더욱 빨라진 속도를 체감할 수 있다고 합니다. 로컬에서는 3배 더 빠른 새로고침 속도를, 프로덕션에선 5배 더 빠른 빌드 속도를 보여준다고 합니다. 특히 Babel에 비해 Rust의 컴파일 속도는 무려 17배나 빠르다고 합니다. next 공식 페이지 왜 SWC에서 더 빨라진 속도를 보여줄까요? SWC는 성능 최적화를 위해 다양한 기법을 사용했지만, 가장 대표적인 이유는 Rust의 병렬처리라고 할 수 있습니다. 이벤트 루프 기반의 싱글 스레드 언어인 Javascript와 달리 Rust는 병렬 처리를 고려해서 설계된 언어입니다. 즉 동
2022년 9월 15일
Jotai는 조-타이 라고 읽습니다.
안녕하세요. 웹개발팀에서 프론트엔드 업무를 맡고 있는 밸리입니다.🐰 사내에서 새로운 프로젝트를 맡게 되면서 상태 관리에 대한 필요성을 느끼게 되어 도입한 조타이에 대한 포스팅입니다. ⚙️ 상태를 관리한다. 여러분은 프로젝트에서 상태를 어떻게 관리하고 계신가요? react에서는 기본적으로 자신의 state와 부모가 자식에게 내려주는 props 개념을 도입해 사용하고 있습니다. 순수 state와 props만으로도 상태 관리가 이루어질 수 있지만, 관리해야 하는 상태가 늘어나고 하나의 상태를 다양한 곳에서 공유해서 쓰기에는 여러 한계에 부딪히게 됩니다. 대표적으로 prop drilling의 문제가 있습니다. 요즘 많이 쓰고 있는 Redux, MobX, Zustand 아직은 베타 버전(2022년 7월 기준)이지만, 요즘 핫한 Recoil 그리고 이번 포스팅 주제인 Jotai까지. 이 외에도 상태를 관리하는 라이브러리와 여러 방법론이 대두되는 것을 보아 프론트엔드에서의 상태 관리는 계속 해나가야 할 숙제이자, 높은 중요도를 요하는 작업이 아닌가 싶습니다. 코드의 관심사를 분리하고 쉽게 관리할 수 있어야 하며, 오류 트래킹이 쉬운 프로젝트를 만들기 위해 상태 관리는 꼭 필요한 요소라고 생각합니다. 필자 또한 다양한 상태 관리 라이브러리들을 사용해보고 상태 관리에 대한 많은 고민을 했던 것 같습니다. 물론 지금도 그렇습니다. 🤔 라이브러리 선택에 대한 고민 프로젝트 내에서 관리해야 하는 상태가 많아지면서 상태관리를 어떻게 할 것인가에 대한 고민과 더불어 라이브러리 선택에 대한 고민이 시작되었습니다. 여러 라이브러리들 중 선택지를 추려보았습니다. 아래 라이브러리들은 서로의 대체재라기보다는 각 상황에 가장 잘 맞는 라이브러리를 선택하셔서 쓰시면 됩니다. 현재까지 가장 높은 점유율을 차지하고 있는 것은 역시 react-redux입니다. Redux ContextAPI(라이브러리는 아닙니다.) Recoil Jotai 아주 간략하게만 정리해보았습니다. Redux Redux는 Flux 아키텍쳐에서 영감을 받아 상태의 중앙화로 prop drilling의 문제를 해결했습니다. 현재까지도 굉장히 인기 있는 라이브러리이며, 저 또한 가장 많이 사용했고, Redux Toolkit과 Redux-Saga를 함께 사용했었습니다. 장점으로는 관련 자료 및 커뮤니티가 방대하며, 신뢰성 있는 라이브러리입니다. 더불어 디버깅에 굉장히 용이하다는 장점을 가지고 있습니다. 큰 서비스일수록 디버깅의 역할이 돋보일 것이라고 생각합니다. 하지만 Redux는 코드량과 학습의 러닝커브 초기에 구성에 있어서 장황한 보일러플레이트를 작성해야 한다는 점을 단점으로 뽑을 수 있습니다. 2. Context API Context API의 경우 리액트에 내장되어 있기에 별도의 설치가 필요 없다는 게 가장 큰 장점입니다. react-redux 또한 Context API가 내장되어 있습니다. 하지만 Provider의 값이 변경될 경우 그 Context를 구독하고 있는 하위의 모든 컴포넌트들의 불필요한 렌더링이 일어나기 때문에 반복, 복잡한 업데이트에서 사용할 경우 최적화에 신경 써야 할 부분이 늘어나 오히려 비효율적입니다. 3. Recoil Recoil은 페이스북에서 개발한 요즘 새롭게 뜨고 있는 상태 관리 라이브러리입니다. 데이터를 atom이라는 단위로 선언하여 사용합니다. Recoil은 리액트스럽고. 리액트처럼 사고합니다. React hooks에 익숙한 분들이라면 다루기가 굉장히 쉽다는 장점이 있습니다. Recoil에서 가장 크게 느꼈던 매력이 바로 이 부분입니다. 최근에는 많이 안정화가 되긴 했지만 아직 베타 버전이라는 단점을 가지고 있습니다. 4. Jotai 사내 다른 개발자 분께 추천을 받았던 라이브러리 입니다. Recoil을 사용하고 싶은데, 베타 버전이라는 점에서 아쉬움을 느꼈다면 서칭해보기 좋은 라이브러리라는 생각이 듭니다. Jotai는 굉장히 가볍습니다. Recoil에 비해 1/6 정도의 번들 사이즈를 가지고 있습니다. Context의 re-rendering 문제를 해
2022년 7월 14일
MSW 사용하여 테스트 코드 작성하기 with GraphQL, react-query
서론 안녕하세요. 더핑크퐁컴퍼니 웹개발팀에서 프론트엔드 개발 업무를 맡고 있는 밸리입니다.🐰 기존 프로젝트는 GraphQL과 Apollo Client의 조합으로 개발하였고, 테스트를 진행할 때는 Apollo Client에서 제공하는 MockedProvider를 사용하여 테스트를 진행했었습니다. 새로운 프로젝트에서 GraphQL과 react-query의 조합으로 개발을 진행하게 되면서, 어떻게 테스트를 진행할지에 대해 고민을 하게 되었습니다. react-query 공식문서에는 @testing-library/react-hooks 를 이용한 테스트 방법이 기재되어 있으나, 단순 react-query hooks에 대한 테스트뿐만 아니라, 다양한 케이스의 API request, response로 렌더링된 요소들에 대한 테스트를 진행하기 위해서 다른 방법을 고민해보아야 했습니다. 애초에 테스트 환경에서는 실제 서버에 API 요청을 날리는 경우는 많지 않습니다. 프론트엔드 테스트인데, 서버의 API 가 실제로 작동하고 안 하고는 서버쪽의 관심사이기 때문입니다. 또한 실제 서버에 API Call을 날리면 여러 리스크가 생길 수 있습니다.(물론 서버에 직접 API Call을 하는 테스트도 있습니다.) 따라서 테스트 환경에서 API request, response를 Mocking하는 작업이 필요했습니다. @testing-library/react-hooks 을 활용한 테스트 예제는 아래 공식 문서를 참고 하세요! https://react-query.tanstack.com/guides/testing#_top Mock Service Worker(MSW) 고민 끝에, 저희팀에서는 Mock Service Worker (MSW) 라이브러리를 채택했습니다. MSW는 API Mocking 라이브러리로, 서버로 가는 네트워크 요청을 가로채서 mocked response를 내려주는 역할을 합니다. 즉 API를 Mocking 해야 하는 모든 상황에서 유용하게 쓰일 수 있습니다. 예로 프론트엔드 개발자가 백엔드 개발자로부터 아직 개발에 필요한 API 를 받지 못한 상황이라면, MockData를 작성해두고, 화면을 구상할 수 있을 것 입니다. 하지만 이는 네트워크 레벨에서 사용해 볼 수 없다는 단점을 가지고 있습니다. 네트워크 레벨에서 통신하는 게 아니므로 axios 나 fetch 도 사용할 수가 없을뿐더러, 네트워크 통신 속에 발생하는 여러 상황들에 맞추어 처리해 놓은 UI 부분에 대해서는 (loading이나 error 등) 완벽한 테스트를 해볼 수가 없습니다. 이럴 때 MSW를 사용하면, Mock 서버 없이도 API 요청들을 네트워크 레벨에서 Mocking 하여 사용할 수 있게 됩니다. MSW는 어떻게 Mocking한 API 요청들을 네트워크 레벨에서 사용할 수 있게 하는 것 일 까요? Service Worker를 사용하는 MSW 우선 MSW의 내부 동작 원리를 보면 아래와 같이 동작하는 것을 알 수 있습니다. 중간에 보면 Service Worker 라는 것이 보이시나요? MSW는 Service Worker를 통해 네트워크 요청을 가로채고 모의 응답을 반환합니다. Service Worker란 무엇일까요? 서비스 워커는 웹 응용 프로그램, 브라우저, 그리고 (사용 가능한 경우) 네트워크 사이의 프록시 서버 역할을 합니다. MDN에 기재되어 있는 Service Worker의 설명입니다. MSW는 Service Worker를 통해 네트워크 사이의 프록시 서버 역할을 하고 있는 것입니다. Service Workder API는 모던 브라우저에서 제공하는 표준 API이므로 타 라이브러리들과의 종속성 문제나 호환성 문제를 고려하지 않아도 된다는 장점이 있습니다. 핵심은 Service Worker 는 앱을 구동하는 메인 스레드와는 다른 스레드(백그라운드 스레드)에서 동작합니다. 즉, 논블로킹으로 작동하므로 메인 스레드의 연산을 가로막지 않습니다. ❗️ 단, 주의할 점이 있습니다. 서비스 워커는 보안 상의 이유로 HTTPS에서만 동작합니다. 네트워크 요청을 수정할
2022년 6월 14일
Google BigQuery와 Data Studio를 활용한 앱 광고 매출 대시보드 개발기
Google BigQuery와 Data Studio를 활용한 매출 분석 대시보드 개발기 안녕하세요, 더핑크퐁컴퍼니 웹개발팀에서 백엔드 업무를 맡고 있는 메타입니다. 🌱 이번에 기존 AWS RDS와 PHP로 운영중이었던, 앱 광고 매출 분석 시스템을 Google BigQuery와 Data Studio를 이용해 이전 및 확장 작업을 진행하면서 느낀 점을 정리해보았습니다. 먼저 요구사항은 아래와 같았습니다. 사내 앱 광고 매출 분석 시스템에서 트래킹 하고 있던 플랫폼을 기존 Admob 1곳에서 8곳으로 늘려주세요. 그리고 그 8곳에서의 매출 현황을 한눈에 보고 싶어요! 🤔 레거시 시스템을 수정할 것이냐 VS 새로운 시스템을 도입할 것이냐 기존 시스템은 새로운 광고 플랫폼 1곳을 추가 할때마다, 별도 추가 테이블을 생성하고 추가된 데이터를 보여주기 위한 프론트 단 개발도 들어가야 했기 때문에 플랫폼이 추가되고 제거될 때마다 PHP 버전 이슈 등 프론트 쪽에서도 신경써야 하는 부분이 많았고, 플랫폼에 등록되어있는 매출 이외에 부가정보(앱정보, 광고단위 정보 등)를 수동으로 관리 하고 있었기 때문에, 운영하면서 타깃 플랫폼이 늘어날 때마다 수동으로 관리해야 하는 포인트도 따라서 늘어나는 문제가 있었습니다. 그래서 과감하게 새로운 시스템을 도입하기로 했습니다. 먼저 프론트 개발 없이 데이터 분석을 가능하게 하는 도구들을 리서치해보았습니다. 후보군은 아래와 같았습니다. elasticsearch + Kibana BigQuery + Data Studio 제일 먼저 생각해 보았던 것은 이전에 앱 사용자 로그 처리 및 분석 작업을 진행 하면서 사용해보았던 elasticsearch + Kibana 스택이었습니다. 하지만 elasticsearch는 RDB에서의 relation과 같은 개념이 없기 때문에 매출정보와 앱정보, 광고단위 정보를 별도 index로 관리할 수 없다는 단점이 있었습니다. (elasticsearch에서 관계형 데이터 모델링 방법이 있기는 하지만, 별도의 두 인덱스를 조인하는 개념이 아니라 하나의 인덱스에 계층이나, 부모 구조의 형태로 구성된 데이터를 넣고 관리하는 느낌) 한번 발생하면 수정될 일이 없는 로그 정보와는 달리 각 광고 플랫폼에 등록된 앱정보나, 광고단위 정보는 운영 중 수시로 바뀔 수 있는 정보였기 때문에 elasticsearch + Kibana로 그 정보들까지 묶어서 확인하기에는 쉽지 않을것 같았습니다. 그리고 elastic cloud 서비스를 이용해 240GB 까지의 스토리지를 할당 받아 사용하고 있었기 때문에, 추후 별도의 확장 이슈가 또 생긴다는 점도 단점으로 생각했습니다. 그래서 그 대안으로 고민해보았던 도구는 BigQuery + Data Studio 였습니다. Google BigQuery 장단점 😃 BigQuery를 알아보면서 장점으로 생각되었던 점은 완전관리형 엔터프라이즈 데이터 웨어하우스로, 별도 설치 운영할 필요가 없고 그렇기 때문에 나중에 생길 수 있는 확장 이슈에 대응이 쉽다. SQL을 지원하기 때문에 접근성이 좋고, RDB처럼 테이블간 relation을 지원하기 때문에 데이터를 테이블 별로 나누어 관리가 가능하다. 쿼리 결과를 클릭 몇 번으로 구글 시트로 내보거나, Data Studio를 통해 시각화 대시보드를 만들어 다른 사람들과 공유할 수 있다. on-demand 가격 정책을 지원하기 때문에 사용량이 많지 않을 때 비용 절감이 가능하다. 파티션 나누기, 자동 캐싱 처리 등 쿼리 비용 절감을 위한 여러 방법들이 존재한다. 🤔 단점으로 생각되었던 점은 BigQuery에는 키나 인덱스의 개념이 없다. 무조건 풀스캔이다. 그렇기 때문에 기존 RDB나 elasticsearch로 운영했을 때만큼의 쿼리 속도를 보장하기 힘들다는 점이었습니다. 하지만 앱 광고 매출 분석 시스템의 경우 빈번하게 데이터의 수정, 삭제가 일어나지도 않고, 조회 작업도 대시보드를 이용하는 팀에서 데이터 분석 작업이 있을 때만 몇 번 일어나는 게 전부이기에, OLTP보다는 OLAP 시스템에 특화 된 BigQuery
2022년 6월 10일
react-testing-library 의 fireEvent 를 userEvent로 마이그레이션 하기 with Antd
안녕하세요. 더핑크퐁컴퍼니에서 프론트엔드 개발 업무를 맡고 있는 밸리 입니다.🐰 이번 글에서는 사내 백오피스 서비스에 프론트엔드 테스트를 붙이고, 마이그레이션한 경험을 공유 드리고자 합니다. 글을 읽기 전 userEvent에 대한 모든 API를 다루지 않습니다. 사용하면서 겪었던 어려움이나, 14로 업데이트 하며 바뀐 점 그리고 사용 경험기를 공유합니다. 자세한 내용은 공식 문서를 참고하시길 바랍니다. @testing-library testing-library는 UI 컴포넌트를 테스트하도록 도와주는 패키지 묶음입니다. 해당 라이브러리는 BDT(Behavior Driven Test)라는 행위 주도 테스트 방법론을 기반으로 하고 있습니다. 기존에는 IDT(Implementation Driven Test)라는 구현 주도 테스트를 주로 진행해왔습니다. 이에 대표적인 테스트 도구로 Enzyme이 있습니다. 둘의 차이를 간단하게 설명하기 위해 간단한 코드를 작성해보았습니다. 아래 코드를 테스트한다고 했을 때 BDT와 IDT는 테스트의 목적이 다릅니다. <div> Hello My Test <strong>important content</strong> </div> IDT는 Hello My Test 라는 텍스트가 div 태그에 들어가 있는지, important content 라는 텍스트가 strong 태그 안에 들어 있는지에 대한 여부가 테스트가 중심이 되지만, BDT는 Hello My Test 가 실제로 페이지 내에 그려졌는지, important content 가 페이지 내에 그려졌는지가 중심이 됩니다. BDT가 대두되었던 이유는 유저 입장에서는 해당 콘텐츠가 어떤 태그로 구현되어 있고 어떤 구조를 가지고 있는지보다, 내가 원하는 콘텐츠가 보여지고 있는지가 더 중요하기 때문입니다. @testing-library 에서는 리액트 코드를 테스트 할 수 있도록 @testing-library/react 를 제공합니다. npm trends 에서 근 1년간 npm 다운로드수를 표시한 그래프 입니다. @testing-library/react와 Enzyme을 비교했을 때 그 인기를 실감할 수 있습니다. react-testing-library의 fireEvent를 userEvent로 변경한 이유 기존에 사내 백오피스에 붙어있던 테스트 코드는 fireEvent를 사용했습니다. fireEvent는 유저가 발생시키는 액션에 대한 테스트를 진행하기 위해 나온 react-testing-library api 입니다. click, type, focus, cut 등의 유저의 이벤트를 정의하고 있습니다. 2018년도 무렵 userEvent 의 v1 이 출시 되었습니다. userEvent는 fireEvent보다 사용자의 상호작용을 더 세세하게 구현했다고 되어있습니다. 간단한 예시를 들어보겠습니다. 유저가 클릭 이벤트를 호출할 때 무슨 일이 일어날까요? 상황마다 다를 수 있지만, 일반적으로는 마우스를 호버한 후에 클릭을 하는 등의 이벤트들이 발생할 것입니다. 대표적인 fireEvent의 click 과 userEvent 의 click 을 비교해보겠습니다. fireEvent.click 은 단순히 click 이벤트만 발생시키지만, userEvent.click 은 어떤 element에서 발생한 이벤트인지 체크한 후에 해당 element 에서 클릭이벤트 호출 시 실제 브라우저에서 발생하는 모든 이벤트를 같이 발생시켜줍니다. 아래는 userEvent click의 내부 코드 입니다. function click(element, init, {skipHover = false, clickCount = 0} = {}) { if (!skipHover) hover(element, init) switch (element.tagName) { case 'LABEL': clickLabel(element, init, {clickCount}) break case 'INPUT': if (element.type === 'checkbox' || element.type === 'radio') { click
2022년 5월 3일