카카오/PostgreSQL

3 개의 포스트

kakao4분 읽기큐레이션 요약

개인화된 Airflow 테스트 환경 구축 및 운영 경험

수천 개의 Airflow DAG를 운영하는 카카오 데이터서비스 조직은 테스트 과정의 반복 작업과 환경 간 차이, 리소스 충돌 문제를 해결하기 위해 PR 단위의 개인 Airflow 환경인 AirZone을 구축했습니다. AirZone은 GitHub PR 코멘트에서 생성·삭제를 요청하면 Kubernetes Job과 전용 Helm 차트로 격리된 Airflow를 배포합니다. 사용자는 인프라를 직접 구성하지 않고 실제 운영 환경과 유사한 하둡·인증 환경에서 DAG를 테스트할 수 있습니다. ## 기존 테스트 방식의 한계 - **로컬 Airflow** - Airflow뿐 아니라 하둡 인증, 연결 설정, Docker 환경까지 직접 구성해야 합니다. - 초기 구축 비용이 크고, 로컬 환경과 운영 환경의 차이로 인해 실제 배포 후 실패할 수 있습니다. - **개발용 Airflow** - 코드를 커밋하고 푸시한 뒤 GitHub webhook, submodule 업데이트, DAG 파일 처리 과정을 기다려야 합니다. - DAG를 수정할 때마다 동기화 지연이 반복되어 개발 속도를 떨어뜨렸습니다. - **테스트용 Airflow** - SSH 컨테이너에 로컬 파일을 복사해야 하므로 코드 수정 때마다 추가 작업이 필요합니다. - 실제 데이터와 하둡에 접근하려면 prod VPN을 연결해야 하는 불편도 있었습니다. - **Production Airflow에서의 테스트** - 테스트 DAG가 스케줄러와 워커 자원을 점유해 다른 프로젝트의 실행을 지연시킬 수 있습니다. - 과도한 리소스 사용으로 노드 장애가 발생하면 같은 노드의 다른 태스크까지 중단될 위험이 있습니다. ## AirZone의 핵심 요구사항 - Kubernetes나 Helm을 몰라도 브라우저에서 Airflow 환경을 생성하고 삭제할 수 있어야 합니다. - 사용자는 DAG 검증에만 집중하고, 인프라 구성은 AirZone이 담당해야 합니다. - Jupyter Notebook을 제공해 별도 로컬 환경 없이 코드를 수정할 수 있어야 합니다. - 운영 환경과 유사한 DAG 실행 환경과 하둡 인증 방식을 제공해야 합니다. - PR마다 독립된 Airflow를 생성해 사용자와 테스트 작업을 서로 격리해야 합니다. ## PR 단위의 격리된 환경 - 레포지터리명과 PR 번호를 조합해 Kubernetes 네임스페이스를 생성합니다. - Airflow 웹 서버, 스케줄러, PostgreSQL, Jupyter, DAG PVC, 로그가 PR별로 분리됩니다. - 한 PR의 테스트가 다른 프로젝트의 스케줄러·워커 자원을 침범하지 않습니다. - 리뷰어는 PR에 남은 링크로 특정 코드 상태의 실행 결과를 직접 확인할 수 있습니다. - PR이 종료되면 네임스페이스를 기준으로 관련 리소스를 쉽게 정리할 수 있습니다. ## 요청 처리와 배포 작업의 분리 - `airzone-api`는 PR 존재 여부, PR이 열려 있는지, 네임스페이스 중복 여부 등 요청의 유효성만 검증합니다. - 실제 Helm 설치와 헬스체크는 별도의 Kubernetes Job이 수행합니다. - API가 수 분이 걸리는 배포 작업을 직접 기다리지 않으므로 빠르게 응답할 수 있습니다. - Job별로 상태와 로그가 독립적으로 남아 실패 단계와 원인을 추적하기 쉽습니다. - 실패한 Job을 삭제한 뒤 새 Job을 생성하는 방식으로 배포를 재시도할 수 있습니다. - 생성 요청은 `create-airzone-{namespace}`, 삭제 요청은 `delete-airzone-{namespace}` 형식의 Job 이름을 사용합니다. ## GitHub PR 코멘트를 사용자 인터페이스로 활용 - PR 생성 이벤트를 webhook으로 받아 저장소, 브랜치, PR 번호, 요청자 정보를 확인합니다. - 사용자가 선택할 수 있도록 하둡 환경별 AirZone 생성 링크를 PR 코멘트에 남깁니다. - 생성 완료 결과와 접속 정보도 PR에 표시해 별도 플랫폼 없이 테스트를 시작할 수 있습니다. - 다만 Jupyter 토큰과 Kubernetes 네임스페이스 토큰처럼 민감한 정보는 공개 범위가 넓은 PR 대신 카카오워크로 전달합니다. - 요청 접수와 배포 완료 시점에 카카오워크 알림을 보내 진행 상태를 알립니다. ## 전용 Helm 차트로 구성한 Airflow 기존 운영용 Airflow 차트가 아닌 AirZone 전용 Helm 차트를 만들어 테스트 환경에 필요한 구성만 묶었습니다. - **Airflow 구성** - 웹 서버와 스케줄러를 배포합니다. - `KubernetesExecutor`, DAG 스캔 주기, 로그 설정, 하둡 관련 변수를 테스트 환경에 맞게 설정합니다. - **데이터베이스와 저장소** - 개인 환경용 PostgreSQL을 함께 배포합니다. - scheduler와 Jupyter가 같은 DAG 작업 디렉터리를 사용하도록 DAG PVC를 공유합니다. - **DAG 동기화** - PR의 head repository와 branch 정보를 받아 해당 코드만 동기화합니다. - Git 초기화 컨테이너 등을 통해 배포 환경에 테스트 대상 DAG를 준비합니다. - **인증과 보안** - 사용자·공용 principal, 키탭, Jupyter 토큰을 환경에 주입합니다. - 하둡 접근에 필요한 Kerberos 인증을 운영 환경과 유사하게 구성합니다. - dkos에서 제공하는 TLS 인증서도 테스트 환경에 반영합니다. - **운영 연동** - 여유 있는 노드 그룹과 같은 리전의 `storageClass`를 선택합니다. - Airflow 로그를 Elasticsearch와 Kibana에서 확인할 수 있도록 연결 정보를 주입합니다. - Jupyter를 함께 제공해 브라우저에서 DAG와 관련 코드를 수정할 수 있게 합니다. ## 자동 정리와 운영 구조 - 생성·삭제 요청은 Kubernetes Job으로 처리합니다. - PR 종료 후 남아 있는 AirZone은 매일 실행되는 CronJob이 자동으로 회수합니다. - 사용자에게는 “PR 코멘트의 링크를 누르는 기능”으로 단순하게 보이지만, 운영자는 요청·설치·헬스체크·알림·정리 단계를 각각 추적할 수 있습니다. - 운영 환경에서 불필요한 PGBouncer나 외부 DB 연결 등은 제외해 개인 테스트 환경의 복잡도와 비용을 줄였습니다. AirZone과 같은 구조를 도입할 때는 테스트 환경을 운영 환경과 최대한 유사하게 유지하되, PR 또는 브랜치 단위로 리소스를 격리하는 것이 중요합니다. 또한 긴 배포 작업은 API 요청과 분리하고, Kubernetes Job의 상태·로그·재시도 기능을 활용하면 장애 대응과 운영 추적이 훨씬 쉬워집니다.

원문 읽기(새 탭에서 열림)
kakao4분 읽기큐레이션 요약

학생에서 개발자로: DB, 보안부터 AI까지, 정답보다 합리적인 선택을 배우다

카카오 신입 개발자 온보딩은 기술의 정답을 암기하는 교육이 아니라, 대규모 트래픽과 운영 환경을 견디는 합리적 설계를 배우는 과정이었다. DB에서는 변경과 운영 비용을 고려하고, 보안에서는 취약점을 자신의 코드 문제로 인식하며, AI에서는 모델의 불확실성을 시스템 설계로 통제하는 관점을 익혔다. 결국 개발자의 핵심 역량은 상황과 리소스에 맞는 선택을 하고, 그 근거를 팀과 공유·설득하는 능력이라는 결론에 도달한다. ## DB: 이론적 정답보다 운영 가능성을 우선하기 - 무결성은 반드시 DB가 전부 책임져야 하는 규칙이 아니라, DB와 애플리케이션 중 어디에 책임을 둘지 정하는 운영 모델이다. - 물리적 Foreign Key는 무결성을 보장하지만, 트래픽과 변경이 많은 환경에서는 락, 성능 저하, 스키마 변경의 유연성 문제를 일으킬 수 있다. - 애플리케이션이 무결성을 담당한다면 테스트와 데이터 보정 로직을 강화해야 한다. - 삭제된 데이터를 복구하거나 감사 추적해야 하므로 `deleted_at`을 활용한 소프트 딜리트가 실무의 중요한 운영 전략이 된다. ## 인덱스와 SQL: 결과가 아니라 실행 경로 설계하기 - 인덱스는 단순히 조회 속도를 높이는 기능이 아니라, 데이터 특성과 질의 방식에 맞는 자료구조 선택이다. - B-Tree 외에도 GIN, GiST, SP-GiST, Vector 인덱스 등 다양한 선택지가 있으며, DB가 어떤 질문을 받는지에 따라 적합한 인덱스가 달라진다. - 실행 계획을 확인하면 동일한 SQL이라도 인덱스를 사용하는지, 테이블 전체를 스캔하는지 파악할 수 있다. - 이러한 실행 경로의 차이는 디스크 I/O와 응답 시간에 직접 영향을 준다. - SQL 작성은 단순히 원하는 결과를 반환하는 작업이 아니라, 효율적인 실행 경로를 유도하는 작업으로 이해해야 한다. ## 중복과 반정규화: 데이터 중복을 성능 전략으로 활용하기 - 정규화와 JOIN이 항상 최선은 아니며, 데이터 규모와 트래픽이 커지면 JOIN이 병목이 될 수 있다. - 읽기 성능을 위해 일부 데이터를 의도적으로 중복 저장하는 반정규화가 합리적인 선택이 될 수 있다. - 당시의 비즈니스 상태를 보존해야 한다면 관련 정보를 스냅샷으로 저장하는 방식도 필요하다. - MongoDB에서는 관계를 `ref`로만 연결할 경우 조회가 복잡해질 수 있어, 화면에 필요한 정보를 함께 저장하면 추가 조회를 줄일 수 있다. ## DB 생태계: 성능·일관성·확장성의 트레이드오프 이해하기 - MySQL의 고가용성 구조, PostgreSQL의 PK 설계, Neon의 스토리지·컴퓨팅 분리 등 DBMS마다 고유한 설계 철학이 있다. - 어떤 시스템도 모든 상황에서 최선일 수 없으며, 성능·일관성·확장성·운영 비용 사이의 균형을 선택해야 한다. - Hadoop과 Spark 같은 빅데이터 기술도 개별 도구가 아니라 저장, 관리, 처리, 분석으로 이어지는 데이터 생태계의 일부로 이해해야 한다. - 이론적으로 완벽한 구조보다 변경에 안전하고 운영 비용을 감당할 수 있는 설계를 우선하게 되었다. ## 보안: 외부 조직의 일이 아니라 개발자의 기본값 - 개인정보 유출 가능성을 가정하면서 보안을 규정이나 인프라팀의 업무가 아닌 자신의 코드에서 시작되는 책임으로 인식하게 되었다. - Dev/Prod 분리, VPN, 백신 등은 편의성을 일부 희생하더라도 안전을 확보하기 위한 장치다. - DDoS 대응에서는 공격을 차단하는 것뿐 아니라 정상적인 트래픽 폭증과 공격을 구분하는 일이 어렵다는 점을 배웠다. - 개발자가 적용할 수 있는 기본 방어책으로 Rate Limit을 활용하고, 이상 징후가 발생하면 혼자 해결하지 않고 대응 체계에 연결해야 한다. ## API 보안과 지속적인 점검 - 취약점을 직접 공격해보는 실습을 통해 보안을 이론이 아닌 자신의 코드에 대한 문제로 체감했다. - AI를 활용한 취약점 탐색, QR 코드 공격, 앱 권한 악용처럼 방어 기술과 공격 기술이 함께 발전하고 있다. - 보안은 배포 직전에 한 번 점검하는 절차가 아니라 개발 초기부터 기본값으로 포함되어야 한다. - 코드 품질은 작성자가 자리를 비워도 다른 개발자가 빠르게 이해하고 운영할 수 있는지까지 포함한다. ## AI Agent: 모델보다 중요한 것은 시스템 설계 - AI Agent는 특별한 모델 자체라기보다, 도구 호출, 라우팅, 예외 처리, LLM 호출을 조합한 시스템 설계에 가깝다. - 기존 서비스 개발에서 사용하던 함수 분리와 조건 분기, 장애 대응 습관을 AI 시스템에도 적용할 수 있다. - LLM은 확률 모델이므로 매번 결과가 달라질 수 있어, 한 번의 뛰어난 답보다 일관된 출력과 안정적인 실행 흐름을 설계해야 한다. - Prompt Chaining으로 작업을 단계별로 나누고, Few-shot으로 출력 형식을 구체화하며, Routing으로 상황에 따라 프롬프트를 분기할 수 있다. ## 멀티 에이전트와 RAG·MCP의 결합 - 하나의 거대한 AI에 모든 역할을 맡기기보다 분석, 콘텐츠 생성 등 역할을 나눈 멀티 에이전트 구조가 더 빠르고 안정적일 수 있다. - 이는 기능과 책임을 분리하는 MSA의 설계 철학과 유사하다. - MCP는 내부 시스템의 데이터와 기능을 AI가 호출할 수 있는 Tool로 노출해, 원격 Function Calling처럼 활용하게 한다. - RAG는 문서를 청킹하고 유사 벡터를 검색해 관련 정보를 컨텍스트에 추가함으로써 할루시네이션을 구조적으로 줄인다. - AI를 잘 활용하려면 결과가 마음에 들지 않을 때 감정적으로 반응하기보다 목표, 출력 형식, 예시, 필요한 데이터와 컨텍스트를 명확히 정의해야 한다. 실무에서는 “무엇이 이론적으로 맞는가”보다 트래픽, 변경 가능성, 보안 위험, 운영 비용을 함께 검토해야 한다. DB 설계와 보안 점검, AI 기능 개발 모두를 일회성 작업이 아니라 지속적으로 관찰하고 개선하는 시스템으로 접근하는 것이 바람직하다.

원문 읽기(새 탭에서 열림)
kakao5분 읽기큐레이션 요약

학생에서 개발자로: 로또 구현부터 레거시 개선까지, 서버의 흐름을 배우다

서버 개발은 복잡해 보이지만, 설계 이유를 질문하고 검증하는 과정을 거치면 막연함을 줄일 수 있다는 것이 글의 핵심 주장입니다. 카카오의 기술 온보딩은 TDD·객체지향 구현, 레거시 인수 테스트, 리팩터링을 단계적으로 수행하며 유지보수 가능한 구조와 안전한 변경 능력을 길렀습니다. 결국 좋은 개발자는 코드를 작성하는 데 그치지 않고, 설계·테스트·협업·AI 활용의 기준을 스스로 세우는 사람이라는 결론입니다. ## 기술 온보딩의 목표와 구성 - 온보딩은 총 3단계로 진행되었습니다. 1. TDD와 OOP 기반 기능 구현 2. 레거시 코드에 대한 인수 테스트 작성 3. 테스트로 보호된 레거시 코드 리팩터링 - 정답을 전달하기보다 다음과 같은 질문을 반복하며 설계의 근거를 고민하게 했습니다. - 왜 이렇게 설계했는가? - 이 책임은 정말 해당 객체가 가져야 하는가? - 이 테스트는 무엇을 보호하는가? - 목표는 유지보수 가능한 구조 설계, 레거시 분석 및 안전한 개선, 협업과 AI를 포함한 책임 있는 개발 역량을 기르는 것이었습니다. - 서버뿐 아니라 FE, Android, iOS 직군도 참여했으며, 기술 스택과 관계없이 좋은 엔지니어링의 기준은 공유될 수 있다는 점을 강조했습니다. ## 질문과 협업으로 서버 개발의 막연함 줄이기 - 트래픽, 동시성, 확장성, 데이터베이스 설계처럼 추상적으로 느껴지는 주제를 실제 구현과 리뷰를 통해 구체화했습니다. - 매일 데일리 미팅에서 트러블슈팅을 공유하고, 페어 프로그래밍으로 설계를 논의하며, PR 리뷰에서 구현 이유를 설명했습니다. - 이를 통해 개발은 개인의 코딩 능력만으로 완성되는 일이 아니라는 점을 체감했습니다. - 코드의 동작 여부보다 스스로 설계를 설명하고 변경의 영향을 예측하는 능력을 중요하게 다뤘습니다. ## 로또 게임 구현: TDD와 객체지향 설계 - 첫 번째 미션은 로또 가격, 자동·수동 발급, 당첨 통계를 구현하는 과제였습니다. - 다음과 같은 제약 조건이 설계 개선을 유도했습니다. - 들여쓰기 깊이 1단계 유지 - 메서드 10라인 이하 - 원시값 포장과 일급 컬렉션 사용 - `else` 사용을 줄이고 Early Return 활용 - TDD 방식으로 테스트를 먼저 작성해 요구사항과 설계를 점검했습니다. ### 랜덤 로직의 테스트 가능성 확보 - 랜덤 번호 생성은 실행마다 결과가 달라 테스트가 어려웠습니다. - 이를 해결하기 위해: - 번호 생성 전략을 인터페이스로 추상화하고 - 생성 전략을 외부에서 주입받으며 - 테스트 전용 Generator를 별도로 구현했습니다. - 그 결과 테스트에서 생성 값을 통제할 수 있었고, 구현체에 대한 결합도도 낮아졌습니다. - TDD는 단순히 테스트를 추가하는 방식이 아니라, 테스트 가능한 구조를 설계하게 만드는 도구로 작용했습니다. ### 값 객체와 캐싱에 대한 고민 - 같은 값을 가진 객체를 매번 새로 생성할지, 재사용할지 고민하며 객체의 정체성과 값의 동일성을 구분했습니다. - 1부터 45까지의 로또 번호처럼 값의 범위가 제한된 경우 캐싱 전략을 검토할 수 있었습니다. - 이 미션을 통해 기능 구현보다 객체의 책임, 생성 방식, 재사용 가능성 등 설계 기준을 고민하게 되었습니다. ## 인수 테스트: 레거시를 안전하게 이해하기 - 두 번째 미션에서는 실제 서비스 수준의 레거시 코드를 바로 수정하지 않고, 먼저 인수 테스트를 작성했습니다. - 테스트가 보호해야 할 대상은 다음과 같았습니다. - 사용자의 행동 - 시스템의 반환 결과 - 외부에서 관찰 가능한 상태 변화 - 단순히 성공 여부만 확인하는 것이 아니라, 결과가 정확한지 검증하는 Strong Assertion 전략을 적용했습니다. - Cucumber 기반 BDD를 사용해 비개발자도 이해할 수 있는 시나리오 형태로 테스트를 구성했습니다. - 테스트를 개발자만의 코드가 아니라 팀 전체가 공유하는 실행 가능한 명세로 바라보았습니다. ### 운영 환경과 테스트 환경 맞추기 - “내 컴퓨터에서는 동작한다”는 문제를 줄이기 위해 Production Parity를 적용했습니다. - 구체적으로: - H2 대신 운영과 같은 PostgreSQL 사용 - Docker 기반으로 실행 환경 통일 - Gradle Task를 이용한 테스트 자동화 - 환경 차이로 인한 테스트 결과의 불일치를 줄이고, 누구나 동일한 조건에서 테스트를 실행할 수 있게 했습니다. ### 테스트 데이터 격리 - 테스트 간 데이터 의존성을 제거하기 위해 다음 전략을 사용했습니다. - 외래 키 관계를 고려한 역순 삭제 - `TRUNCATE ... CASCADE` - 공통 Cleanup 유틸리티 작성 - 모든 테스트가 초기화된 동일한 상태에서 시작하도록 보장해 테스트의 재현성과 안정성을 높였습니다. - 중요한 것은 특정 도구를 사용하는 것보다 상황에 맞는 데이터 격리 방법을 선택하는 판단 기준이라고 설명합니다. ## 레거시 리팩터링: 구조와 동작의 분리 - 세 번째 미션의 핵심은 레거시 코드를 단순히 “클린 코드”로 바꾸는 것이 아니라, 안전한 변경의 기준을 세우는 것이었습니다. - 가장 중요한 원칙은 구조 변경과 동작 변경을 분리하는 것입니다. - 구조를 개선할 때는 기존 동작을 유지 - 동작을 변경할 때는 구조 개선과 섞지 않기 - 이렇게 변경 목적을 분리하면 코드 리뷰와 테스트를 통해 변경 범위를 명확히 검증할 수 있습니다. - 의도하지 않은 동작 변화가 발생하면 리뷰에서 이를 찾아내고, 변경을 통제하는 능력을 기를 수 있었습니다. ## AI와 협업할 때의 검증 범위 - 리팩터링 과정에서 AI를 활용해 넓은 범위의 코드 개선을 빠르게 시도했습니다. - 그러나 AI에게 한 번에 큰 범위의 변경을 요청하면 수정량이 커져 검증이 어려워지는 문제가 발생했습니다. - 따라서 AI의 제안을 그대로 수용하기보다 변경 범위를 작게 나누고, 각 변경을 테스트와 리뷰로 확인하는 방식이 필요하다는 교훈을 얻었습니다. - AI는 개발자를 대신하는 도구가 아니라, 개발자가 책임 있게 검토하고 통제해야 하는 협업 도구로 다뤄졌습니다. 실무에서는 기능 구현 전에 책임과 설계 이유를 설명할 수 있는지 확인하고, 레거시 코드는 먼저 외부 동작을 보호하는 테스트를 마련하는 것이 좋습니다. 이후 구조 변경과 동작 변경을 분리해 작은 단위로 개선하며, AI를 사용할 때도 변경 범위를 제한하고 반드시 테스트와 리뷰로 검증하는 접근이 안전합니다.

원문 읽기(새 탭에서 열림)