
기억하는 AI : Agentic Memory 그리고 A-MEN
자율 에이전트(Autonomous Agent)에 대해 살펴본게 엊그제 같은데 이게 현실화 되는걸 눈으로 보는게 신기한 시대입니다.이게 가능했던 이유는 바로 추론(reasoning) 모델이 등장했기 때문입니다. 추론 모델(Reasoning Model)과 Autonomous Agent는 뇌(두뇌)와 몸(행동 주체)의 관계처럼, 서로 유기적으로 연결되어 있습니다.Autonomous Agent는 memory, planning, action, tools로 구성됩니다. 여기에서 Agentic Memory는 Long-term memory에 속합니다.추론 모델은 Chain-of-Thought, Tree-of-Thought, ReAct, Reflection-Rewrite 등을 통해 복잡한 문제를 해결하기 위해 생각의 흐름을 구성하고 실행 전략을 만듭니다.이렇게 만들어진 모델을 이용하여 Autonomous Agent는 검색, API 호출, DB 조회, 문서 생성 등 다양한 도구나 기능을 활용하여 외부 목표를 받아 스스로 계획하고, 판단하고, 실행합니다.• None• None Reflection: 자신이 낸 답을 다시 보고 수정• None Toolformer: 적절한 시점에 도구 사용 결정어시스턴트 app에서 autonouos agnet와 agent memory의 관계를 설명해 보겠습니다.용자 발화가 입력되면 autonomous agent 의 역할을 하는 executor가 추론 모델 (llm core, planner, reflector)과 agentic memory에서 사용자 발화에 대한 답변을 수행합니다.아래 시퀀스 다이어그램에서는 뭉치에 대해 물어보는 과정에 llm과 memory의 프로세스를 설명합니다. 메모리에는 이 사용자의 반려견 이름이 뭉치라는 것이 저장되어 있습니다.사용자 발화를 중심으로 Autonomous agent가 llm을 통해 agentic memory를 이용하여 개인화된 정보를 가져오는 과정을 설명해 보겠습니다.“뭉치는 물놀이 좋아해?”에이전트는 이 발화를 Autonomous agent를 통해 LLM에 전달하고, 내부적으로 Planner 모듈이 호출됩니다.Planner는 뭉치와 물놀이 관계에 대해 Agentic Memory에서 조회하지만 관련된 항목이 없는 상태입니다.이 결과는 LLM을 통해 자연어로 변환되어 사용자에게 전달되며, 에이전트는 다음과 같이 답변합니다:“기억에 없어요. 알려주세요!”이후 사용자가 정보를 제공합니다:“뭉치는 수영장에서 놀기를 좋아해요.”해당 입력은 Autonomous agent를 통해 Reflection 모듈로 전달되며, Reflection는 이 정보를 구조화하고 요약하여 Agentic Memory에 저장합니다.이 저장 과정은 향후 동일하거나 유사한 질문에 대해 정확한 정보를 반환하기 위한 학습 과정입니다. 시간이 지난 후에 사용자가 이후 동일한 질문을 반복해봅니다:“뭉치가 좋아하는 놀이 뭐였지?”Autonomous agent는 이전과 동일하게 Planner를 통해 memory를 조회하고, 이번에는 저장된 내용을 기반으로 응답을 생성합니다.Agentic Memory는 관련 정보를 반환하고, LLM은 이를 자연어로 표현하여 다음과 같은 응답을 출력합니다:“뭉치는 수영장에서 놀기를 좋아해요.”이로써 뭉치가 수영장에서 노는 것을 좋아하는 것이 memory에 저장되고 다음번에 유사한 질문을 하더라도 어시스턴트는 이를 근거로 답변을 하게 됩니다.A-MEM(Agentic Memory for LLM Agents)은 Rutgers University, Ant Group, Salesforce Research의 공동 연구로 개발된 차세대 AI 메모리 시스템입니다.이 시스템은 대형 언어 모델(LLM) 기반 에이전트가 장기적인 상호작용과 복잡한 추론을 수행할 수 있도록 지원합니다.A-MEM은 Zettelkasten 방식에서 영감을 받아, 각 상호작용을 ‘노트’로 저장합니다.각 노트는 다음과 같은 구조화된 속성을 포함합니다 Zettelkasten(체텔카스텐)은 “노트 카드 상자”를 뜻하는 단어로, 지식을 축적하고 연결해가는 지식 관리 시스템 또는 생각 정리법입니다.새로운 노트가 추가되면, A-MEM은 기존 메모리와의 의미론적 유사성을 분석하여 관련된 노트들과 자동으로 연결(link)을 생성합니다.이 과정은 단순한 키워드 매칭을 넘어서, LLM이 생성한 컨텍스트를 기반으로 하여 더 깊은 수준의 의미론적 연결을 형성합니다.A-MEM 아키텍처 다이어그램에이전트가 새로운 상호작용을 경험하면, A-MEM은 이를 구조화된 메모리 노트로 변환합니다. 각 노트는 다음과 같은 속성을 포함합니다:• None 태그 (Tags): 주제나 범주를 나타내는 분류 정보• None 컨텍스트 (Context): LLM이 생성한 의미론적 요약• None 임베딩 (Embedding): 노트의 의미를 벡터로 표현한 값이러한 구조화된 노트는 에이전트가 정보를 체계적으로 저장하고, 필요할 때 효과적으로 검색할 수 있도록 합니다.새로운 노트가 추가되면, A-MEM은 기존 메모리와의 의미론적 유사성을 분석하여 관련된 노트들과 자동으로 연결(link)을 생성합니다.이 과정은 단순한 키워드 매칭을 넘어서, LLM이 생성한 컨텍스트를 기반으로 하여 더 깊은 수준의 의미론적 연결을 형성합니다.새로운 노트가 추가되면, A-MEM은 기존 메모리의 컨텍스트와 태그를 업데이트하여 메모리 네트워크가 지속적으로 진화할 수 있도록 합니다.이러한 메모리 진화는 에이전트가 과거 경험을 바탕으로 새로운 상황에 적응하고, 더 정교한 추론을 수행할 수 있게 합니다.에이전트가 새로운 질문이나 작업을 수행할 때, A-MEM은 현재의 상호작용과 관련된 과거의 경험을 효과적으로 검색하여 활용합니다.이를 통해 에이전트는 현재의 상호작용과 관련된 과거의 경험을 효과적으로 활용할 수 있습니다.A-MEM은 ChromaDB와 같은 벡터 데이터베이스를 활용하여, 의미론적 유사성에 기반한 빠르고 정확한 메모리 검색을 지원합니다.이를 통해 에이전트는 현재의 상호작용과 관련된 과거의 경험을 효과적으로 활용할 수 있습니다.Agentic Memory의 가장 큰
5/14/2025

기억하는 AI : Agentic Memory 그리고 A-MEN
자율 에이전트(Autonomous Agent)에 대해 살펴본게 엊그제 같은데 이게 현실화 되는걸 눈으로 보는게 신기한 시대입니다.이게 가능했던 이유는 바로 추론(reasoning) 모델이 등장했기 때문입니다. 추론 모델(Reasoning Model)과 Autonomous Agent는 뇌(두뇌)와 몸(행동 주체)의 관계처럼, 서로 유기적으로 연결되어 있습니다.Autonomous Agent는 memory, planning, action, tools로 구성됩니다. 여기에서 Agentic Memory는 Long-term memory에 속합니다.추론 모델은 Chain-of-Thought, Tree-of-Thought, ReAct, Reflection-Rewrite 등을 통해 복잡한 문제를 해결하기 위해 생각의 흐름을 구성하고 실행 전략을 만듭니다.이렇게 만들어진 모델을 이용하여 Autonomous Agent는 검색, API 호출, DB 조회, 문서 생성 등 다양한 도구나 기능을 활용하여 외부 목표를 받아 스스로 계획하고, 판단하고, 실행합니다.• None• None Reflection: 자신이 낸 답을 다시 보고 수정• None Toolformer: 적절한 시점에 도구 사용 결정어시스턴트 app에서 autonouos agnet와 agent memory의 관계를 설명해 보겠습니다.용자 발화가 입력되면 autonomous agent 의 역할을 하는 executor가 추론 모델 (llm core, planner, reflector)과 agentic memory에서 사용자 발화에 대한 답변을 수행합니다.아래 시퀀스 다이어그램에서는 뭉치에 대해 물어보는 과정에 llm과 memory의 프로세스를 설명합니다. 메모리에는 이 사용자의 반려견 이름이 뭉치라는 것이 저장되어 있습니다.사용자 발화를 중심으로 Autonomous agent가 llm을 통해 agentic memory를 이용하여 개인화된 정보를 가져오는 과정을 설명해 보겠습니다.“뭉치는 물놀이 좋아해?”에이전트는 이 발화를 Autonomous agent를 통해 LLM에 전달하고, 내부적으로 Planner 모듈이 호출됩니다.Planner는 뭉치와 물놀이 관계에 대해 Agentic Memory에서 조회하지만 관련된 항목이 없는 상태입니다.이 결과는 LLM을 통해 자연어로 변환되어 사용자에게 전달되며, 에이전트는 다음과 같이 답변합니다:“기억에 없어요. 알려주세요!”이후 사용자가 정보를 제공합니다:“뭉치는 수영장에서 놀기를 좋아해요.”해당 입력은 Autonomous agent를 통해 Reflection 모듈로 전달되며, Reflection는 이 정보를 구조화하고 요약하여 Agentic Memory에 저장합니다.이 저장 과정은 향후 동일하거나 유사한 질문에 대해 정확한 정보를 반환하기 위한 학습 과정입니다. 시간이 지난 후에 사용자가 이후 동일한 질문을 반복해봅니다:“뭉치가 좋아하는 놀이 뭐였지?”Autonomous agent는 이전과 동일하게 Planner를 통해 memory를 조회하고, 이번에는 저장된 내용을 기반으로 응답을 생성합니다.Agentic Memory는 관련 정보를 반환하고, LLM은 이를 자연어로 표현하여 다음과 같은 응답을 출력합니다:“뭉치는 수영장에서 놀기를 좋아해요.”이로써 뭉치가 수영장에서 노는 것을 좋아하는 것이 memory에 저장되고 다음번에 유사한 질문을 하더라도 어시스턴트는 이를 근거로 답변을 하게 됩니다.A-MEM(Agentic Memory for LLM Agents)은 Rutgers University, Ant Group, Salesforce Research의 공동 연구로 개발된 차세대 AI 메모리 시스템입니다.이 시스템은 대형 언어 모델(LLM) 기반 에이전트가 장기적인 상호작용과 복잡한 추론을 수행할 수 있도록 지원합니다.A-MEM은 Zettelkasten 방식에서 영감을 받아, 각 상호작용을 ‘노트’로 저장합니다.각 노트는 다음과 같은 구조화된 속성을 포함합니다 Zettelkasten(체텔카스텐)은 “노트 카드 상자”를 뜻하는 단어로, 지식을 축적하고 연결해가는 지식 관리 시스템 또는 생각 정리법입니다.새로운 노트가 추가되면, A-MEM은 기존 메모리와의 의미론적 유사성을 분석하여 관련된 노트들과 자동으로 연결(link)을 생성합니다.이 과정은 단순한 키워드 매칭을 넘어서, LLM이 생성한 컨텍스트를 기반으로 하여 더 깊은 수준의 의미론적 연결을 형성합니다.A-MEM 아키텍처 다이어그램에이전트가 새로운 상호작용을 경험하면, A-MEM은 이를 구조화된 메모리 노트로 변환합니다. 각 노트는 다음과 같은 속성을 포함합니다:• None 태그 (Tags): 주제나 범주를 나타내는 분류 정보• None 컨텍스트 (Context): LLM이 생성한 의미론적 요약• None 임베딩 (Embedding): 노트의 의미를 벡터로 표현한 값이러한 구조화된 노트는 에이전트가 정보를 체계적으로 저장하고, 필요할 때 효과적으로 검색할 수 있도록 합니다.새로운 노트가 추가되면, A-MEM은 기존 메모리와의 의미론적 유사성을 분석하여 관련된 노트들과 자동으로 연결(link)을 생성합니다.이 과정은 단순한 키워드 매칭을 넘어서, LLM이 생성한 컨텍스트를 기반으로 하여 더 깊은 수준의 의미론적 연결을 형성합니다.새로운 노트가 추가되면, A-MEM은 기존 메모리의 컨텍스트와 태그를 업데이트하여 메모리 네트워크가 지속적으로 진화할 수 있도록 합니다.이러한 메모리 진화는 에이전트가 과거 경험을 바탕으로 새로운 상황에 적응하고, 더 정교한 추론을 수행할 수 있게 합니다.에이전트가 새로운 질문이나 작업을 수행할 때, A-MEM은 현재의 상호작용과 관련된 과거의 경험을 효과적으로 검색하여 활용합니다.이를 통해 에이전트는 현재의 상호작용과 관련된 과거의 경험을 효과적으로 활용할 수 있습니다.A-MEM은 ChromaDB와 같은 벡터 데이터베이스를 활용하여, 의미론적 유사성에 기반한 빠르고 정확한 메모리 검색을 지원합니다.이를 통해 에이전트는 현재의 상호작용과 관련된 과거의 경험을 효과적으로 활용할 수 있습니다.Agentic Memory의 가장 큰
2025.05.14

좋아요

별로에요

Kotlin과 JPA의 한계 극복: 빌링 시스템 고도화 과정
저희는 Kotlin + Spring Boot 기반으로 개발한 빌링 시스템을 수년간 운영하고 있습니다.시스템 개발 착수 당시 기준으로 비교적 최신 버전인 Kotlin, Spring Boot, Spring Data JPA, 그리고 QueryDSL 조합으로 서비스의 주요 기능을 개발해왔습니다.그러나 시간이 지나면서 기술적 부채가 쌓이고 시스템의 사용량이 늘어나면서 성능에 대한 문제도 발생하기 시작하여 이를 해결하기 위한 고도화를 진행하게 되었습니다.이 글에서는 그 배경과 과정, 그리고 저희가 선택한 기술적 판단들 중에 일부를 공유하고자 합니다.처음에 시스템 개발은 아래와 같은 기술 스택으로 진행했습니다.이러한 조합은 이미 Java 환경에서 널리 사용되는 검증된 방식이었고, Kotlin 환경에서도 큰 문제 없이 사용 가능할 것이라고 생각했습니다.하지만, 개발하고 시스템을 운영하는 과정에서 다음과 같은 문제들이 발생했습니다.Kotlin 의 data class 와 JPA의 궁합 문제Kotlin 의 data class 는 불변성과 copy 메서드, 구조 분해 등의 강력한 기능을 제공합니다.그러나 JPA 는 불변성을 고려하여 설계된 기술이 아니기 때문에 data class 를 엔티티 클래스로 사용할 수 없었습니다.따라서 아래와 같이 설계하게 되었는데요,프로퍼티가 다른 코드에 의해 변경될 수 있고 의도치 않은 데이터 변경이 일어날 수 있어서, 엔티티와 비슷한 모습의 불변 DTO 클래스를 추가로 작성하여 사용해야 했습니다.이런 구조는 초기 개발 당시 큰 문제로 느껴지지는 않았지만 시간이 흐르면서 DTO 가 중구난방으로 추가되는 문제가 발생하기 시작했습니다.그래서 이렇게 사용하면 참 좋겠다는 생각을 많이 하게 되었습니다.데이터의 양이 많아지면서 쿼리 성능에 문제가 발생하기 시작했고, SQL 을 새로 작성하거나 튜닝해야 했습니다.하지만 QueryDSL 이 지원하는 기능에는 한계가 있어서 원하는대로 작업할 수 없거나 복잡하게 개발을 해야했습니다.SQL 하나를 간략하게 예를 들어보자면 아래와 같은 sub query 를 실행하기 위해서이런 클래스를 작성해야 했습니다.결국 Native Query 를 사용하게 되면서 QueryDSL 을 통해 얻을 수 있는 타입 안전성이나 DB 의존성 회피 등 JPA 의 장점을 일부 포기하는 타협을 하게 되었습니다.게다가 현재 QueryDSL은 더 이상 원작자에 의해 공식적으로 유지보수되지 않고 있으며,현재는 OpenFeign 에 의해 fork 되어 이어지고 있는 상태로 장기적인 측면에서 봤을 때 지속성에 의구심이 들었습니다.문제 해결은 이렇게 하기로...일단 최신 언어의 기능과 성능 개선점을 도입하기 위해 전반적인 기술 스택 업그레이드 및 변경을 진행했습니다.이를 통해 Spring 의 최신 기능들과 Kotlin 의 K2 컴파일러, Java 의 Virtual Thread 등을 사용할 수 있게 되었습니다.jOOQ 는 QueryDSL 과 동일하게 타입에 안전한 코드를 작성할 수 있고, Native SQL 과 100% 호환되어 복잡한 쿼리를 작성하는데 문제가 없어 보였습니다.그리고 Kotlin 과도 궁합이 좋아서 동적 쿼리 작성 도구로 선택되었습니다.jOOQ 도 QueryDSL 과 마찬가지로 메타모델 클래스를 생성해야 한다는 불편함이 있지만, 타입 안전성을 위해서는 피할 수 없는 부분이고annotation processor 기반이었던 QueryDSL 과는 달리 jOOQ 는 SQL DSL 코드 생성 기반이라 좀 더 안정적이고 직관적인 개발이 가능했습니다.DDL 스크립트를 바탕으로 메타모델 클래스 생성을 위한 build.gradle.kts 예시솔루션 적용은 이런 순서로...빌링 시스템의 특성 상 영속성 프레임워크의 전면 교체는 위험 부담이 크기 때문에 다음과 같은 순서로 고도화하는 전략을 택했습니다:• None 복잡한 조회 쿼리를 중심으로 작성된 QueryDSL 기반 코드를 jOOQ 로 우선 전환하고• None 성능 개선 여부와 유지보수 용이성을 일정 기간동안 검증했습니다.JPA 으로 구현한 CRUD 기능을 서서히 변경• None 변경이 적은 엔티티부터 JPA에서 JDBC 기반 구현으로 서서히 교체하면서• None 도메인 모델은 불변 객체(immutable object)로 새로 작성했습니다.• None 데이터를 서비스 기능의 요구에 맞게 제공하기 위해 aggregation 클래스를 도입하여• None jOOQ 나 JDBC 로 가져온 엔티티를 조합해 응답 객체를 구성하며, 불필요해진 DTO 클래스를 제거했습니다.• None JPA, QueryDSL 의 한계를 벗어나 순수 SQL 과 같은 수준에서 자유로운 쿼리를 작성 가능해졌습니다.• None 서브쿼리, 윈도우 함수, DBMS 특화 기능 등을 코드로 명확하게 구현 할 수 있었습니다.• None 코드 수정량은 많았지만... 필요 없는 Lazy 로딩이나 N+1 이슈 등, 원하지 않았던 join 발생 등을 줄일 수 있었습니다.• None 원하는 대로 SQL 을 작성할 수 있다보니 튜닝으로 응답 시간 개선도 가능했습니다.• None Kotlin 의 장점을 살려 불변 모델로 작성함으로써 도메인의 안정성이 향상되었다고 생각되고• None Aggregation 클래스를 통해 비즈니스 요구에 맞는 응답 구조를 명확히 분리했습니다.• None 그리고 불필요한 클래스 파일을 제거하면서 다이어트 효과도 얻을 수 있었습니다.빌드 및 운영 환경에서의 안정성 향상• None QueryDSL 사용으로 인한 annotation processor 이슈, 버전 충돌 이슈 등을 제거함으로써 전체적인 기술 스택 업그레이드가 가능했고 개발 생산성도 향상되었다고 생각합니다.JPA 는 오랜 시간 널리 사용되어온 강력한 기술이지만, Kotlin 을 본격적으로 도입하면서 그 한계를 깊이 체감하게 되었습니다."잘 동작하는 시스템은 건드리지 말라"는 업계의 속설을 한 번쯤은 접해보셨을 듯 한데요저희는 더 나은 개발 환경과 미래의 저희를 위해 손을 대버렸고, 결과적으로는 잘한 선택이라고 생각합니다.사실 Kotlin Exposed 으로 도전해볼껄 하는 아쉬움이 조금은 남지만, 너
java
kotlin
spring
5/14/2025

Kotlin과 JPA의 한계 극복: 빌링 시스템 고도화 과정
저희는 Kotlin + Spring Boot 기반으로 개발한 빌링 시스템을 수년간 운영하고 있습니다.시스템 개발 착수 당시 기준으로 비교적 최신 버전인 Kotlin, Spring Boot, Spring Data JPA, 그리고 QueryDSL 조합으로 서비스의 주요 기능을 개발해왔습니다.그러나 시간이 지나면서 기술적 부채가 쌓이고 시스템의 사용량이 늘어나면서 성능에 대한 문제도 발생하기 시작하여 이를 해결하기 위한 고도화를 진행하게 되었습니다.이 글에서는 그 배경과 과정, 그리고 저희가 선택한 기술적 판단들 중에 일부를 공유하고자 합니다.처음에 시스템 개발은 아래와 같은 기술 스택으로 진행했습니다.이러한 조합은 이미 Java 환경에서 널리 사용되는 검증된 방식이었고, Kotlin 환경에서도 큰 문제 없이 사용 가능할 것이라고 생각했습니다.하지만, 개발하고 시스템을 운영하는 과정에서 다음과 같은 문제들이 발생했습니다.Kotlin 의 data class 와 JPA의 궁합 문제Kotlin 의 data class 는 불변성과 copy 메서드, 구조 분해 등의 강력한 기능을 제공합니다.그러나 JPA 는 불변성을 고려하여 설계된 기술이 아니기 때문에 data class 를 엔티티 클래스로 사용할 수 없었습니다.따라서 아래와 같이 설계하게 되었는데요,프로퍼티가 다른 코드에 의해 변경될 수 있고 의도치 않은 데이터 변경이 일어날 수 있어서, 엔티티와 비슷한 모습의 불변 DTO 클래스를 추가로 작성하여 사용해야 했습니다.이런 구조는 초기 개발 당시 큰 문제로 느껴지지는 않았지만 시간이 흐르면서 DTO 가 중구난방으로 추가되는 문제가 발생하기 시작했습니다.그래서 이렇게 사용하면 참 좋겠다는 생각을 많이 하게 되었습니다.데이터의 양이 많아지면서 쿼리 성능에 문제가 발생하기 시작했고, SQL 을 새로 작성하거나 튜닝해야 했습니다.하지만 QueryDSL 이 지원하는 기능에는 한계가 있어서 원하는대로 작업할 수 없거나 복잡하게 개발을 해야했습니다.SQL 하나를 간략하게 예를 들어보자면 아래와 같은 sub query 를 실행하기 위해서이런 클래스를 작성해야 했습니다.결국 Native Query 를 사용하게 되면서 QueryDSL 을 통해 얻을 수 있는 타입 안전성이나 DB 의존성 회피 등 JPA 의 장점을 일부 포기하는 타협을 하게 되었습니다.게다가 현재 QueryDSL은 더 이상 원작자에 의해 공식적으로 유지보수되지 않고 있으며,현재는 OpenFeign 에 의해 fork 되어 이어지고 있는 상태로 장기적인 측면에서 봤을 때 지속성에 의구심이 들었습니다.문제 해결은 이렇게 하기로...일단 최신 언어의 기능과 성능 개선점을 도입하기 위해 전반적인 기술 스택 업그레이드 및 변경을 진행했습니다.이를 통해 Spring 의 최신 기능들과 Kotlin 의 K2 컴파일러, Java 의 Virtual Thread 등을 사용할 수 있게 되었습니다.jOOQ 는 QueryDSL 과 동일하게 타입에 안전한 코드를 작성할 수 있고, Native SQL 과 100% 호환되어 복잡한 쿼리를 작성하는데 문제가 없어 보였습니다.그리고 Kotlin 과도 궁합이 좋아서 동적 쿼리 작성 도구로 선택되었습니다.jOOQ 도 QueryDSL 과 마찬가지로 메타모델 클래스를 생성해야 한다는 불편함이 있지만, 타입 안전성을 위해서는 피할 수 없는 부분이고annotation processor 기반이었던 QueryDSL 과는 달리 jOOQ 는 SQL DSL 코드 생성 기반이라 좀 더 안정적이고 직관적인 개발이 가능했습니다.DDL 스크립트를 바탕으로 메타모델 클래스 생성을 위한 build.gradle.kts 예시솔루션 적용은 이런 순서로...빌링 시스템의 특성 상 영속성 프레임워크의 전면 교체는 위험 부담이 크기 때문에 다음과 같은 순서로 고도화하는 전략을 택했습니다:• None 복잡한 조회 쿼리를 중심으로 작성된 QueryDSL 기반 코드를 jOOQ 로 우선 전환하고• None 성능 개선 여부와 유지보수 용이성을 일정 기간동안 검증했습니다.JPA 으로 구현한 CRUD 기능을 서서히 변경• None 변경이 적은 엔티티부터 JPA에서 JDBC 기반 구현으로 서서히 교체하면서• None 도메인 모델은 불변 객체(immutable object)로 새로 작성했습니다.• None 데이터를 서비스 기능의 요구에 맞게 제공하기 위해 aggregation 클래스를 도입하여• None jOOQ 나 JDBC 로 가져온 엔티티를 조합해 응답 객체를 구성하며, 불필요해진 DTO 클래스를 제거했습니다.• None JPA, QueryDSL 의 한계를 벗어나 순수 SQL 과 같은 수준에서 자유로운 쿼리를 작성 가능해졌습니다.• None 서브쿼리, 윈도우 함수, DBMS 특화 기능 등을 코드로 명확하게 구현 할 수 있었습니다.• None 코드 수정량은 많았지만... 필요 없는 Lazy 로딩이나 N+1 이슈 등, 원하지 않았던 join 발생 등을 줄일 수 있었습니다.• None 원하는 대로 SQL 을 작성할 수 있다보니 튜닝으로 응답 시간 개선도 가능했습니다.• None Kotlin 의 장점을 살려 불변 모델로 작성함으로써 도메인의 안정성이 향상되었다고 생각되고• None Aggregation 클래스를 통해 비즈니스 요구에 맞는 응답 구조를 명확히 분리했습니다.• None 그리고 불필요한 클래스 파일을 제거하면서 다이어트 효과도 얻을 수 있었습니다.빌드 및 운영 환경에서의 안정성 향상• None QueryDSL 사용으로 인한 annotation processor 이슈, 버전 충돌 이슈 등을 제거함으로써 전체적인 기술 스택 업그레이드가 가능했고 개발 생산성도 향상되었다고 생각합니다.JPA 는 오랜 시간 널리 사용되어온 강력한 기술이지만, Kotlin 을 본격적으로 도입하면서 그 한계를 깊이 체감하게 되었습니다."잘 동작하는 시스템은 건드리지 말라"는 업계의 속설을 한 번쯤은 접해보셨을 듯 한데요저희는 더 나은 개발 환경과 미래의 저희를 위해 손을 대버렸고, 결과적으로는 잘한 선택이라고 생각합니다.사실 Kotlin Exposed 으로 도전해볼껄 하는 아쉬움이 조금은 남지만, 너
2025.05.14
java
kotlin
spring

좋아요

별로에요

코드 품질 개선 기법 11편: 반복되는 호출에 함수도 지친다
안녕하세요. 커뮤니케이션 앱 LINE의 모바일 클라이언트를 개발하고 있는 Ishikawa입니다.저희 회사는 높은 개발 생산성을 유지하기 위해 코드 품질 및 개발 문화 개선에 힘쓰고 있습니다. 이를 위해 다양한 노력을 하고 있는데요. 그중 하나가 Review Committee 활동입니다.Review Committee에서는 머지된 코드를 다시 리뷰해 리뷰어와 작성자에게 피드백을 주고, 리뷰하면서 얻은 지식과 인사이트를 Weekly Report라는 이름으로 매주 공유하고 있습니다. 이 Weekly Report 중 일반적으로 널리 적용할 수 있는 주제를 골라 블로그에 코드 품질 개선 기법 시리즈를 연재하고 있습니다.이번에 블로그로 공유할 Weekly Report의 제목은 '반복되는 호출에 함수도 지친다'입니다.반복되는 호출에 함수도 지친다어떤 서비스에서 사용자끼리 '친구'가 되는 기능을 구현하려고 합니다. 현재 사용자 입장에서 다른 사용자가 친구인지 확인하거나 새로 친구를 추가하는 기능을 구현하기 위해 다음과 같은 클래스를 사용한다고 가정해 봅시다.호출자 측 코드는 다음과 같습니다.위 코드에 문제가 있을까요?호출자 측 코드의 구조를 추출하면 가 됩니다. 즉, '현재 수신 객체의 상태에 따라 수신 객체를 변경'하는 코드가 되는데요. 이렇게 '현재 수신 객체의 상태에 따라 수신 객체를 변경'하는 경우 상태를 변경하는 함수 자체에서 확인하도록 만드는 것이 더 좋은 경우가 많습니다.위 코드에서 의 경우 이미 를 충족하는 것으로 가정하는 것 같습니다. 만약 사용자가 이미 친구인 상태에서 를 호출하면 예외가 발생하는 등 를 꼭 확인해야 하는 경우에 확인이 누락된다면 버그가 발생할 것입니다. 이를 해결하려면 확인을 내부에서 수행하는 것이 좋습니다.이미 친구 상태일 때 가 아무것도 하지 않는 것은 자연스러운 동작으로 보이는 경우가 많습니다. 하지만 '아무것도 하지 않는다'는 것을 특별히 강조해야 한다면 함수 이름을 으로 바꾸거나 주석으로 조건을 설명해야 합니다.이렇게 수신 객체에게 자신의 상태를 확인하도록 하면 굳이 보여줄 필요가 없는 상태를 숨기고 겉으로 드러나는 상태 전이를 단순화할 수 있습니다.다음 코드를 리팩터링하는 것을 생각해 봅시다.위 코드를 다음과 같이 리팩터링하는 것은 부적절합니다.의 리팩터링이 부적절한 이유는 콜백으로 인해 불필요한 의존성 순환이 발생하기 때문입니다. 또한 언뜻 보기에는 가 동기식 콜백인지 비동기식 콜백인지 알 수 없다는 점도 문제입니다.이런 경우 를 고차 함수로 만드는 것보다 반환값으로 결과를 반환하는 것이 좋습니다. 현재 예시의 경우에는 진위값( )으로 충분합니다.이렇게 하면 이 언제 호출되는지 명확하게 알 수 있습니다. 다만 이렇게 함수 이름에서 반환값을 언급하지 않는 경우에는 문서에서 반환값을 설명해야 합니다(자세한 내용은 Code readability: Session 3 문서를 참고하세요). 또한 필요하다면 호출자가 반환값을 확인하도록 강제하세요(코드 품질 개선 기법 2편: 확인 여부를 확인했나요?의 첫 번째 방법 참고).
5/14/2025

코드 품질 개선 기법 11편: 반복되는 호출에 함수도 지친다
안녕하세요. 커뮤니케이션 앱 LINE의 모바일 클라이언트를 개발하고 있는 Ishikawa입니다.저희 회사는 높은 개발 생산성을 유지하기 위해 코드 품질 및 개발 문화 개선에 힘쓰고 있습니다. 이를 위해 다양한 노력을 하고 있는데요. 그중 하나가 Review Committee 활동입니다.Review Committee에서는 머지된 코드를 다시 리뷰해 리뷰어와 작성자에게 피드백을 주고, 리뷰하면서 얻은 지식과 인사이트를 Weekly Report라는 이름으로 매주 공유하고 있습니다. 이 Weekly Report 중 일반적으로 널리 적용할 수 있는 주제를 골라 블로그에 코드 품질 개선 기법 시리즈를 연재하고 있습니다.이번에 블로그로 공유할 Weekly Report의 제목은 '반복되는 호출에 함수도 지친다'입니다.반복되는 호출에 함수도 지친다어떤 서비스에서 사용자끼리 '친구'가 되는 기능을 구현하려고 합니다. 현재 사용자 입장에서 다른 사용자가 친구인지 확인하거나 새로 친구를 추가하는 기능을 구현하기 위해 다음과 같은 클래스를 사용한다고 가정해 봅시다.호출자 측 코드는 다음과 같습니다.위 코드에 문제가 있을까요?호출자 측 코드의 구조를 추출하면 가 됩니다. 즉, '현재 수신 객체의 상태에 따라 수신 객체를 변경'하는 코드가 되는데요. 이렇게 '현재 수신 객체의 상태에 따라 수신 객체를 변경'하는 경우 상태를 변경하는 함수 자체에서 확인하도록 만드는 것이 더 좋은 경우가 많습니다.위 코드에서 의 경우 이미 를 충족하는 것으로 가정하는 것 같습니다. 만약 사용자가 이미 친구인 상태에서 를 호출하면 예외가 발생하는 등 를 꼭 확인해야 하는 경우에 확인이 누락된다면 버그가 발생할 것입니다. 이를 해결하려면 확인을 내부에서 수행하는 것이 좋습니다.이미 친구 상태일 때 가 아무것도 하지 않는 것은 자연스러운 동작으로 보이는 경우가 많습니다. 하지만 '아무것도 하지 않는다'는 것을 특별히 강조해야 한다면 함수 이름을 으로 바꾸거나 주석으로 조건을 설명해야 합니다.이렇게 수신 객체에게 자신의 상태를 확인하도록 하면 굳이 보여줄 필요가 없는 상태를 숨기고 겉으로 드러나는 상태 전이를 단순화할 수 있습니다.다음 코드를 리팩터링하는 것을 생각해 봅시다.위 코드를 다음과 같이 리팩터링하는 것은 부적절합니다.의 리팩터링이 부적절한 이유는 콜백으로 인해 불필요한 의존성 순환이 발생하기 때문입니다. 또한 언뜻 보기에는 가 동기식 콜백인지 비동기식 콜백인지 알 수 없다는 점도 문제입니다.이런 경우 를 고차 함수로 만드는 것보다 반환값으로 결과를 반환하는 것이 좋습니다. 현재 예시의 경우에는 진위값( )으로 충분합니다.이렇게 하면 이 언제 호출되는지 명확하게 알 수 있습니다. 다만 이렇게 함수 이름에서 반환값을 언급하지 않는 경우에는 문서에서 반환값을 설명해야 합니다(자세한 내용은 Code readability: Session 3 문서를 참고하세요). 또한 필요하다면 호출자가 반환값을 확인하도록 강제하세요(코드 품질 개선 기법 2편: 확인 여부를 확인했나요?의 첫 번째 방법 참고).
2025.05.14

좋아요

별로에요

MySQL ALTER DDL 수행 방식에 대한 이해
MySQL은 2000년대부터 웹서비스를 많이 사용하는 개발자들에 의해 많이 사용되었습니다. 오픈 소스로서 사용이 쉬웠을 뿐 아니라 개발자들이 다루기 쉽고 이해하기 쉬운 관계형 데이터베이스이기 때문에 빠르고 가볍게 개발할 수 있는 웹서비스에 최적의 데이터베이스였습니다.하지만, MySQL은 서비스가 잘되고 사용자가 늘어날수록 운영하기 쉽지 않았습니다. 여러 가지 문제가 있었지만, 그 중 하나가 서비스가 운영 중인 상황에서 Online DDL을 수행하기 어렵다는 점이었습니다.이 문제를 해결하기 위해, MySQL은 Ver 5.6부터 기존 COPY 알고리즘을 개선한 In-Place 알고리즘을 추가했습니다. In-Place 알고리즘을 통해 테이블 가용성을 높일 수 있었지만 사용이 편한 것은 아니었습니다. COPY 알고리즘 보다 개선되기는 했지만 DDL 작업의 시작과 종료 시점에 메타 락을 획득해야하고, 작업 종류에 따라서는 Copy 알고리즘과 마찬가지로 임시 테이블 생성 및 데이터 복제 과정도 필요하기 때문입니다. 이 때문에 MySQL Ver. 8.0 부터는 Instant 알고리즘을 추가해 테이블 사이즈와 상관없이 간단한 DDL 작업을 수행할 수 있도록 개선했습니다.MySQL은 현재 이렇게 세 가지 ALTER DDL 알고리즘을 제공합니다. 그래서, 이 문서에서는 MySQL의 ALTER DDL에 관한 주요 함수와 알고리즘, 알고리즘간 Meta Lock 동작을 비교해보며 내부 동작 과정을 자세히 살펴보고자 합니다. 이를 통해 MySQL을 사용하는 서비스 운영에 도움이 되기를 바랍니다.먼저, MySQL이 ALTER DDL을 크게 어떤 단계로 수행하는지 알아보겠습니다.MySQL은 DDL 수행 알고리즘과 무관하게 크게 3단계로 작업을 진행합니다. 단, 후술할 상세 로직 및 함수들은 In-Place/Instant 알고리즘 위주로 작성되어 있습니다.해당 ALTER DDL 문 실행에 사용할 알고리즘과 Lock 유형을 선택하는 단계입니다.작업 순서는 다음과 같습니다.• 사용자가 명시한 알고리즘으로 DDL문을 수행할 수 있는지 확인합니다. ⇒ 컬럼 이름의 중복이나 존재하지 않는 컬럼을 수정하려는 등 테이블 메타 데이터 조회로 바로 확인이 가능한 문제들을 체크합니다.• handler::check_if_supported_inplace_alter() 함수로 ALTER TABLE의 실행에 필요한 Lock 모드를 확인합니다. ⇒ 함수가 반환한 Lock 모드와 사용자가 지정한 Lock 모드를 비교합니다. 둘 사이에 충돌이 발생한다면 오류를 발생하고 작업을 중지합니다. ex) HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE 인데 LOCK=none 설정을 한 경우에 해당합니다.실제 DDL 작업을 수행하는 단계입니다. 앞단계에서 선택한 알고리즘과 Lock 모드를 실제 수행합니다.상세 작업 순서는 다음과 같습니다.• 앞서 선택한 Lock 모드에 필요한 Metadata Lock을 획득합니다. 이 단계에서, MDL_SHARED_UPGRADABLE Metadata Lock을 획득합니다.• In-Place 알고리즘을 선택했다면, Metadata Lock을 MDL_EXCLUSIVE로 승격하여 다른 세션에서 Metadata Lock을 획득하는 것을 차단합니다.• handler:ha_prepare_inplace_alter_table()을 호출해 제약사항을 체크하고, 인덱스와 외래키의 Metadata를 업데이트합니다. Instant 알고리즘의 경우 이 단계를 수행하지 않습니다.• In-Place 알고리즘을 선택했다면, 2번 단계에서 승격했던 Lock 모드를 다시 강등합니다.• DDL 수행 도중 DML이 차단되어야 한다면 Metadata Lock을 MDL_SHARED_NO_WRITE로 강등합니다.• 그렇지 않다면, MDL_SHARED_UPGRADABLE로 강등합니다.• ALTER TABLE에 의해 요청된 변경사항을 실행하기 위해 handler::ha_inplace_alter_table()을 호출합니다. Instant 알고리즘의 경우 이 단계를 수행하지 않습니다.• 작업을 수행하는 테이블에 대한 Metadata Lock을 MDL_EXCLUSIVE로 승격합니다. ⇒ 승격 후, handler::ha_commit_inplace_alter_table()을 호출하여 테이블의 Metadata를 변경합니다.ALTER 작업이 모두 마무리 된 후 정리하는 단계입니다. 지금까지의 변경 내역을 기반으로 Data Dictionary를 업데이트하고 커밋합니다. 스토리지 엔진의 atomic DDL지원 여부에 따라 다음과 같이 마무리 작업을 진행합니다.• Atomic DDL을 지원하는 스토리지 엔진의 경우: ⇒ Old_Table과 New_Table간의 테이블 이름을 변경하는 작업을 먼저 수행하고 Data Dictionary를 업데이트, 커밋합니다.• Atomic DDL을 지원하지 않는 스토리지 엔진의 경우: ⇒ Data Dictionary를 업데이트 하고, Old_Table과 New_Table의 테이블 이름을 변경하는 작업을 수행합니다.마지막으로 지금까지 사용한 객체와 메모리를 정리하고, MDL_SHARED_READ Lock을 획득해 변경된 테이블의 Metadata를 최종 확인하며 마무리합니다.ALTER DDL문이 수행될 때 즉, Execution 단계에서 사용되는 주요 함수는 다음과 같습니다.위 함수들에 대해 간단히 알아보도록 하겠습니다.ALTER DDL과 현재 테이블 명세를 비교합니다. 테이블의 제약사항을 체크하고, 인덱스나 외래키에 대한 Metadata를 업데이트하는 역할을 합니다.여기에서 확인하는 제약사항은 다음과 같습니다.이 단계는 모든 알고리즘에서 수행되는 단계는 아닙니다. Instant 알고리즘을 사용하겠다고 한 ALTER DDL문이라면 이 단계에서 아무 작업도 수행하지 않고 바로 빠져나옵니다.소스 코드 상에서 다음과 같이 확인이 가능합니다.이 함수안에서 실제 ALTER 작업이 수행됩니다. 다음과 같은 작업들이 이 함수 안에서 수행됩니다.Instant 알고리즘을 사용하거나, 또는 In-Place 알고리즘을 사용하지만
mysql
5/14/2025

MySQL ALTER DDL 수행 방식에 대한 이해
MySQL은 2000년대부터 웹서비스를 많이 사용하는 개발자들에 의해 많이 사용되었습니다. 오픈 소스로서 사용이 쉬웠을 뿐 아니라 개발자들이 다루기 쉽고 이해하기 쉬운 관계형 데이터베이스이기 때문에 빠르고 가볍게 개발할 수 있는 웹서비스에 최적의 데이터베이스였습니다.하지만, MySQL은 서비스가 잘되고 사용자가 늘어날수록 운영하기 쉽지 않았습니다. 여러 가지 문제가 있었지만, 그 중 하나가 서비스가 운영 중인 상황에서 Online DDL을 수행하기 어렵다는 점이었습니다.이 문제를 해결하기 위해, MySQL은 Ver 5.6부터 기존 COPY 알고리즘을 개선한 In-Place 알고리즘을 추가했습니다. In-Place 알고리즘을 통해 테이블 가용성을 높일 수 있었지만 사용이 편한 것은 아니었습니다. COPY 알고리즘 보다 개선되기는 했지만 DDL 작업의 시작과 종료 시점에 메타 락을 획득해야하고, 작업 종류에 따라서는 Copy 알고리즘과 마찬가지로 임시 테이블 생성 및 데이터 복제 과정도 필요하기 때문입니다. 이 때문에 MySQL Ver. 8.0 부터는 Instant 알고리즘을 추가해 테이블 사이즈와 상관없이 간단한 DDL 작업을 수행할 수 있도록 개선했습니다.MySQL은 현재 이렇게 세 가지 ALTER DDL 알고리즘을 제공합니다. 그래서, 이 문서에서는 MySQL의 ALTER DDL에 관한 주요 함수와 알고리즘, 알고리즘간 Meta Lock 동작을 비교해보며 내부 동작 과정을 자세히 살펴보고자 합니다. 이를 통해 MySQL을 사용하는 서비스 운영에 도움이 되기를 바랍니다.먼저, MySQL이 ALTER DDL을 크게 어떤 단계로 수행하는지 알아보겠습니다.MySQL은 DDL 수행 알고리즘과 무관하게 크게 3단계로 작업을 진행합니다. 단, 후술할 상세 로직 및 함수들은 In-Place/Instant 알고리즘 위주로 작성되어 있습니다.해당 ALTER DDL 문 실행에 사용할 알고리즘과 Lock 유형을 선택하는 단계입니다.작업 순서는 다음과 같습니다.• 사용자가 명시한 알고리즘으로 DDL문을 수행할 수 있는지 확인합니다. ⇒ 컬럼 이름의 중복이나 존재하지 않는 컬럼을 수정하려는 등 테이블 메타 데이터 조회로 바로 확인이 가능한 문제들을 체크합니다.• handler::check_if_supported_inplace_alter() 함수로 ALTER TABLE의 실행에 필요한 Lock 모드를 확인합니다. ⇒ 함수가 반환한 Lock 모드와 사용자가 지정한 Lock 모드를 비교합니다. 둘 사이에 충돌이 발생한다면 오류를 발생하고 작업을 중지합니다. ex) HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE 인데 LOCK=none 설정을 한 경우에 해당합니다.실제 DDL 작업을 수행하는 단계입니다. 앞단계에서 선택한 알고리즘과 Lock 모드를 실제 수행합니다.상세 작업 순서는 다음과 같습니다.• 앞서 선택한 Lock 모드에 필요한 Metadata Lock을 획득합니다. 이 단계에서, MDL_SHARED_UPGRADABLE Metadata Lock을 획득합니다.• In-Place 알고리즘을 선택했다면, Metadata Lock을 MDL_EXCLUSIVE로 승격하여 다른 세션에서 Metadata Lock을 획득하는 것을 차단합니다.• handler:ha_prepare_inplace_alter_table()을 호출해 제약사항을 체크하고, 인덱스와 외래키의 Metadata를 업데이트합니다. Instant 알고리즘의 경우 이 단계를 수행하지 않습니다.• In-Place 알고리즘을 선택했다면, 2번 단계에서 승격했던 Lock 모드를 다시 강등합니다.• DDL 수행 도중 DML이 차단되어야 한다면 Metadata Lock을 MDL_SHARED_NO_WRITE로 강등합니다.• 그렇지 않다면, MDL_SHARED_UPGRADABLE로 강등합니다.• ALTER TABLE에 의해 요청된 변경사항을 실행하기 위해 handler::ha_inplace_alter_table()을 호출합니다. Instant 알고리즘의 경우 이 단계를 수행하지 않습니다.• 작업을 수행하는 테이블에 대한 Metadata Lock을 MDL_EXCLUSIVE로 승격합니다. ⇒ 승격 후, handler::ha_commit_inplace_alter_table()을 호출하여 테이블의 Metadata를 변경합니다.ALTER 작업이 모두 마무리 된 후 정리하는 단계입니다. 지금까지의 변경 내역을 기반으로 Data Dictionary를 업데이트하고 커밋합니다. 스토리지 엔진의 atomic DDL지원 여부에 따라 다음과 같이 마무리 작업을 진행합니다.• Atomic DDL을 지원하는 스토리지 엔진의 경우: ⇒ Old_Table과 New_Table간의 테이블 이름을 변경하는 작업을 먼저 수행하고 Data Dictionary를 업데이트, 커밋합니다.• Atomic DDL을 지원하지 않는 스토리지 엔진의 경우: ⇒ Data Dictionary를 업데이트 하고, Old_Table과 New_Table의 테이블 이름을 변경하는 작업을 수행합니다.마지막으로 지금까지 사용한 객체와 메모리를 정리하고, MDL_SHARED_READ Lock을 획득해 변경된 테이블의 Metadata를 최종 확인하며 마무리합니다.ALTER DDL문이 수행될 때 즉, Execution 단계에서 사용되는 주요 함수는 다음과 같습니다.위 함수들에 대해 간단히 알아보도록 하겠습니다.ALTER DDL과 현재 테이블 명세를 비교합니다. 테이블의 제약사항을 체크하고, 인덱스나 외래키에 대한 Metadata를 업데이트하는 역할을 합니다.여기에서 확인하는 제약사항은 다음과 같습니다.이 단계는 모든 알고리즘에서 수행되는 단계는 아닙니다. Instant 알고리즘을 사용하겠다고 한 ALTER DDL문이라면 이 단계에서 아무 작업도 수행하지 않고 바로 빠져나옵니다.소스 코드 상에서 다음과 같이 확인이 가능합니다.이 함수안에서 실제 ALTER 작업이 수행됩니다. 다음과 같은 작업들이 이 함수 안에서 수행됩니다.Instant 알고리즘을 사용하거나, 또는 In-Place 알고리즘을 사용하지만
2025.05.14
mysql

좋아요

별로에요

왓챠파티@무비랜드: 콘텐츠에 진심
〈오피스〉와 〈러브레터〉에 담은 진심, 왓챠팀의 덕업일치 프로젝트 Part 1매달 셋째 주 수요일은 왓챠파티@무비랜드!2025년 2월 19일 수요일, 열한 번째 테마를 마지막으로 왓챠파티@무비랜드의 여정이 막을 내렸습니다.2024년 초, 왓챠는 성수동에 오픈을 앞둔 무비랜드에서 1년간 오프라인 왓챠파티를 진행하기로 결심합니다. 무비랜드는 30석 규모의 소극장으로, 매달 다른 큐레이터가 선정한 영화를 상영하며 이야기를 이어가는 공간이고, 왓챠파티는 왓챠 구독자라면 누구나 참여할 수 있는 다중 동시 감상 서비스로, 영화를 감상하며 실시간 채팅으로 이야기를 나눌 수 있는 기능인데요. 각자의 방법으로 ‘이야기’에 주목하는 무비랜드와 왓챠파티가 만나면 뭔가 흥미로운 일이 일어나지 않을까? 왓챠는 그렇게 첫 장기 오프라인 프로젝트를 통해 사용자와 조금 더 가까이, 더 길게 호흡할 수 있는 기회를 만들기로 합니다.이 회고는 지난 1년 동안 왓챠파티@무비랜드를 기획하고 준비하며 경험한, 현장 사진으로는 남기기 어려웠던 비하인드 스토리를 중심으로 구성되었습니다. 왓챠 무비랜드TF 구성원의 시점에서 전하는 이 이야기의 첫 편은, 콘텐츠를 향한 진심이 고스란히 담긴 테마들을 소개하는 것으로 시작합니다. 깨물면 안 아픈 손가락은 없지만, 열한 개의 테마 중 특히 애정이 깊었던 〈오피스〉와 〈러브레터〉를 중심으로 전해드립니다.-직장인이라면 5월의 시작은 살짝 더 즐겁다. 근로자의 날, 근로자의 노고를 위로하고 근무의욕을 높이기 위한 제정된 휴일로 시작되기 때문. 역시 회사는 안 가는 편이 더 좋지 않나? 그런데, 여기 출근을 더 좋아할 것 같은 사람들이 있다. 이런 직장이 있다고? 이런 동료가 존재한다고? 오피스 속 던더 미플린 스크랜턴점은 얼핏 회사보다 놀이터에 가깝다. — 〈오피스〉 테마 소개 중5월, 노동절! 오피스화창했던 5월의 셋째 주 수요일, 상영작은 〈The Office〉였습니다. 오프라인 왓챠파티 기획 초기부터 만장일치로 정해졌던 ‘확신의 테마’로 마치 정규음반의 타이틀곡처럼, 세 번째 트랙으로 준비하게 되었습니다. 콘텐츠를 향한 우리의 진심을 보여줄 차례라며, 유난히 의욕에 넘쳤던 순간들이 떠오릅니다.흥분한 덕후들의 잘 해보자 슬랙 캡쳐“상영되게 하라” — 시리즈 에피소드 엮기시리즈물의 극장 상영을 현실로 만들기 위해서는 고민이 필요했습니다. 2005년부터 2013년까지, 무려 8년의 세월에 걸쳐 방영된 아홉 개의 시즌, 총 186개의 에피소드로 이뤄진 미드 〈오피스〉를 어떻게 상영할 것인가?1회차 상영에는 몇 편을 엮을까? 어떤 에피소드를 선정해야 할까? 어떤 주제로 묶어야 팬들이 공감하는 파티를 할 수 있을까? 한 달 간격으로 새로운 테마의 상영 이벤트를 준비하는 건 매번 어렵고 시간에 쫓기는 일이었습니다. 특히나 극장 상영을 한 적 없는 오피스의 경우, 그 이상의 촘촘한 계획과 노력 그리고 확인 절차가 필요했어요.팀 내 〈오피스〉 덕후를 자처한 3명 — 알리아, 마일즈, 제인 — 을 중축으로 3시간에 가까운 첫 회의를 가졌습니다. 이야기를
5/13/2025

왓챠파티@무비랜드: 콘텐츠에 진심
〈오피스〉와 〈러브레터〉에 담은 진심, 왓챠팀의 덕업일치 프로젝트 Part 1매달 셋째 주 수요일은 왓챠파티@무비랜드!2025년 2월 19일 수요일, 열한 번째 테마를 마지막으로 왓챠파티@무비랜드의 여정이 막을 내렸습니다.2024년 초, 왓챠는 성수동에 오픈을 앞둔 무비랜드에서 1년간 오프라인 왓챠파티를 진행하기로 결심합니다. 무비랜드는 30석 규모의 소극장으로, 매달 다른 큐레이터가 선정한 영화를 상영하며 이야기를 이어가는 공간이고, 왓챠파티는 왓챠 구독자라면 누구나 참여할 수 있는 다중 동시 감상 서비스로, 영화를 감상하며 실시간 채팅으로 이야기를 나눌 수 있는 기능인데요. 각자의 방법으로 ‘이야기’에 주목하는 무비랜드와 왓챠파티가 만나면 뭔가 흥미로운 일이 일어나지 않을까? 왓챠는 그렇게 첫 장기 오프라인 프로젝트를 통해 사용자와 조금 더 가까이, 더 길게 호흡할 수 있는 기회를 만들기로 합니다.이 회고는 지난 1년 동안 왓챠파티@무비랜드를 기획하고 준비하며 경험한, 현장 사진으로는 남기기 어려웠던 비하인드 스토리를 중심으로 구성되었습니다. 왓챠 무비랜드TF 구성원의 시점에서 전하는 이 이야기의 첫 편은, 콘텐츠를 향한 진심이 고스란히 담긴 테마들을 소개하는 것으로 시작합니다. 깨물면 안 아픈 손가락은 없지만, 열한 개의 테마 중 특히 애정이 깊었던 〈오피스〉와 〈러브레터〉를 중심으로 전해드립니다.-직장인이라면 5월의 시작은 살짝 더 즐겁다. 근로자의 날, 근로자의 노고를 위로하고 근무의욕을 높이기 위한 제정된 휴일로 시작되기 때문. 역시 회사는 안 가는 편이 더 좋지 않나? 그런데, 여기 출근을 더 좋아할 것 같은 사람들이 있다. 이런 직장이 있다고? 이런 동료가 존재한다고? 오피스 속 던더 미플린 스크랜턴점은 얼핏 회사보다 놀이터에 가깝다. — 〈오피스〉 테마 소개 중5월, 노동절! 오피스화창했던 5월의 셋째 주 수요일, 상영작은 〈The Office〉였습니다. 오프라인 왓챠파티 기획 초기부터 만장일치로 정해졌던 ‘확신의 테마’로 마치 정규음반의 타이틀곡처럼, 세 번째 트랙으로 준비하게 되었습니다. 콘텐츠를 향한 우리의 진심을 보여줄 차례라며, 유난히 의욕에 넘쳤던 순간들이 떠오릅니다.흥분한 덕후들의 잘 해보자 슬랙 캡쳐“상영되게 하라” — 시리즈 에피소드 엮기시리즈물의 극장 상영을 현실로 만들기 위해서는 고민이 필요했습니다. 2005년부터 2013년까지, 무려 8년의 세월에 걸쳐 방영된 아홉 개의 시즌, 총 186개의 에피소드로 이뤄진 미드 〈오피스〉를 어떻게 상영할 것인가?1회차 상영에는 몇 편을 엮을까? 어떤 에피소드를 선정해야 할까? 어떤 주제로 묶어야 팬들이 공감하는 파티를 할 수 있을까? 한 달 간격으로 새로운 테마의 상영 이벤트를 준비하는 건 매번 어렵고 시간에 쫓기는 일이었습니다. 특히나 극장 상영을 한 적 없는 오피스의 경우, 그 이상의 촘촘한 계획과 노력 그리고 확인 절차가 필요했어요.팀 내 〈오피스〉 덕후를 자처한 3명 — 알리아, 마일즈, 제인 — 을 중축으로 3시간에 가까운 첫 회의를 가졌습니다. 이야기를
2025.05.13

좋아요

별로에요

테스트를 부탁해 - Firebase App Testing Agent 체험기
Firebase가 최근 UI 테스트 자동화를 위한 신규 기능 App Testing Agent를 공개했습니다.에이닷 클라이언트 개발팀에서도 UI 테스트를 활발히 진행하고 있던 터라 자연스럽게 눈길이 끌렸습니다.궁금증을 못 참고 바로 몇 가지 시나리오를 실행해 보며 기능을 체험했고 동시에 백그라운드에서 무슨 일이 벌어지는지도 슬쩍 들여다봤습니다.이 글에서는 그 과정에서 얻은 실제 테스트 결과와 내부 로직을 추측해 본 이야기를 가볍게 풀어 보려 합니다.Firebase App Testing Agent는 Gemini in Firebase가 구동하는 AI 기반 UI 테스트 자동화 에이전트입니다.“로그인 플로우를 검증해 줘”처럼 자연어로 목표만 입력하면 에이전트가 앱 화면을 해석하고 버튼, 제스처 등 사용자 행동을 시뮬레이션해 테스트 시나리오를 생성하고 실행합니다.완료 후에는 각 단계의 스크린샷과 로그가 포함된 리포트를 제공해 문제 지점을 한눈에 파악할 수 있도록 돕습니다.두 가지의 테스트 실행 방식을 제공하며 이 글에서는 'AI 가이드 테스트'에 초점을 맞춰 보려고 합니다.• None 에이전트는 자연어로 작성한 테스트 목표를 해석해 앱의 UI 트리를 탐색하고, 실제 사용자 동작을 모사해 시나리오를 자동 실행합니다.• None 테스트는 단계별 시퀀스로 구성되며, 각 단계마다 목표(Goal), 힌트(Hint), 성공 판정 기준(Success Criteria)을 가이드할 수 있습니다.• None 이 글을 작성하는 2025년 4월 25일 현재, 테스트 실행에는 Gemini-2.0-Flash-001 모델이 사용되고 있습니다.현재 에이닷 클라이언트 팀에서 진행하고 있는 것과 동일한 수준의 테스트 시나리오를 작성하고 실행해보면서 App Testing Agent의 가능성과 한계에 대해서 알아보겠습니다.로그인 후 대화방에 진입했을 때 임의의 새 메시지가 정상적으로 노출되는지를 확인하는 간단한 테스트 케이스를 작성하고,이를 실행한 뒤 결과를 확인해보겠습니다.Firebase Console의 App Distribution 메뉴에서 ‘테스트 사례’ 탭으로 이동하면, 테스트를 생성하거나 실행할 수 있는 화면에 진입하게 됩니다.화면에 진입한 뒤 '새 테스트 사례' 버튼을 클릭하면 새로운 테스트 항목을 직접 등록할 수 있습니다.테스트 제목을 지정할 수 있으며 목표가 하나인 경우 비워두면 목표가 테스트 제목으로 사용됩니다.에이전트로 수행할 작업은 가급적 한 문장 이내로 간단히 작성하는 것이 좋습니다. 복잡한 동작일 경우 단계를 나누어 작성하면 오작동 가능성을 줄일 수 있습니다.Gemini가 앱을 이해하고 탐색하는 데 도움이 되는 추가 정보를 제공합니다.예를 들면, '온보딩 화면을 지나 이동합니다. 팝업을 모두 닫습니다. 로그인하지 않습니다.'와 같은 정보입니다.테스트 목표를 달성하는 데 필요한 예상 동작 또는 앱 상태를 설명합니다.예를 들면, '기본 앱 홈 페이지가 화면에 표시되고 모든 이미지가 로드되었으며 오류가 표시되지 않습니다.'와 같은 내용입니다.인트로 화면에서 '바로 시작하기' 버튼을 클릭해서 로그인 선택 화면으로 진입할 수 있도록 목표를 설정했습니다.T ID로 로그인 화면으로 진입하도록 목표를 설정했습니다. Step2에서는 목표를 통해 버튼을 누르도록 지시하지 않고 의도한 행동을 할 수 있도록 힌트를 제공했습니다.아이디랑 비밀번호 입력 후, 로그인 버튼을 누르도록 했습니다.버튼의 일부분만 보이는 경우 클릭을 하지 않는 경우가 있어서 버튼 전체가 보일 때까지 스크롤을 하도록 힌트를 제공했습니다.대화방 진입 후 임의의 메시지가 출력되는지 확인하도록 목표를 설정 했습니다.테스트 사례 화면에서 '테스트 실행' 버튼을 누르면, 테스트 실행에 필요한 설정을 진행할 수 있는 화면으로 전환됩니다.테스트 실행 시 제공되는 두 가지 유형인 'AI 가이드 테스트'와 '무작위 크롤링 테스트' 중 하나를 선택해 테스트를 수행할 수 있습니다.테스트 실행 시 사용할 기기를 선택할 수 있으며, 약 80개의 실제 기기와 에뮬레이터가 준비되어 있어 다양한 환경에서 테스트가 가능합니다.로그인이 필요한 앱이라면 테스트 실행 시 사용할 아이디와 비밀번호를 사전에 입력해둘 수 있습니다.테스트를 실행한 뒤에는 App Distribution의 해당 릴리스 버전에서 ‘앱 테스트 에이전트’ 탭을 통해 테스트 진행 상황과 결과를 실시간으로 확인할 수 있습니다.App Testing Agent는 목표대로 인트로 화면에서 ‘바로 시작하기’ 버튼을 클릭한 후 로딩 완료를 위해 10초 동안 대기했습니다.App Testing Agent는 로그인 선택 화면에서 ‘T ID로 로그인하기’ 버튼을 클릭해 T ID 로그인 화면으로 이동했으며, 해당 화면 진입 여부를 기준으로 테스트 결과를 Pass로 판단했습니다.App Testing Agent는 테스트 시작 시 설정한 아이디와 비밀번호를 입력해 로그인을 진행했습니다.이후, 에이닷 타이틀이 표시된 메인 화면에 정상적으로 진입한 것을 확인하고 이를 성공 기준으로 판단하여 테스트 결과를 Pass로 처리했습니다.다소 인상적이었던 점은 사전에 존재 여부를 명시하지 않았던 권한 팝업이 표시되었음에도 불구하고App Testing Agent가 이를 자동으로 인식하고 확인 버튼을 눌러 정상적으로 목표를 달성했다는 점입니다. 🤩일반적인 UI 테스트에서는 예상되지 않은 팝업이나 UI 변화로 인해 다음 단계로 진행하지 못하고 테스트가 실패하는 경우가 많습니다.임의의 메시지가 출력될 때까지 10초간 대기하도록 가이드했기 때문에 App Testing Agent는 해당 시간 동안 대기합니다.테스트 중 예상치 못한 돌발 상황으로 위치 서비스 관련 시스템 팝업이 노출되었지만 App Testing Agent는 ‘아니오’ 버튼을 스스로 인식하고 클릭하여 팝업을 정상적으로 닫았습니다.테스트 목표였던 임의의 메시지 출력을 감지하고 그 텍스트 내용을 정확히 확인한 후 Pass로 처리하는 과정을 보면, App Testing Agent의 UI 분석 능력이 상당히 정교하다는 점을 알 수 있습니다.‘에이전트 뷰 표시’를 켜면 테스트 중 에이전트가 접근성 정보를
5/13/2025

테스트를 부탁해 - Firebase App Testing Agent 체험기
Firebase가 최근 UI 테스트 자동화를 위한 신규 기능 App Testing Agent를 공개했습니다.에이닷 클라이언트 개발팀에서도 UI 테스트를 활발히 진행하고 있던 터라 자연스럽게 눈길이 끌렸습니다.궁금증을 못 참고 바로 몇 가지 시나리오를 실행해 보며 기능을 체험했고 동시에 백그라운드에서 무슨 일이 벌어지는지도 슬쩍 들여다봤습니다.이 글에서는 그 과정에서 얻은 실제 테스트 결과와 내부 로직을 추측해 본 이야기를 가볍게 풀어 보려 합니다.Firebase App Testing Agent는 Gemini in Firebase가 구동하는 AI 기반 UI 테스트 자동화 에이전트입니다.“로그인 플로우를 검증해 줘”처럼 자연어로 목표만 입력하면 에이전트가 앱 화면을 해석하고 버튼, 제스처 등 사용자 행동을 시뮬레이션해 테스트 시나리오를 생성하고 실행합니다.완료 후에는 각 단계의 스크린샷과 로그가 포함된 리포트를 제공해 문제 지점을 한눈에 파악할 수 있도록 돕습니다.두 가지의 테스트 실행 방식을 제공하며 이 글에서는 'AI 가이드 테스트'에 초점을 맞춰 보려고 합니다.• None 에이전트는 자연어로 작성한 테스트 목표를 해석해 앱의 UI 트리를 탐색하고, 실제 사용자 동작을 모사해 시나리오를 자동 실행합니다.• None 테스트는 단계별 시퀀스로 구성되며, 각 단계마다 목표(Goal), 힌트(Hint), 성공 판정 기준(Success Criteria)을 가이드할 수 있습니다.• None 이 글을 작성하는 2025년 4월 25일 현재, 테스트 실행에는 Gemini-2.0-Flash-001 모델이 사용되고 있습니다.현재 에이닷 클라이언트 팀에서 진행하고 있는 것과 동일한 수준의 테스트 시나리오를 작성하고 실행해보면서 App Testing Agent의 가능성과 한계에 대해서 알아보겠습니다.로그인 후 대화방에 진입했을 때 임의의 새 메시지가 정상적으로 노출되는지를 확인하는 간단한 테스트 케이스를 작성하고,이를 실행한 뒤 결과를 확인해보겠습니다.Firebase Console의 App Distribution 메뉴에서 ‘테스트 사례’ 탭으로 이동하면, 테스트를 생성하거나 실행할 수 있는 화면에 진입하게 됩니다.화면에 진입한 뒤 '새 테스트 사례' 버튼을 클릭하면 새로운 테스트 항목을 직접 등록할 수 있습니다.테스트 제목을 지정할 수 있으며 목표가 하나인 경우 비워두면 목표가 테스트 제목으로 사용됩니다.에이전트로 수행할 작업은 가급적 한 문장 이내로 간단히 작성하는 것이 좋습니다. 복잡한 동작일 경우 단계를 나누어 작성하면 오작동 가능성을 줄일 수 있습니다.Gemini가 앱을 이해하고 탐색하는 데 도움이 되는 추가 정보를 제공합니다.예를 들면, '온보딩 화면을 지나 이동합니다. 팝업을 모두 닫습니다. 로그인하지 않습니다.'와 같은 정보입니다.테스트 목표를 달성하는 데 필요한 예상 동작 또는 앱 상태를 설명합니다.예를 들면, '기본 앱 홈 페이지가 화면에 표시되고 모든 이미지가 로드되었으며 오류가 표시되지 않습니다.'와 같은 내용입니다.인트로 화면에서 '바로 시작하기' 버튼을 클릭해서 로그인 선택 화면으로 진입할 수 있도록 목표를 설정했습니다.T ID로 로그인 화면으로 진입하도록 목표를 설정했습니다. Step2에서는 목표를 통해 버튼을 누르도록 지시하지 않고 의도한 행동을 할 수 있도록 힌트를 제공했습니다.아이디랑 비밀번호 입력 후, 로그인 버튼을 누르도록 했습니다.버튼의 일부분만 보이는 경우 클릭을 하지 않는 경우가 있어서 버튼 전체가 보일 때까지 스크롤을 하도록 힌트를 제공했습니다.대화방 진입 후 임의의 메시지가 출력되는지 확인하도록 목표를 설정 했습니다.테스트 사례 화면에서 '테스트 실행' 버튼을 누르면, 테스트 실행에 필요한 설정을 진행할 수 있는 화면으로 전환됩니다.테스트 실행 시 제공되는 두 가지 유형인 'AI 가이드 테스트'와 '무작위 크롤링 테스트' 중 하나를 선택해 테스트를 수행할 수 있습니다.테스트 실행 시 사용할 기기를 선택할 수 있으며, 약 80개의 실제 기기와 에뮬레이터가 준비되어 있어 다양한 환경에서 테스트가 가능합니다.로그인이 필요한 앱이라면 테스트 실행 시 사용할 아이디와 비밀번호를 사전에 입력해둘 수 있습니다.테스트를 실행한 뒤에는 App Distribution의 해당 릴리스 버전에서 ‘앱 테스트 에이전트’ 탭을 통해 테스트 진행 상황과 결과를 실시간으로 확인할 수 있습니다.App Testing Agent는 목표대로 인트로 화면에서 ‘바로 시작하기’ 버튼을 클릭한 후 로딩 완료를 위해 10초 동안 대기했습니다.App Testing Agent는 로그인 선택 화면에서 ‘T ID로 로그인하기’ 버튼을 클릭해 T ID 로그인 화면으로 이동했으며, 해당 화면 진입 여부를 기준으로 테스트 결과를 Pass로 판단했습니다.App Testing Agent는 테스트 시작 시 설정한 아이디와 비밀번호를 입력해 로그인을 진행했습니다.이후, 에이닷 타이틀이 표시된 메인 화면에 정상적으로 진입한 것을 확인하고 이를 성공 기준으로 판단하여 테스트 결과를 Pass로 처리했습니다.다소 인상적이었던 점은 사전에 존재 여부를 명시하지 않았던 권한 팝업이 표시되었음에도 불구하고App Testing Agent가 이를 자동으로 인식하고 확인 버튼을 눌러 정상적으로 목표를 달성했다는 점입니다. 🤩일반적인 UI 테스트에서는 예상되지 않은 팝업이나 UI 변화로 인해 다음 단계로 진행하지 못하고 테스트가 실패하는 경우가 많습니다.임의의 메시지가 출력될 때까지 10초간 대기하도록 가이드했기 때문에 App Testing Agent는 해당 시간 동안 대기합니다.테스트 중 예상치 못한 돌발 상황으로 위치 서비스 관련 시스템 팝업이 노출되었지만 App Testing Agent는 ‘아니오’ 버튼을 스스로 인식하고 클릭하여 팝업을 정상적으로 닫았습니다.테스트 목표였던 임의의 메시지 출력을 감지하고 그 텍스트 내용을 정확히 확인한 후 Pass로 처리하는 과정을 보면, App Testing Agent의 UI 분석 능력이 상당히 정교하다는 점을 알 수 있습니다.‘에이전트 뷰 표시’를 켜면 테스트 중 에이전트가 접근성 정보를
2025.05.13

좋아요

별로에요

Jetpack Compose로 접근성 최적화하기
접근성 - 선택이 아닌 의무스마트폰을 눈을 감고 사용해본 적이 있으십니까? 혹은 한 손만으로 모든 기능을 사용해보셨습니까? 우리가 당연하게 여기는 앱 사용 경험이 누군가에게는 매일의 도전일 수 있습니다.한국에는 약 260만 명의 장애인과 빠르게 증가하는 고령 사용자들이 있습니다. 이들도 우리와 동일하게 음식을 주문하고, 택시를 부르고, 친구와 대화하기 위해 앱을 사용합니다.그러나 많은 앱들이 이들을 위한 기본적인 접근성조차 갖추지 못하고 있는 실정입니다.「장애인차별금지 및 권리구제 등에 관한 법률」에 따라 모바일 앱 개발에서 접근성은 선택이 아닌 필수입니다. 하지만 오늘 이야기하려는 것은 복잡한 법적 기준이나 깊은 기술적 내용이 아닙니다.모든 사용자가 앱을 편리하게 사용할 수 있도록 하는 것은 개발자의 책임이자 기회이기도 합니다.이 글에서는 개발자나 디자이너가 쉽게 놓칠 수 있는 접근성 요소들을 살펴보고, 간단한 변화만으로도 어떻게 더 많은 사용자에게 더 나은 경험을 제공할 수 있는지 알아보려 합니다.그럼 어렵지 않은 몇 가지 고려사항들이 어떻게 앱의 사용성을 향상시킬 수 있는지, 함께 알아보도록 하겠습니다.Jetpack Compose에서는 수정자가 접근성 구현의 핵심입니다. 이 수정자를 통해 UI 요소의 접근성 속성을 세밀하게 제어할 수 있습니다.복잡한 UI 컴포넌트에서는 여러 요소가 하나의 논리적 단위로 작동해야 하는 경우가 많습니다.mergeDescendants 속성을 사용하면 불필요한 포커스 이동을 줄여 사용자 경험을 개선할 수 있습니다:복잡한 컴포넌트의 경우, 각 하위 요소마다 TalkBack이 포커스를 이동하면 사용자 경험이 저하될 수 있습니다.mergeDescendants=true를 설정하면 여러 요소를 하나의 접근성 노드로 병합하여 사용자 경험을 크게 개선할 수 있습니다.제스쳐에 의해 동작되는 액션은 시각적 사용자에게는 직관적이지만, 스크린 리더 사용자에게는 어려울 수 있습니다.아래 예제는 스와이프로 삭제를 하는 동작에 대한 대체 접근성 액션을 제공하는 방법을 보여줍니다:스와이프 제스처와 같은 복잡한 인터랙션은 장애가 있는 사용자가 사용하기 어려울 수 있습니다.위와 같이 Custom Action을 제공함으로써 TalkBack 사용자도 해당 기능을 쉽게 이용할 수 있게 됩니다.화면에 표시되는 텍스트와 스크린 리더가 읽어야 할 텍스트가 다른 경우가 있습니다.특히 통화, 기호, 약어 등에서 이런 차이가 발생합니다:이처럼 화면에는 기호로 표시하더라도, 스크린 리더 사용자에게는 더 명확하게 읽히도록 할 수 있습니다.요소 유형 정의와 특수 의미론:UI 요소의 역할과 상태를 명확히 정의하면 스크린 리더 사용자가 컨텍스트를 더 잘 이해할 수 있습니다:이렇게 요소의 역할(Role)과 상태를 명시적으로 정의하면 스크린 리더가 더 정확한 정보를 사용자에게 전달할 수 있습니다.때로는 UI 상태가 변경될 때 접근성 포커스를 프로그래밍 방식으로 제어해야 할 필요가 있습니다.특히 다이얼로그가 표시되거나 동적 콘텐츠가 로드될 때 유용합니다:다이얼로그와 같은 중요한 UI 요소가 나타날 때 자동으로 포커스를 이동시키면 스크린 리더 사용자의 방향성을 크게 개선할 수 있습니다.동적으로 변하는 콘텐츠는 시각적 사용자에게는 명확하지만, 스크린 리더 사용자에게는 인지하기 어려울 수 있습니다.이런 변화를 명시적으로 알리는 것이 중요합니다:TalkBack은 안드로이드의 스크린 리더로, 사용자의 접근성 경험을 결정짓는 핵심 요소입니다.응답성과 사용성을 향상시키기 위한 추가적인 최적화 Tip 들을 알아보겠습니다.접근성 포커스를 효과적으로 관리하는 것은 TalkBack 사용자 경험의 핵심입니다:시각장애인에게는 논리적으로 예상되는 곳으로 편하게 포커스가 이동되는 것이 무엇보다 중요한 부분입니다.장식적 요소는 접근성 포커스에서 제외하고, 중요한 컨트롤에 포커스를 집중시켜 사용자 경험을 효율적으로 만들 수 있습니다.traversalIndex 속성을 사용하면 레이아웃과 상관없이 논리적인 탐색 순서를 정의할 수 있습니다.이는 레이아웃 순서와 접근성 순서가 다를 때 특히 유용합니다.스크린 리더가 정확하고 의미 있는 정보를 전달하도록 발화 품질을 최적화하는 것이 중요합니다:약어나 특수 기호는 스크린 리더가 제대로 발음하지 못할 수 있습니다. contentDescription이나 text 속성을 사용해 올바른 발음을 유도할 수 있습니다.또한 liveRegion 속성을 통해 동적 콘텐츠에 대한 발화 우선순위를 설정할 수 있습니다.데이터가 비동기적으로 로드될 때 접근성 이벤트 발생을 동기화하는 것이 중요할때도 있습니다.비동기 로딩 상황에서는 데이터가 준비된 후 사용자에게 명시적으로 알려주는 것이 중요합니다.이를 통해 스크린 리더 사용자도 콘텐츠 변화를 놓치지 않을 수 있습니다.접근성은 더 이상 선택이 아닌 필수 요소입니다.뛰어난 접근성 구현은 장애가 있는 사용자뿐만 아니라, 모든 사용자의 경험을 향상시킵니다.명확한 포커스 관리, 의미 있는 콘텐츠 설명, 논리적인 탐색 순서 등은 보편적 사용성의 원칙과도 일치합니다.이 글에서는 Jetpack Compose에서 접근성을 구현하는 방법부터, TalkBack 최적화까지 기술적 접근성에 대해 다루었습니다.접근성은 단순한 기능 추가를 넘어, 깊이 있는 엔지니어링이 요구되는 영역입니다.성능과 접근성 사이의 균형을 맞추는 작업은 지속적인 도전이지만, 올바른 기법과 전략적 접근을 통해 모든 사용자에게 최적의 경험을 제공할 수 있습니다.접근성은 특별한 기능이 아니라, 좋은 앱 설계의 기본입니다.개발 과정 전반에 걸쳐 접근성을 고려하고, 이 문서에서 소개한 기법들을 적극적으로 활용한다면 Jetpack Compose 애플리케이션의 접근성을 체계적으로 높일 수 있을 것입니다.
5/13/2025

Jetpack Compose로 접근성 최적화하기
접근성 - 선택이 아닌 의무스마트폰을 눈을 감고 사용해본 적이 있으십니까? 혹은 한 손만으로 모든 기능을 사용해보셨습니까? 우리가 당연하게 여기는 앱 사용 경험이 누군가에게는 매일의 도전일 수 있습니다.한국에는 약 260만 명의 장애인과 빠르게 증가하는 고령 사용자들이 있습니다. 이들도 우리와 동일하게 음식을 주문하고, 택시를 부르고, 친구와 대화하기 위해 앱을 사용합니다.그러나 많은 앱들이 이들을 위한 기본적인 접근성조차 갖추지 못하고 있는 실정입니다.「장애인차별금지 및 권리구제 등에 관한 법률」에 따라 모바일 앱 개발에서 접근성은 선택이 아닌 필수입니다. 하지만 오늘 이야기하려는 것은 복잡한 법적 기준이나 깊은 기술적 내용이 아닙니다.모든 사용자가 앱을 편리하게 사용할 수 있도록 하는 것은 개발자의 책임이자 기회이기도 합니다.이 글에서는 개발자나 디자이너가 쉽게 놓칠 수 있는 접근성 요소들을 살펴보고, 간단한 변화만으로도 어떻게 더 많은 사용자에게 더 나은 경험을 제공할 수 있는지 알아보려 합니다.그럼 어렵지 않은 몇 가지 고려사항들이 어떻게 앱의 사용성을 향상시킬 수 있는지, 함께 알아보도록 하겠습니다.Jetpack Compose에서는 수정자가 접근성 구현의 핵심입니다. 이 수정자를 통해 UI 요소의 접근성 속성을 세밀하게 제어할 수 있습니다.복잡한 UI 컴포넌트에서는 여러 요소가 하나의 논리적 단위로 작동해야 하는 경우가 많습니다.mergeDescendants 속성을 사용하면 불필요한 포커스 이동을 줄여 사용자 경험을 개선할 수 있습니다:복잡한 컴포넌트의 경우, 각 하위 요소마다 TalkBack이 포커스를 이동하면 사용자 경험이 저하될 수 있습니다.mergeDescendants=true를 설정하면 여러 요소를 하나의 접근성 노드로 병합하여 사용자 경험을 크게 개선할 수 있습니다.제스쳐에 의해 동작되는 액션은 시각적 사용자에게는 직관적이지만, 스크린 리더 사용자에게는 어려울 수 있습니다.아래 예제는 스와이프로 삭제를 하는 동작에 대한 대체 접근성 액션을 제공하는 방법을 보여줍니다:스와이프 제스처와 같은 복잡한 인터랙션은 장애가 있는 사용자가 사용하기 어려울 수 있습니다.위와 같이 Custom Action을 제공함으로써 TalkBack 사용자도 해당 기능을 쉽게 이용할 수 있게 됩니다.화면에 표시되는 텍스트와 스크린 리더가 읽어야 할 텍스트가 다른 경우가 있습니다.특히 통화, 기호, 약어 등에서 이런 차이가 발생합니다:이처럼 화면에는 기호로 표시하더라도, 스크린 리더 사용자에게는 더 명확하게 읽히도록 할 수 있습니다.요소 유형 정의와 특수 의미론:UI 요소의 역할과 상태를 명확히 정의하면 스크린 리더 사용자가 컨텍스트를 더 잘 이해할 수 있습니다:이렇게 요소의 역할(Role)과 상태를 명시적으로 정의하면 스크린 리더가 더 정확한 정보를 사용자에게 전달할 수 있습니다.때로는 UI 상태가 변경될 때 접근성 포커스를 프로그래밍 방식으로 제어해야 할 필요가 있습니다.특히 다이얼로그가 표시되거나 동적 콘텐츠가 로드될 때 유용합니다:다이얼로그와 같은 중요한 UI 요소가 나타날 때 자동으로 포커스를 이동시키면 스크린 리더 사용자의 방향성을 크게 개선할 수 있습니다.동적으로 변하는 콘텐츠는 시각적 사용자에게는 명확하지만, 스크린 리더 사용자에게는 인지하기 어려울 수 있습니다.이런 변화를 명시적으로 알리는 것이 중요합니다:TalkBack은 안드로이드의 스크린 리더로, 사용자의 접근성 경험을 결정짓는 핵심 요소입니다.응답성과 사용성을 향상시키기 위한 추가적인 최적화 Tip 들을 알아보겠습니다.접근성 포커스를 효과적으로 관리하는 것은 TalkBack 사용자 경험의 핵심입니다:시각장애인에게는 논리적으로 예상되는 곳으로 편하게 포커스가 이동되는 것이 무엇보다 중요한 부분입니다.장식적 요소는 접근성 포커스에서 제외하고, 중요한 컨트롤에 포커스를 집중시켜 사용자 경험을 효율적으로 만들 수 있습니다.traversalIndex 속성을 사용하면 레이아웃과 상관없이 논리적인 탐색 순서를 정의할 수 있습니다.이는 레이아웃 순서와 접근성 순서가 다를 때 특히 유용합니다.스크린 리더가 정확하고 의미 있는 정보를 전달하도록 발화 품질을 최적화하는 것이 중요합니다:약어나 특수 기호는 스크린 리더가 제대로 발음하지 못할 수 있습니다. contentDescription이나 text 속성을 사용해 올바른 발음을 유도할 수 있습니다.또한 liveRegion 속성을 통해 동적 콘텐츠에 대한 발화 우선순위를 설정할 수 있습니다.데이터가 비동기적으로 로드될 때 접근성 이벤트 발생을 동기화하는 것이 중요할때도 있습니다.비동기 로딩 상황에서는 데이터가 준비된 후 사용자에게 명시적으로 알려주는 것이 중요합니다.이를 통해 스크린 리더 사용자도 콘텐츠 변화를 놓치지 않을 수 있습니다.접근성은 더 이상 선택이 아닌 필수 요소입니다.뛰어난 접근성 구현은 장애가 있는 사용자뿐만 아니라, 모든 사용자의 경험을 향상시킵니다.명확한 포커스 관리, 의미 있는 콘텐츠 설명, 논리적인 탐색 순서 등은 보편적 사용성의 원칙과도 일치합니다.이 글에서는 Jetpack Compose에서 접근성을 구현하는 방법부터, TalkBack 최적화까지 기술적 접근성에 대해 다루었습니다.접근성은 단순한 기능 추가를 넘어, 깊이 있는 엔지니어링이 요구되는 영역입니다.성능과 접근성 사이의 균형을 맞추는 작업은 지속적인 도전이지만, 올바른 기법과 전략적 접근을 통해 모든 사용자에게 최적의 경험을 제공할 수 있습니다.접근성은 특별한 기능이 아니라, 좋은 앱 설계의 기본입니다.개발 과정 전반에 걸쳐 접근성을 고려하고, 이 문서에서 소개한 기법들을 적극적으로 활용한다면 Jetpack Compose 애플리케이션의 접근성을 체계적으로 높일 수 있을 것입니다.
2025.05.13

좋아요

별로에요

AI 시대, 디자이너를 없앴더니 생긴 일
AI 시대에 디자이너는 어떤 모습일까요?한번 쯤 이런 생각 해보지 않으셨나요? UI가 더 이상 필요 없어지거나, 로봇이 모든 일을 대신하는 날이 오면, 디자이너는 어떤 역할을 하게 될까?저도 같은 고민을 하다가 반대로 생각해 보기로 했어요. 언젠가 사라질 운명이라면, 차라리 그 미래를 앞당겨서 직접 경험해보기로요. “미래를 예측하는 가장 좋은 방법은 미래를 만드는 것이다”라는 말도 있잖아요.제가 팀에서 빠지더라도 아무 문제 없이 돌아가는 시스템을 만들어보는 실험이었어요.하나, 반복되는 작업을 '규칙'으로 정의하기저는 제휴를 맺은 회사들과 돈을 주고받는 정산 업무를 맡고 있는 팀에 있었어요. 문제는, 회사마다 수수료 계산 방식이 모두 달라서 매번 비슷한 일을 반복해야 했다는 거예요. 팀 인원이 아무리 많아도 일손이 부족한 구조였어요.그래서 생각했죠. "매번 다른 방법으로 하지 말고, 공통된 규칙을 찾아보면 어떨까?"예를 들어, 어떤 제휴사는 사용자가 토스를 통해 대출을 많이 받으면 더 많은 수수료를 지급하고, 덜 이용하면 적은 수수료를 주는 구조였어요. 이런 유사한 계약들을 묶어 ‘구간별 수수료율’이라는 하나의 규칙으로 정리했더니, 여러 회사의 정산을 모두 같은 방식으로 처리할 수 있게 됐어요.이런 식으로 복잡한 계약들을 표 하나로 정리하면서, 매번 새로 디자인하지 않아도 되는 구조 가 만들어졌어요. 이 시스템이 없었다면 지금도 저는 비슷한 화면 수백 장을 계속 만들고 있었을지도 몰라요.규칙을 만든지 반년쯤 지난 어느 날, 신기한 광경을 목격했어요. 프론트엔드 개발자가 저 없이도 UI를 만들고 있는 거예요. 디자인에 관심이 있는 개발자이기도 했지만, 디자이너 없이 돌아가는 시스템을 만들었다는 작은 성공의 신호였던 것 같아요. 물론 제가 디자인하는 시간도 매우 줄어들었고요.제가 생각한 다음 단계는 스펙을 작성하는 것조차 없애는 것이었어요. 보통 디자인하기 전에 제품의 요구조건 문서를 만들잖아요. 이것도 시스템화 할 수 있을 것 같았어요. 정산 서비스를 요청하는 임직원이 몇 가지 설문에만 답하면 그 정보를 토대로 UI가 자동 생성되도록 매핑 시스템을 만들었죠. 완전히 자동화되진 않았지만, 어시스턴트 디자이너 혼자 작업할 수 있을 만큼은 되더라고요.이쯤 되니 진짜로 ‘디자이너를 없앤다’는 실험이 현실이 되어가고 있다는 느낌이 들었어요.누구나 디자인할 수 시스템을 갖추고 나니, ‘이 시스템을 돌리는 주체가 꼭 사람이어야 할까?’ 라는 생각이 들었어요. 지금은 AI가 MCP(Model Context Protocol - AI와 제품이 소통할 수 있는 표준화된 소통 체계)를 통해 디자인 툴을 다루는 시대잖아요. 게다가 토스에서는 직접 디자인툴을 만들어 사용하기 때문에 어떠한 제약도 없이 다양한 시도를 해볼 수 있어요.아직은 바로 사용할 만한 수준의 화면을 만들지는 못하지만, 괜찮은 시스템이 정의되어 있다면 AI는 꽤 준수한 결과물을 만들어내더라고요. 그래서 앞으로 사람은 모두 시스템을 만들고, AI가 그 시스템을 활용해 제품을 만들게 되는 날이 올 것
5/13/2025

AI 시대, 디자이너를 없앴더니 생긴 일
AI 시대에 디자이너는 어떤 모습일까요?한번 쯤 이런 생각 해보지 않으셨나요? UI가 더 이상 필요 없어지거나, 로봇이 모든 일을 대신하는 날이 오면, 디자이너는 어떤 역할을 하게 될까?저도 같은 고민을 하다가 반대로 생각해 보기로 했어요. 언젠가 사라질 운명이라면, 차라리 그 미래를 앞당겨서 직접 경험해보기로요. “미래를 예측하는 가장 좋은 방법은 미래를 만드는 것이다”라는 말도 있잖아요.제가 팀에서 빠지더라도 아무 문제 없이 돌아가는 시스템을 만들어보는 실험이었어요.하나, 반복되는 작업을 '규칙'으로 정의하기저는 제휴를 맺은 회사들과 돈을 주고받는 정산 업무를 맡고 있는 팀에 있었어요. 문제는, 회사마다 수수료 계산 방식이 모두 달라서 매번 비슷한 일을 반복해야 했다는 거예요. 팀 인원이 아무리 많아도 일손이 부족한 구조였어요.그래서 생각했죠. "매번 다른 방법으로 하지 말고, 공통된 규칙을 찾아보면 어떨까?"예를 들어, 어떤 제휴사는 사용자가 토스를 통해 대출을 많이 받으면 더 많은 수수료를 지급하고, 덜 이용하면 적은 수수료를 주는 구조였어요. 이런 유사한 계약들을 묶어 ‘구간별 수수료율’이라는 하나의 규칙으로 정리했더니, 여러 회사의 정산을 모두 같은 방식으로 처리할 수 있게 됐어요.이런 식으로 복잡한 계약들을 표 하나로 정리하면서, 매번 새로 디자인하지 않아도 되는 구조 가 만들어졌어요. 이 시스템이 없었다면 지금도 저는 비슷한 화면 수백 장을 계속 만들고 있었을지도 몰라요.규칙을 만든지 반년쯤 지난 어느 날, 신기한 광경을 목격했어요. 프론트엔드 개발자가 저 없이도 UI를 만들고 있는 거예요. 디자인에 관심이 있는 개발자이기도 했지만, 디자이너 없이 돌아가는 시스템을 만들었다는 작은 성공의 신호였던 것 같아요. 물론 제가 디자인하는 시간도 매우 줄어들었고요.제가 생각한 다음 단계는 스펙을 작성하는 것조차 없애는 것이었어요. 보통 디자인하기 전에 제품의 요구조건 문서를 만들잖아요. 이것도 시스템화 할 수 있을 것 같았어요. 정산 서비스를 요청하는 임직원이 몇 가지 설문에만 답하면 그 정보를 토대로 UI가 자동 생성되도록 매핑 시스템을 만들었죠. 완전히 자동화되진 않았지만, 어시스턴트 디자이너 혼자 작업할 수 있을 만큼은 되더라고요.이쯤 되니 진짜로 ‘디자이너를 없앤다’는 실험이 현실이 되어가고 있다는 느낌이 들었어요.누구나 디자인할 수 시스템을 갖추고 나니, ‘이 시스템을 돌리는 주체가 꼭 사람이어야 할까?’ 라는 생각이 들었어요. 지금은 AI가 MCP(Model Context Protocol - AI와 제품이 소통할 수 있는 표준화된 소통 체계)를 통해 디자인 툴을 다루는 시대잖아요. 게다가 토스에서는 직접 디자인툴을 만들어 사용하기 때문에 어떠한 제약도 없이 다양한 시도를 해볼 수 있어요.아직은 바로 사용할 만한 수준의 화면을 만들지는 못하지만, 괜찮은 시스템이 정의되어 있다면 AI는 꽤 준수한 결과물을 만들어내더라고요. 그래서 앞으로 사람은 모두 시스템을 만들고, AI가 그 시스템을 활용해 제품을 만들게 되는 날이 올 것
2025.05.13

좋아요

별로에요

우리의 애플리케이션에서 PreparedStatement는 어떻게 동작하고 있는가
wade.hong DB를 다룰 때 기반 기술이 되는 PreparedStatement을 내부 구현 탐구를 통해서 어떻게 동작하는지 잘 표현한 글입니다.jaden.jacky 속 보이는 분석을 통해, 우리가 무심코 사용하는 추상화된 기술들 간의 관계와 내부 동작을 이해하는데 도움이 되는 좋은 글입니다.안녕하세요. 카카오페이에서 서버 개발을 하고 있는 cdragon입니다. 저는 주로 Java, Kotlin 같은 JVM 기반 언어를 사용하고, 데이터베이스 연동에는 ORM인 Hibernate를 이용하고 있습니다. 최근 MySQL 성능 관련 스터디를 진행했는데, MySQL에서 제공하고 있는 PREPARE에 대해 다시 한번 살펴보는 계기가 됐습니다.PREPARE는 MySQL에서 prepared statement를 생성하는 커맨드인데요. 프레임워크 없이 순수 JDBC 구현체를 이용해서 데이터베이스 연동 코드를 작성해 본 경험이 있다면 PreparedStatement 인터페이스를 통해 많이들 접해보셨을 것 같습니다. 저 역시 데이터베이스의 기능으로 보다는 JDBC의 Statement, PreparedStatement로 먼저 접했습니다.위에서 언급한 대로 저희는 애플리케이션에서 데이터베이스 연동 시 Hibernate를 사용하고 있는데요. 애플리케이션에서 데이터베이스까지 가는 길에는 Hibernate뿐만 아니라 Connection Pool, JDBC를 거쳐야 합니다. MySQL에 대한 스터디를 진행하다 보니 문득 우리가 사용하는 추상화된 기술들 안에서 PreparedStatement가 어떻게 동작하고 있을지 궁금해졌습니다.예측한 대로 MySQL에 PREPARE를 실행하고 있는지도 궁금했고, 그에 대한 설정들은 어디서, 어떻게 제어하고 있는지도 궁금해졌습니다. 그리고 추상화 계층을 한 꺼풀씩 벗겨가며 확인해 보기로 했습니다. 이번 글에서는 그 과정과 결과를 공유하고자 합니다.이 글에서는 아래와 같은 내용을 다룹니다.• JDBC Statement와 PreparedStatement 인터페이스의 개념과 차이점을 다룹니다.• MySQL PREPARE의 동작방식을 살펴봅니다.• Hibernate, HikariCP, MySQL Connector/J에서 PreparedStatement 관련 설정은 어떻게 이루어지며, 내부 구현은 어떻게 되어있는지 분석합니다.• 마지막으로 PreparedStatement 관련 설정에 따른 성능 테스트와 함께 고려해야 할 사항들을 공유합니다.다만 이 글에서 다음 내용은 다루지 않습니다.이 글을 통해 다양한 추상화 계층에서 JDBC를 어떻게 활용하는지 이해하고, 효과적으로 활용하는데 도움이 되기를 바랍니다.이 글에서 다룰 코드 예제, 테스트 기술들의 상세 스펙은 아래와 같습니다.테스트로 활용할 간단한 테이블의 스키마는 아래와 같습니다.JDBC Driver를 직접 사용해서 DB에 쿼리를 실행하는 고전적인 코드를 살펴보겠습니다.DB Connection을 획득하고, createStatement() 메서드를 호출해 Statement 객체를 얻어 쿼리를
hibernate
java
mysql
5/13/2025

우리의 애플리케이션에서 PreparedStatement는 어떻게 동작하고 있는가
wade.hong DB를 다룰 때 기반 기술이 되는 PreparedStatement을 내부 구현 탐구를 통해서 어떻게 동작하는지 잘 표현한 글입니다.jaden.jacky 속 보이는 분석을 통해, 우리가 무심코 사용하는 추상화된 기술들 간의 관계와 내부 동작을 이해하는데 도움이 되는 좋은 글입니다.안녕하세요. 카카오페이에서 서버 개발을 하고 있는 cdragon입니다. 저는 주로 Java, Kotlin 같은 JVM 기반 언어를 사용하고, 데이터베이스 연동에는 ORM인 Hibernate를 이용하고 있습니다. 최근 MySQL 성능 관련 스터디를 진행했는데, MySQL에서 제공하고 있는 PREPARE에 대해 다시 한번 살펴보는 계기가 됐습니다.PREPARE는 MySQL에서 prepared statement를 생성하는 커맨드인데요. 프레임워크 없이 순수 JDBC 구현체를 이용해서 데이터베이스 연동 코드를 작성해 본 경험이 있다면 PreparedStatement 인터페이스를 통해 많이들 접해보셨을 것 같습니다. 저 역시 데이터베이스의 기능으로 보다는 JDBC의 Statement, PreparedStatement로 먼저 접했습니다.위에서 언급한 대로 저희는 애플리케이션에서 데이터베이스 연동 시 Hibernate를 사용하고 있는데요. 애플리케이션에서 데이터베이스까지 가는 길에는 Hibernate뿐만 아니라 Connection Pool, JDBC를 거쳐야 합니다. MySQL에 대한 스터디를 진행하다 보니 문득 우리가 사용하는 추상화된 기술들 안에서 PreparedStatement가 어떻게 동작하고 있을지 궁금해졌습니다.예측한 대로 MySQL에 PREPARE를 실행하고 있는지도 궁금했고, 그에 대한 설정들은 어디서, 어떻게 제어하고 있는지도 궁금해졌습니다. 그리고 추상화 계층을 한 꺼풀씩 벗겨가며 확인해 보기로 했습니다. 이번 글에서는 그 과정과 결과를 공유하고자 합니다.이 글에서는 아래와 같은 내용을 다룹니다.• JDBC Statement와 PreparedStatement 인터페이스의 개념과 차이점을 다룹니다.• MySQL PREPARE의 동작방식을 살펴봅니다.• Hibernate, HikariCP, MySQL Connector/J에서 PreparedStatement 관련 설정은 어떻게 이루어지며, 내부 구현은 어떻게 되어있는지 분석합니다.• 마지막으로 PreparedStatement 관련 설정에 따른 성능 테스트와 함께 고려해야 할 사항들을 공유합니다.다만 이 글에서 다음 내용은 다루지 않습니다.이 글을 통해 다양한 추상화 계층에서 JDBC를 어떻게 활용하는지 이해하고, 효과적으로 활용하는데 도움이 되기를 바랍니다.이 글에서 다룰 코드 예제, 테스트 기술들의 상세 스펙은 아래와 같습니다.테스트로 활용할 간단한 테이블의 스키마는 아래와 같습니다.JDBC Driver를 직접 사용해서 DB에 쿼리를 실행하는 고전적인 코드를 살펴보겠습니다.DB Connection을 획득하고, createStatement() 메서드를 호출해 Statement 객체를 얻어 쿼리를
2025.05.13
hibernate
java
mysql

좋아요

별로에요

실시간 OLAP을 위한 Apache Pinot 운영 노하우
coco.nut Apache Pinot 클러스터 구성부터 상용 서비스까지 어디에도 쉽게 찾을 수 없는 엔지니어의 실전 노하우를 담았습니다.yol.yoli 카카오페이가 직접 부딪히며 얻은 Apache Pinot 운영 경험을 생생한 이야기로 들려드립니다.안녕하세요. 하둡플랫폼파트에서 하둡플랫폼을 운영하고 있는 sunny라고 합니다. 저희 파트에서는 다양한 목적에 맞춰 여러 클러스터를 구축하고 운영하며, 전사 사용자들에게 빅데이터 플랫폼을 제공하고 있습니다.이 글의 주제인 Apache Pinot는 대용량 데이터 스트림을 실시간으로 수집하고 분석할 수 있는 분산형 OLAP 데이터 저장소예요. 실시간 데이터뿐만 아니라 배치 데이터도 처리하고, 이 둘을 결합하여 분석할 수도 있습니다. Druid나 ClickHouse와 자주 비교되기도 합니다.실시간 업데이트(Upsert) 기능을 활용하여 지속적으로 변경되는 데이터를 즉시 반영하는 용도로 Pinot를 도입했습니다.Pinot 도입 초기에는 참고할만한 운영 사례가 부족하고 현재 버전에 비해 안정성이 다소 미흡해서 고생을 많이 했었는데요. 이 글에서는 오픈소스인 Apache Pinot를 도입하고 운영하면서 얻은 경험과 노하우를 공유하고자 합니다. Pinot 운영이 복잡할 수 있지만, 이 글에서 운영 노하우를 숙지하시면 실시간 데이터 분석을 활용하시는 데 도움이 될 거라 생각합니다. (본 내용은 Pinot 1.2.0 버전 기준으로 작성하였습니다.)이 글은 이런 분들에게 추천해요.• 실시간 OLAP에 관심 많은 엔지니어• Pinot 도입을 고려 중이거나 운영 중인 엔지니어이 글을 통해 다음과 같은 것들을 얻어가실 수 있어요.• 운영 경험에서 얻은 이슈 해결 케이스• 서비스 안정성을 높이는 클러스터 구성 Tip• 실시간 업데이트 테이블 성능을 높이는 방법이 글은 Pinot를 운영하면서 얻은 유용한 노하우를 전달하는 게 목적이므로 Pinot 개념은 간단하게 짚고 넘어가겠습니다.Pinot는 링크드인(LinkedIn)에서 공개한 실시간 OLAP 오픈소스 솔루션입니다. 링크드인 외에도 Uber, Stripe 등 다양한 기업에서도 Pinot를 활용하여 실시간 대시보드, 실시간 분석 애플리케이션 등 다양한 서비스를 제공하고 있습니다.• Stripe: 실시간 결제 대시보드, 판매자를 위한 실시간/배치 데이터 집계데이터 제공Pinot의 핵심적인 특징은 대용량 데이터를 빠르게 쿼리할 수 있도록 설계된 분산 시스템이라는 점입니다. 데이터를 세그먼트(Segment)로 분할하여 여러 서버에 분산 저장하고, 병렬 처리 방식으로 빠른 쿼리가 가능합니다. Pinot는 실시간 스트리밍 데이터와 배치 데이터를 수집하여 즉시 쿼리할 수 있습니다.Pinot 클러스터는 다음과 같은 주요 컴포넌트로 구성됩니다.• 브로커(Broker): 클라이언트로부터 쿼리 요청을 받아 서버로 라우팅하고, 여러 서버에서 받은 결과를 취합하여 최종 결과를 클라이언트에 전달함.• 서버(Server): 세그먼트 데이터를 저장하고 쿼리 요청을 처리함.• 컨트롤러(Contr
hadoop
kafka
trino
5/13/2025

실시간 OLAP을 위한 Apache Pinot 운영 노하우
coco.nut Apache Pinot 클러스터 구성부터 상용 서비스까지 어디에도 쉽게 찾을 수 없는 엔지니어의 실전 노하우를 담았습니다.yol.yoli 카카오페이가 직접 부딪히며 얻은 Apache Pinot 운영 경험을 생생한 이야기로 들려드립니다.안녕하세요. 하둡플랫폼파트에서 하둡플랫폼을 운영하고 있는 sunny라고 합니다. 저희 파트에서는 다양한 목적에 맞춰 여러 클러스터를 구축하고 운영하며, 전사 사용자들에게 빅데이터 플랫폼을 제공하고 있습니다.이 글의 주제인 Apache Pinot는 대용량 데이터 스트림을 실시간으로 수집하고 분석할 수 있는 분산형 OLAP 데이터 저장소예요. 실시간 데이터뿐만 아니라 배치 데이터도 처리하고, 이 둘을 결합하여 분석할 수도 있습니다. Druid나 ClickHouse와 자주 비교되기도 합니다.실시간 업데이트(Upsert) 기능을 활용하여 지속적으로 변경되는 데이터를 즉시 반영하는 용도로 Pinot를 도입했습니다.Pinot 도입 초기에는 참고할만한 운영 사례가 부족하고 현재 버전에 비해 안정성이 다소 미흡해서 고생을 많이 했었는데요. 이 글에서는 오픈소스인 Apache Pinot를 도입하고 운영하면서 얻은 경험과 노하우를 공유하고자 합니다. Pinot 운영이 복잡할 수 있지만, 이 글에서 운영 노하우를 숙지하시면 실시간 데이터 분석을 활용하시는 데 도움이 될 거라 생각합니다. (본 내용은 Pinot 1.2.0 버전 기준으로 작성하였습니다.)이 글은 이런 분들에게 추천해요.• 실시간 OLAP에 관심 많은 엔지니어• Pinot 도입을 고려 중이거나 운영 중인 엔지니어이 글을 통해 다음과 같은 것들을 얻어가실 수 있어요.• 운영 경험에서 얻은 이슈 해결 케이스• 서비스 안정성을 높이는 클러스터 구성 Tip• 실시간 업데이트 테이블 성능을 높이는 방법이 글은 Pinot를 운영하면서 얻은 유용한 노하우를 전달하는 게 목적이므로 Pinot 개념은 간단하게 짚고 넘어가겠습니다.Pinot는 링크드인(LinkedIn)에서 공개한 실시간 OLAP 오픈소스 솔루션입니다. 링크드인 외에도 Uber, Stripe 등 다양한 기업에서도 Pinot를 활용하여 실시간 대시보드, 실시간 분석 애플리케이션 등 다양한 서비스를 제공하고 있습니다.• Stripe: 실시간 결제 대시보드, 판매자를 위한 실시간/배치 데이터 집계데이터 제공Pinot의 핵심적인 특징은 대용량 데이터를 빠르게 쿼리할 수 있도록 설계된 분산 시스템이라는 점입니다. 데이터를 세그먼트(Segment)로 분할하여 여러 서버에 분산 저장하고, 병렬 처리 방식으로 빠른 쿼리가 가능합니다. Pinot는 실시간 스트리밍 데이터와 배치 데이터를 수집하여 즉시 쿼리할 수 있습니다.Pinot 클러스터는 다음과 같은 주요 컴포넌트로 구성됩니다.• 브로커(Broker): 클라이언트로부터 쿼리 요청을 받아 서버로 라우팅하고, 여러 서버에서 받은 결과를 취합하여 최종 결과를 클라이언트에 전달함.• 서버(Server): 세그먼트 데이터를 저장하고 쿼리 요청을 처리함.• 컨트롤러(Contr
2025.05.13
hadoop
kafka
trino

좋아요

별로에요