이번 글에서는 복제에 이어 데이터 파티셔닝 방법과 이를 인덱싱하기 위한 전략을 알아봅니다. 특히 데이터가 커지는 경우에는 필수적으로 파티셔닝이 필요하며, 이를 나누기 위한 키-범위 파티셔닝과 해쉬 파티셔닝에 대해서 알아보고, 쓰기와 읽기 성능을 고려한 로컬과 글로벌 이차 인덱스방법에 대해서 기술합니다. 또한 개발이 끝이 아니듯 운영에서 발생할 수 있는 리밸런싱 전략에 대해서 알아봅니다. 전반적으로 대용량 데이터베이스를 설계자 입장에서 고려하고 고민할 것들이 많이 도출 되는 좋은 단원이였습니다!
신규개념
개념
설명
파티셔닝(Partitioning)
성능, 확장성, 유지성을 목적으로 논리적인 데이터를 다수의 엔터티로 분할하는 행위
복제(Replication)
동일한 데이터를 여러 노드에 저장하여 장애 복구를 대비하는 방법
노드(Node)
스토리지 단일 서비스 혹은 네트워크 일부를 구성하는 서버
핫스팟(Hospot)
특정 파티션에 부하가 집중되는 현상
해시 파티셔닝
데이터를 균등하기 분산시키기 위해서 해시 함수를 사용
키 범위 파티셔닝
연속된 키 범위에 따라 데이터를 분배
이차 인덱스 파티셔닝
검색을 위한 인덱스를 파티셔닝하는 방식
리밸런싱(Rebalancing)
노드 추가/삭제 시 데이터를 재분배하는 과정
라우팅(Routing)
네트워크에서 경로를 선택하는 프로세스
1. 파티셔닝의 정의
파티셔닝이란 큰 데이터베이스를 여러 개의 작은 데이터 베이스로 나누는 기법
네트워크 분할(Network Partition)과는 다르며 주 목적은 확장성(Scalability) 확보
대용량 데이터를 효율적으로 관리하고 고 성능 쿼리 처리를 가능하게 한다.
보통 샤딩(Sharding)이라는 용어와 혼동되며 데이터 베이스마다 다르게 불
(Amendment) 사실 파티셔닝이라는 개념은 윈도우 설치할 때 하나의 디스크에 "파티셔닝"하여 C / D드라이브로 만들 때 마주할 수 있다.
이처럼 파티셔닝은 데이터베이스를 논리적으로나 물리적으로 분할하여 관리하는 기술로, 데이터 베이스의 성능응 향상시키고 관리를 용이하게 합니다. 파티셔닝을 통해 데이터 베이스 조회 속도를 높이고 백업 및 복구시간을 단축할 수 있기 때문입니다.
출처: F-lab 데이터 파티셔닝 전략
2. 파티셔닝과 복제
대부분의 시스템에서 파티셔닝과 복제가 함께 사용됨
파티셔닝: 데이터를 여러 노드에 나누어서 저장
복제: 동일한 데이터를 여러 노드에 저장하여 장애 대비
(Amendment) 파티셔닝과 복제가 주로 이루어지는 이유는 각 목적을 살펴보면 된다. 데이터의 크기가 커질수록 한 노드(서버)에 저장할 수 없게 된다. 이를 여러 노드에 나워서 수평 확장을 노려볼 수 있다. 파티션 단위로 데이터 읽기/쓰기 작업을 하여 성능 향상을 하는 한편, 특정 파티션만 검색하도록 최적화하여 쿼리 성능을 향상시킬 수 있다. 이로 인한 단점은 한 노드에 한 개만 저장되므로 노드 장애 시 데이터 유실 위험이 존재한다. 반면, 복제는 고가용성(High Availability)와 내결함성(Fault Tolerance) 확보가 목적으로 동일한 데이터를 여러 노드에 복제하여 데이터 손실을 방지하며, 특정 노드가 장애가 나더라도 복제본의 데이터를 제공을 가능하게 한다. 읽기 부하(Read Load)를 여러 복제본에 분산하여 읽기 성능 향상이 가능하며 특히 리더 -팔로워 모델이 유용하다. 단점으론느 복제된 데이터를 주기적으로 동기화해야하므로 쓰기 성능 저하 가능하며 데이터 일관성의 유지가 필요하다. 따라서, 파티셔닝. 을 통해 확장성을 확보하는 한편 복제를 통해 가용성을 보장하는 것이 대규모 데이터 베이스의 운영의 핵심이라고 할 수 있다.
3. 파티셔닝 전략
1. 키 - 범위 파티셔닝(Key Range Partitioning)
연속적인 키 범위를 기준으로 데이터를 분할
장점: 키 순서가 유지되므로 범위 쿼리에 적합
단점: 특정 키 범위에 쿼리가 집중되면 핫 스팟 발생 가능
쉽게 말하면 백과사전에서 특정 단어를 찾는 과정과 비슷하며, Hbase, RethinkDB, MongDB에 쓰임. 핫스팟의 문제는 센서 데이터의 예를 들면 편한데, 만약 센서 데이터의 파티션이 날짜로 지정해되면 특정 파티션은 과부화가 걸리는 반면 다른 파티션은 유후상태(Idle)이 된다. 이를 보완하기 위해서 센서이름 + 타임스탬프 형태의 키를 구성하면 각 센서별로 파티션에 나뉘고 이후 시간 순서대로 저장되므로 쓰기 부하가 고루 분산될 수 있는 장점이 있다.
2. 해시 파티셔닝(Hash Partitioning)
키를 해시 함수에 통과시켜 균등하게 분산
장점: 데이터가 균등하게 분배되어 핫스팟 발생 가능성 적음
단점: 키 정렬이 깨져서 범위 쿼리의 어려움
skwed된 데이터와 hotspot의 문제 때문에 고안된 데이터 모델. 파티션 간의 고른 데이터 분배를 노릴 수 있음.
4. 파티셔닝과 이차 인덱스
이차 인덱스는 데이터 베이스에서 기본 키 이외에 다른 컬럼을 기준으로 빠르게 데이터를 검색하기 위한 인덱스이다. 파티셔닝이 적용된 데이터베이스에서는 기본 키를 기준으로 데이터를 찾는 것은 쉽지만, 기본 키가 아닌 다른 컬럼으로 검색할 때 문제가 발생할 수 있으며 이를 해결하기 위해 이차 인덱스가 등장한다. (Amendement)
CREATE TABLE users (
user_id INT PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(255),
country VARCHAR(50)
);
위 경우 user_id가 기본키이므로 user_id = 1234 는 빠르게 검색 가능한 반면, where country = 'USA' 같은 쿼리는 효율적으로 실행되지 않으므로, 다음 이차 인덱스를 생성하여 검색 성능 향상을 노린다.
CREATE INDEX idx_country ON users(country);
이런 이차 인덱스를 저장함에 있어서 2가지 방식을 선택할 수 있다. 1. 문서 기반 인덱스( Document-partitioned indexes, Local Index)
각 index를 각 파티션에 저장
이차 인덱스를 해당 데이터가 있는 파티션 내부에 저장
이차 인덱스가 기본키 및 값과 동일한 파티션에 저장됨. 즉 데이터 쓰기를 할때는 해당 파티션 하나만 업데이트하면 되므로 쓰기 성능이 우수하다. 반면 이차 인덱스를 기반으로 조회할 경우 모든 파티션을 검색(Scatter)하고 결과를 모아야(Gather)해야하므로 성능 저하 가능성이 있다. (Amendment) 예를 들어 country = 'USA" 데이터가 각 파티션에 분산되어 있다면 각 파티션 마다 country 인덱스가 생성 됨. 데이터를 추가하거나 수정할 때 해당하는 파티션만 업데이트하면 되므로 쓰기 성능이 좋으나, 데이터 검색할 때는 쿼리 속도가 느려질 수 있다. 2. 용어 기반 인덱스(Term-partitioned indexes, Global Index)
Gloabl 인덱스를 여러 파티션에 나눠 저장
이차 인덱스를 별도의 파티션으로 저장
이 방식에서는 이차 인덱스가 기본키와 독립적으로 분할되어 저장되어 이차 인덱스는 특정 필드 기준으로 별도로 파티셔닝된다. 또한 하나의 인덱스 방식이 여러 개의 파티션에 있는 데이터를 포함할 수 있다. 쓰기 시에는 여러개의 파티션을 업데이트 해야하므로 쓰기 성능이 저하될 수 있으나 읽기 시에는 특정 값을 포함하는 단일 파티션에서만 데이터를 가져올 수 있으므로 조회 성능이 향상된다. (Amendement) where country = 'USA' 검색 시 USA 관련 데이터가 저장된 단일 파티션만 조회하면 되므로 성능이 빠르고 읽기 성능이 최적화, 데이터를 추가할 때, 이차 인덱스가 여러 파티션에 걸쳐 존재하므로 여러 개의 파티션을 업데이트 해야하여 쓰기 성능이 저하될 수 있다.
문서기반(로컬 인덱스)
용어기반(글로벌 인덱스)
쓰기성능
빠름
느림
검색성능
느림
빠름
5. 리밸런싱
개발보다 중요한 것은 운영이라고 했던가 필수적으로 데이터베이스를 운영하다보면 특정 노드에 데이터가 집중되거나 부하가 편향되는 불균형(imbalance)가 일어날 수 있다. 따라서 리밸런싱을 통해서 데이터와 쿼리 부하를 공정하게 분배해야하는 요구사항이 반드시 생겨난다. 또한 리밸런싱을 할 떄 지켜야할 원칙은 ① 리밸런싱 후 노드간의 데이터와 부하가 균등해야하며 ② 리밸런싱 과정에서 읽기와 쓰기에 대한 수행은 계속되어야 하며 ③ 리밸런싱의 속도를 빠르고 네트워크 및 디스크 I/O 부하를 최소화하기 위하여 필요한 데이터 이상의 데이터가 노드 간 이동해서는 안된다. 등 이다. 1. 고정된 파티셔닝(Fixed Partitioning)
데이터베이스를 고정된 파티션으로 나누는 기법. 노드가 추가되거나 삭제되어도 파티션의 갯수는 동일
가장 고전적인 방법으로 고정된 파티션의 갯수를 유지하는 방법이다. 하지만 처음에 너무 적은 파티션을 설정하면 확장시 부담이 커지고 많으면 관리 오버헤드가 커진다. 시간이 지나면 특정 파티션에 데이터가 몰려 일부 파티션에만 과부화가 생길 수 있으며, 초기 설정된 파티션의 갯수가 적절한지 어려운 단점이 있다. 따라서 데이터 양이 일정하고 급격한 변화가 없는 경우, 시스템 운영이 단순해야하는 정형데이터의 경우나 리밸런싱 속도가 중요한 경우 고정된 파티셔닝 방법을 고려할 수 있다. 2. 동적 파티셔닝(Dynamic Partitioning) 동적 파티셔닝은 데이터가 증가하면 자동으로 파티션을 2개로 분할하고 반대로 삭제하면 통합하는 방식으로 관리하는 기법이다. 하지만 데이터가 작은 경우 모든 쓰기의 연산이 하나의 노드에서 처리되며 나머지 노드는 유후(Idle)상태가 된다.
6. 라우팅 요청(Request Routing)
리밸런싱이 필요하고 멋지지만 비싼 작업인건 확인해보았다. 그럼 이렇게 수동/자동 혹은 고정/동적인 리밸런싱 과정 속에서 데이터 읽기/쓰기에 대한 요청이 들어왔을 때 누가 중재해줄 것인가에 대한 질문이 자동으로 생긴다. 이를 Service Discovery 라고 한다.
foo 라는 자료를 찾을 때 시도할 수 있는 방법 3가지
아마 합리적인 방법은 각 파티션이 가지고 있는 key range에 대한 정보를 저장하는 것이고 이를 ZooKeeper 라고 한다.
7. 마무리
사실 데이터에 쿼리를 날려만 봤지 대용량 데이터를 저장하는 설계쪽에서는 생각해본적이 거의 없다. 데이터의 무결성과 정합성을 지키기위한 방법부터 초기 데이터베이스에서 확장가능하면서 성능을 최적화하기 위한 파티셔닝 그리고 그 파티셔닝을 운영단계에서 리밸런싱 하는 과정 등을 한번에 볼 수 있는 너무나 좋은 단원이라고 생각되었다. 물론 SaaS 서비스에서는 이를 서비스에 맡기고 인간의 개입은 최소화하겠지만 이런 이론들을 알고 공부할 수 있는 이 책의 전개 흐름과 다이어그램이 너무 좋았다. 또또 공부해보고 싶은 마음이 든다. 이 단원을 통해 앞으로 알았으면 좋겠는 것들은 다음과 같다.
솔직히 말이죠 이런 책을 읽는다는게 쉽지만은 않습니다만, 특히 이번장에서는 데이베이스의 저장구조와 검색에 대한 테크니컬한 내용이 많이 들어가서 중간에 도망갈뻔 했습니다. 그런데 참고 읽다보니 OLTP와 OLAP에 대한 구조가 너무나도 비교가 잘되었고 최근(?) 유행하게 되었는 칼럼 데이터베이스도 눈 여겨볼 수 있는 좋은 단원이였습니다. 얼마나 좋았나면 분석하는 분들에게 일부 문단을 뜯어서 읽어주고 싶은 느낌이였어요. 필자는 2장에서는 어플리케이션 개발자가 데이터 베이스에 데이터를 제공하는 형식을 설명한다면 3장은 데이터베이스 관점에서 데이터를 저장하는 방법과 요청했을 때 다시 찾을 수 있는 방법에 대해서 안내하고 있습니다. 한번 가보시죠~
신규개념
개념
설명
로그(Log)
컴퓨터 시스템과 네트워크에서 발생하는 활동과 사건에 대한 기록
컴팩션(Compaction)
로그에서 중복된 키를 버리고 각 키의 최신 갱신 값만 유지하는 것을 의미한다.
오버헤드(overhead)
어떤 처리를 하기위해 들어가는 간접적인 처리시간
오프셋(offset)
두번째 주소를 만들기 위해 기준이 되는 주소에 더해진 값(like 상대주소)
병합정렬(mergesort)
하나의 리스트를 균등한 크기로 분할을 하고, 분할된 부분을 정렬한뒤 합치면서 전체 정렬하는 방법
SS테이블(Sorted String Table)
세그먼트 파일 형식에서 키-값 쌍을 가진 테이블 형태의 데이터 구조
LSM 트리
Log Structed merge Tree, 정렬된 파일 병함과 컴팩션 원리를 기반으로 하는 저장소 엔진
OLTP
Online Transaction Processing,인터넷을 통해 많은 사람들이 동시에 데이터베이스를 처리하는 시스템
OLAP
Online Analytical Processing, 온라인상에서 데이터를 분석 처리하는 기술
데이터 웨어하우스
Data Warehouse, 다양한 데이터 소스에서 데이터를 추출, 변환, 요약하여 사용자에게 제공할 수 있는 데이터베이스의 집합체
1. 해시색인, B-tree
(New) 데이터베이스를 가장 쉽게 만드는 방법은 키-값 저장소의 형태로 로그(log)처럼 추가 전용(append only)한 특성을 사용할 수 있다. 하지만 레코드가 많을수록 검색의 비용은 O(n)이기 때문에 성능 최적화 측면에서 바람직하진 않다. 따라서 색인(index)를 이용할 수 있다. 색인은 기본 데이터의 파생된 추가적인 구조이기 때문에, 내용에는 영향을 미치지 않지만 단지 질의 성능에만 영향을 준다.
갑자기 생각난건데, 내가 입사한 회사에서는 의료처방기록을 나라에서 공급받아 분석했었는데, 인덱스가 걸려있는 컬럼이 환자 식별자에만 걸려있냐고 물어보니까, 그냥 모든 컬럼을 걸었다고 답변받았던 기억이난다. 이유는 데이터가 실시간도 아니고 행이 몇 억개밖에 안되어서 그렇다고...
key-value 저장소는 보통 해시 맵(has map)으로 구현하며, 색인 전략은 키를 데이터 파일의 바이트 오프셋에 매핑해 인메모리 해시 맵을 유지하는 전략이다.
하지만 파일에 항상 추가만 한다면 결국 디스크 공간이 부족해진다. 이는 특정 크기의 세그멘트(segment)로 나누는 방식이 좋은 해결책이다. 이를 컴팩션(compaction)을 수행하여 해결할 수 있다. 컴팩션은 로그에서 중복된 키를 버리고 각 키의 최신 갱신 값만 유지하는 것을 의미한다.
고양이(mew) 동영상의 조회수가 1082 최신값으로 업데이트
내가 운영하는 마인크래프트 서버도 latest.log파일에 저장이 되고 일정 수준이 넘어가면 2025-02-21-(1), (2)와 같이 Segemet가 저장되는데 이것도 일부의 compaction 이라 할 수 있을까? 생각했지만 정보를 덮어쓰거나 하지 않으므로 거리가 있다고 생각했다.
(궁금한 점) Youtube 도 Compaction을 이용해 조회수를 업데이트 할까? 근데 그럼 1시간 전의 조회수나 24시간의 조회수는 나중 시점에 알 수 없는걸까? 갓 구글이 이런 데이터가 없을리 없을 것 같고...그냥 log별로 따로 저장할까? 책의 예시를 Youtube에 저장하려니 조금 결이 안맞는 것 같은 생각이 든다.
2. SS테이블과 LSM트리
(New) 해시테이블은 메모리에 저장해야하므로 키가 너무 많으면 문제가된다. 무작위 접근이 많이 필요하고 가득찼을 때 확장비용이 비싸기 때문. 또한 해시 테이블은 범위 질의(range query)에 효율적이지 않다. 키를 정렬하여 이를 보완한 것이 정렬된 문자 테이블(Sorted String Table)이다. 세그멘트 병합은 병합정렬과 비슷하다(다음 그림)
(Difficulty) 그리고 이 SS테이블과 memtable(쓰기가 들어오면 인메모리 균형트리 데이터구조?)의 동작의 단점으로 memtable의 가장 최신 쓰기가 손실되어 분리된 로그를 디스크 상에 유지하기 위하여 LSM(log-structed Merge Tree)가 나왔다는데 memtable에서 이해가 멈췄다....
데이터 웨어하우스(Data Warehouse)는 분석가들이 OLTP 작업에 영향을 주지 않고 마음껏 질의할 수 있는 개별 데이터베이스이며, OLTP 시스템에 있는 데이터의 읽기 전용 복사본이다.
솔직히 이 단원을 보기 전까지는 웹어플리케이션에서 발생하는 데이터에 대한 경험이 다소 적었기 때문에 OLTP와 OLAP에 대한 특별한 구분점을 찾지 못하였다. 하지만 OLTP 시스템 자체가 비즈니스 데이터 처리(Ex 전자 상거래)를 위해 즉 서비스 운영을 위해 만들어진 본래의 목적을 가지고 있다면, 분석을 그 자체에서 수행한다는 것은 매우 위험한 일임을 이제야 깨닫게 되었다. 따라서 이를 "복제"하여 데이터의 복사본을 가지고 분석을 수행해야함을 이해할 수 있었고, 또한 분석이라는 쿼리 자체의 특성 예를 들면 일부 컬럼을 가져오고 대량의 행을 가져오는 등의 특성에 필요한 데이터베이스의 설계가 필요한 점을 이해할 수 있었다.
(Amendment) 스타스키마(star schema) 다음 databrick 블로그의 인용문구로 대체!
스타 스키마는 데이터베이스에서 데이터를 정리하는 데 사용하는 다차원적 데이터 모델로, 쉽게 이해하고 분석할 수 있습니다. 스타 스키마는 데이터 웨어하우스, 데이터베이스, 데이터 마트 등의 툴에 적용할 수 있습니다. 스타 스키마는 대규모 데이터 세트에 대한 쿼리를 최적화하도록 설계되었습니다. Ralph Kimball이 1990년대에 도입한 스타 스키마는 반복적 비즈니스 정의의 복제를 줄여 데이터 웨어하우스에서 데이터를 빠르게 집계하고 필터링하도록 지원하므로 데이터 저장, 내역 관리, 데이터 업데이트에 효율적입니다. https://www.databricks.com/kr/glossary/star-schema
5. 컬럼지향 데이터베이스
(New) 대부분의 OLTP는 로우 지향의 방식으로 데이터를 배치. 문서 데이터와비비슷하며, 컬럼 지향 저장소는 칼럼 별로 모든 값을 함께 저장함. 또한 압축에 능함!!
특히, 나는 Pandas라는 모듈을 자주 사용하곤 하는데 이 문단에서 Pandas의 자료형이 생각났다. 해당 모듈에서는 DataFame이라는 행열 구조의 matrix와 Series라는 열구조 2가지의 데이터 자료형을 선언해놓았다. 왜 하필 행 구조가 아닌 열구조인 Series로 선언했을까 고민했을 때 얼핏 보았던, 분석은 컬럼단위로 진행된다는 이 공감이 가서 그렇게 이해했는데 이게 OLAP의 특성과 비슷하다고 생각했다.
6. 마무리
초반에는 지끈지끈 했지만 데이터베이스의 저장과 검색 관점에서 차근히 풀어가는 책의 전개방식이 되게 좋았다. 발생 -> 문제 -> 해결 -> 발전으로 이루어지는 데이터베이스의 발전 방향과 특히 데이터 분석 쪽에 이렇게 연결되어 있는 (책 발간당시는 꽤나 과거) 자료를 이제야 찾다니 그것도 흥미로웠다. 생각보다 어플리케이션 분석은 유래가 오래되었고 이를 잡마켓 수면위로 올라온지 얼마안된 것 뿐이구나 라고 생각했다
대부분의 데이터분석의 자료의 출처는 관계형 데이터베이스(RDB)를 말합니다. 정형데이터를 관리할 수 있는 Standard로 여겨졌고 실제로도 Oracle을 필두로한 데이터베이스 시스템이 과거 주류를 차지했습니다. 하지만 RDB의 정규화의 특징으로 나타나는 문제들이 발생할 수 있고 그에 따라 파생된 NoSQL 데이터 모델들이 등장했습니다. 이번 글에서는 데이터 모델의 역사와 종류 그리고 어플리케이션을 설계할 때 있어서 어떤 데이터 모델을 선택해야하는지에 대한 근거를 알아보도록 하겠습니다. 본 글에서는 New, Difficulty, Amendment 형식에 따라 작성하겠습니다.
1. 관계형 데이터베이스
관계형 데이터베이스(RDB)는 1980년대부터 약 30년간 주류를 이뤄왔습니다. 트랜잭션(transection)는 데이터베이스의 상태를 변경시키는 작업단위로, 원자성(Atomicity), 일관성(Consistency), 격리성(Isolation), 지속성(Durability) 의 ACID의 개념을 중심으로한 특징을 가지고 있으며 다음 링크에 잘 설명되어있습니다.
비즈니스 데이터 처리에 중점을 둔 RDB가 장기간 집권하였습니다. 반면, 컴퓨터를 더 다양한 목적으로 사용함에 따라 쓰기 확장성, 특수 질의, 동적이고 풍부한 표현력을 가진 데이터베이스 니즈에 따라 2010년 NoSQL의 탄생이 이루어졌습니다. 다음은 링크드인을 예시로하는 링크드인 예시로 보는 관계형과 문서형 DB의 구조 차이입니다.
(좌) RDB 의 구조 / (우) 문서형 DB의 구조
3. 문서형 데이터베이스
관계형 데이터베이스의 Schema는 엄격하지만 또한 성능의 저하도 일으키는 문제를 가지고 있습니다. 예컨데 ALTER TABLE을 통한 스키마 변경을 위해서는 테이블 사용을 중단해야하는 등의 이슈가 있습니다. 이를 대안으로 가져온 Schemaless한 구조가 NoSQL이였고 Elasticc Search 가 대표적인 서비스로 Kibana는 이와 호환되는 시각화 툴 입니다.
EK의 파이프라인 흐름
(Amendment) 실제로현대에는 관계형 데이터베이스와 문서형 데이터베이스의 혼합이 이루어지고 있으며, 종종 프로젝트 주제로 나오는 starbuck 데이터에서 Table안에 JSON 형식을 가지고 있는 것이 생각났습니다. 또한 빅쿼리의 구조체(Structure)도 이런 일환 중 하나일까 생각이 들었습니다.
데이터를 위한 질의 언어 질의 언어는 SQL와 같은 선언형 질의 언어와 흔히 말하는 프로그래밍과 유사한 명령형 코드로 나눌 수 있습니다. 명령어는 특정 순서로 연산을 하게끔 구체적으로 지시합니다. Loop을 돌리며 조건을 만족하는지 판단하고 추가 Loop를 지속하거나 중단하는 등의 분기점을 나눕니다. 반면 선언형 질의언어에서는 방법이 아닌 알고자하는 패턴에 대해서 기술하게 됩니다. 다음은 선언형 질의언어의 예시인 SQL 의 문법 구조입니다.
SELECT {컬럼}, {집계함수}
FROM {테이블}
WHERE {조건}
GROUP BY {컬럼}
이 점에서 SQL이 LLM의 자연어 질의와 비슷하다고 생각했습니다. 두 질의언어 모두 구체적인 명령이나 절차에 대해서 명시하지 않으며 상징적으로 선언하며 그 내부의 로직을 LLM이나 쿼리최적화에 맡긴다는 점이 공통적이였기 때문입니다. 또한, 웹에서 CSS 선택자(selector), XPATH 표현식 역시 선언형 질의언어라는 형식이라는 것이 재미있었네요.
4. 그래프 데이터베이스
그래프 데이터 예시: https://www.gov-ncloud.com/marketplace/AgensGraph
어플리케이션이 주로 일대다 관계이거나 레코드 간에 관계가 없다면 문서 모델이 적합 할 것입니다. 하지만 다대다 관계가 일반적인 소셜네트워크와 같은 경우면 그래프형 모델링이 더 자연스럽다고 할 수 있겠습니다. 그래프는 Vertex(정점 혹은 노드, 엔터티라고 불림)과 Edge(간선 혹은 관계)의 개념을 중심으로 하는 모델입니다. 예를 들어 자동차 네비게이션과 같이 도로 네트워크에서 두 지점간 최단 경로를 검색하는 등의 동작이 그래프 모델의 활용 방법이 될 수 있으며 그래프의 장점은 동종 데이터에 국한되지 않는다는 점입니다.
Neo4j 의 핵심개념은 노드(Node), 관계(Relationship), 속성(Properties), 라벨(Label)등으로 이루어져있으며, Cypher 쿼리 언어로 노드와 관계를 탐색하고 조작합니다. 대표적으로 Cypher 쿼리 언어의 코드는 다음과 같습니다.
1. 노드 생성
CREATE (n:Person {name: '홍길동', age: 30})
2. 관계 생성
MATCH (a:Person {name: '홍길동'}), (b:Company {name: 'ABC기업'}) CREATE (a)-[:WORKS_AT]->(b)
3. 데이터 조회
MATCH (n:Person)-[:FRIEND_OF]->(friend) WHERE n.name = '홍길동' RETURN friend.name
4. 데이터 업데이트
MATCH (n:Person {name: '홍길동'}) SET n.age = 31
5. 노드 및 관계 삭제
MATCH (n:Person {name: '홍길동'}) DETACH DELETE n
5. 데이터 모델의 차이점
관계형 데이터베이스
문서형 데이터베이스
그래프 데이터베이스
핵심키워드
SQL, 정규화, 테이블, 관계
JSON, 비정형 데이터, Schemaless
Vertex, Edge
장점
강력한 데이터 무결성, 복잡한 쿼리(SQL) 가능, 데이터 구조가 명확함
유연한 스키마, 직관적인 데이터 저장 가능, 수평 확장 용이
복잡한 관계(N:N) 데이터 빠르게 탐색, 네트워크 분석
단점
스키마 변경 어려움, 대규모 데이터 처리 시 성능 저하
데이터 중복 가능성, N:1 관계 적합하지 않음
대규모 데이터셋에서 성능 튜닝 필요
사용 예시
금융 시스템, 전통적인 웹 어플리케이션 백엔드(MySQL)
콘텐츠 관리 시스템, 로그 및 이벤트 데이터, 전자상거래(MongoDB)
추천시스템, 지식 그래프
종합적으로 각 데이터 모델을 살펴보니 어플리케이션의 목적에 따라 데이터 모델을 선택해야하는 점을 명확히 할 수 있었습니다. 고전적인 비즈니스 데이터 처리에는 관계형 데이터베이스를 컨텐츠 보관과 schemaless한 빠른 저장은 문서형 데이터 베이스, 다대다 관계를 염두에 둔 처리는 그래프 데이터베이스를 선택해야할 것입니다. 모호한 데이터 모델들에 대해서 이해할 수 있는 과목이였고, 물론 그래프 데이터베이스를 직접 마주한 적은 없어서 모호한 면이 있었지만 재미있게 읽었습니다.
해당 카테고리와 글은 데이터 중심의 어플리케이션 설계 책을 Pair reading하는 스터디의 결과물을 저장합니다. 사실 데이터라는 말에 혹해서 선택한 책이지만 백엔드 관점에서 이상적인 설계가 무엇인지 다루는 책이긴 합니다. 그럼에도 불구하고 한번 읽어보려고 용기있게 스터디를 모았습니다. 해당 스터디는 다음 템플릿을 이용해 매주 작성할 예정입니다.
1. 스터디 방안
모집인원: 최대 10명
기간
매주 약 chapter 1개 분량( 40 ~ 60 page)
Part1: 2025. 02. 09(일) ~ 2025.03.02(일) / 총 4주
Part2: 2025. 03. 09(일) ~ 2025.04.06(일) / 총 5주
Part3: 2025. 04. 13(일) ~ 2025.04.27(일) / 총 3주
진행방식
매 주 1단원씩 책을 읽고 다음 템플릿(NDA)를 이용하여 본인의 블로그, 노션, 줄글로 주차별 스레드 등에 작성합니다.
새롭게 알게된 점(New)
어려웠거나 이해하지 못한 부분(Difficulty)
추가 내용(Amendment): 학습이 도움이 되었던 블로그, 유튭, 사례 등
매주 일요일 22시에 허들로 모셔 학습에 대한 간단한 talk을 합니다. 30분정도?
2. 본문
일단 본인은 CS를 그냥 아는 데이터 분석가임을 밝힙니다. 그럼에도 불구하고 CS지식을 읽는데 그렇게 어려움을 느끼진 않습니다. 하지만 본 책을 읽는데는 확실히 기초지식이 떨어지기 때문에 용어와 흥미로운 레퍼런스 위주로 찾아보면서 꼬리 학습을 진행할 예정입니다.
Part1는 데이터 시스템의 기초로 Chatper 1은 신뢰할 수 있고 확장 가능하며 유지보수하기 쉬운 어플리케이션 이라는 거창한 제목으로 시작합니다. 마지 완벽한 이상향을 찾는 것 같지만 그래도 이상향을 추구해야 남는 쪼가리라도 이쁜 것이니 한번 살펴보려고합니다. 여기서 말하고자하는 데이터 시스템의 3가지 요소는 신뢰성(Reliability), 확장성(Scalability), 유지보수성(Maintainability) 3가지 입니다.
3. New
메시지 큐(Messae Queue): 큐(Queue)를 이용해서 메세지를 전달하는 시스템이며 메시지 지향 미들웨어(MOM)을 구현한 시스템이다. 라고 합니다. 근데 솔직히 API와 구별점을 잘 못찾겠어서 물어봤습니다.
메시지 큐(Message Queue)
API
정의
비동기적인 메시지를 주고 받을 수 있는 큐 시스템
한 시스템이 다른 시스템의 기능을 호출할 수 있도록 정의된 인터페이스
주요 특징
메시지를 임시 저장하고 필요한 시점에 소비 가능
실시간 요청 - 응답 방식으로 동작
예시
이메일 전송 요청 -> 메시지 큐에 저장 -> 이메일 서버가 가서 처리
사용자가 웹 사이트에서 버튼 클릭 -> 서버에 API 요청 전송 -> 응답 반환
둘이 동등 비교하는 항목은 아닌 것 같은데 가장 핵심적인 부분은 동기 & 비동기 시스템이지 않을까 라고 생각했습니다. Kafka 와 같은 소프트웨어가 메시지를 저장하고 처리하는 브로커의 대표명사인 것 같습니다.
Core Components of a Message Queue
4. Difficulty
이번 챕터에서 흥미로우면서도 명확하게 이해하지못 한 것은 확장성 단원의 부하기술하기 - 트위터 사례입니다.
트윗 작성과 홈 타임라인의 처리를 위한 기존 레거시 구조는 다음과 같다고 합니다.
1. 트윗 작성은 새로운 트윗 전역 컬렉션에 삽입한다. 사용자가 자신의 홈 타임라인을 요청하면 팔로우하는 모든 사람을 찾고, 이 사람들의 모든 트윗을 찾아서 시간순으로 정렬해서 합친다. 그럼 그림 1-2와 같은 관계형 데이터베이스 에서는 다음과 같이 질의를 작성한다.
2. 각 수신 사용자용 트윗 우편함처럼 개별 사용자의 홈 타임라인 캐시를 유지한다.(그림 1-3 참고) 사용자가 트윗을 작성하면 해당 사용자를 팔로우하는 사람들을 모두 찾고 팔로워 각자의 홈 타임라인 캐시에 새로운 트윗을 삽입한다. 그러면 홈 타임라인의 읽기요청은 요청 결과를 미리 계산했기 때문이 비용이 저렴하다.
하기 문단은 책의 해설을 보고 제가 생각한 내용입니다.
위 내용을 보면 결국 트위터는 1,2번을 혼합한(Hybrid) 구조로 만들었다고 합니다. 생각해보건데 1번 지문에서 팔로우하는 모든 사람을 찾고 부분이 full scan이며, 시간순으로 정렬한다는 점 시간복잡도 관점에서 성능이 떨어지는 요인에 기인하지 않았을까 생각이 들긴합니다. 1번은 홈타임라인을 보는 시점에 부하가 많고 2번은 트위터를 쓰는 시점에 부하가 많으므로, 팔로워가 매우 많은 소수 사용자들은 1번을 이용해 보는 시점에 리소스를 쓰고(수많은 팔로워가 한번에 보진 않을거고 시점을 나눠서 타임라인을 볼꺼니까?) 그리고 대부분의 사용자들은 팔로워가 적으니까 2번처럼 쓰는 시점에 부하를 높혀 상호보완적으로 하지 않았을까 라는 생각이 들었습니다.
5. Amendement
- 샌드박스(Sandbox): 라이브 환경에 영향을 미치지않고 소프트웨어나 어플리케이션을 테스트하고 개발할 수 있는 제어된 환경. 번역 그대로는 나무나 플라스틱으로 만들어진 모래가 담긴 공간이며 간이 놀이터라는 의미를 담고 소프트웨어 쪽에서 사용되는 듯.
흔히 정부사업에서 (규제) 샌드박스라는 단어가 등장하는데, 이는 특정 산업의 육성을 위해 일정구간 규제를 면제하거나 유예하는 것을 의미함. 토스는 "선불전자지급수단으로 상품 구매 시 결제부족분에 대한 소액후불 결제 서비스"의 서비스명으로 규제 샌드박스를 지정받고 성과 낸 경험이 있음
이번 글은 슬랙 기반 커뮤너티에서 참여자들에게 독려의 CRM 메세지를 보낸 경험과 커뮤너티 안의 게임채널에서 방문알람 봇을 만든 사례를 작성합니다. CS에 대한 개념이 살짝 필요하긴 하지만 기본적인 API 개념과 파이썬을 활용하여 쉽게 만들어볼 수 있는 사례라서 API를 제공하는 커뮤너티 어플리케이션에서 유용하게 쓰는데 도움이 되길 바랍니다!
1. 개요
필자는 개발자의 글쓰기 모임인 글또10기에 참여하고 있으며 또한 운영진으로 활동하고 있다. 아무래도 개발자 기반의 커뮤니티다 보니까 글쓰는 활동이나 내부 커뮤너티 활동들을 봇을 이용해서 활용할 기회가 많다. 예를 들면, 글 제출을 한다던지 혹은 제출된 글에 대해서 LLM으로 피드백을 해준다던지 자동화가 되어 있는 기능들이 많다. 이번 글에서는 Customer Relational Managment(a.k.a. CRM) 운영진의 일환으로 설날 맞이 보낸 CRM 메세지와 게임채널에서 마인크래프트를 이용한 서버를 운영하고 출석체크 봇을 만든 경험을 남기고자 한다.
2. 글또 CRM 메세지 보내기
2.1. CRM 메시지 기획하기
우리의 커뮤너티는 글쓰기를 주제로 약 4-6개월간 지속되는 활동으로 그 기간동안 활동한 내역을 집계하면 재밌는 얘기가 많이 나온다. 글또에서는 CRM 봇인지니 봇이 비정기적으로 지금까지의 활동내역을 보여주고 Activation을 넛지하는 전통(?)이 있다. 지난 기수도 역시 소소하게 이런 메세지를 보냈었고 반응이 되게 좋았었다. 아래는 이번기수에 내가 받은(사실 내가 보낸) 메세지이다.
10기 실제로 보낸(그리고 내가 받은) 메시지
역시 10기에도 보내자고 얘기가 계속 나왔고 결국 설날에 맞춰 보내기로 하였다. 내가 CRM 팀으로 이전한 건 이번이 처음이었기 때문에 추상적으로 어떻게 하겠다는 감은 있어도 실제로 해본적은 없기 때문에 좀 더 명확하게 이 프로세스를 전수 받고자 자처하여 메세지 봇을 만드는데 지원하였다. 사실 메세지를 보내는 시스템은 구축이 되어있었다. CRM 팀이 처음인 나는 지니봇이 작동하는 프로세스를 명확하게 하고 팀원들에게 공유하면 좋겠다고 생각했고 figjam을 이용해서 다음과 같이 정리했다. 참고로 우리의 지니봇은 주로 커뮤너티에서 전달하는 쿠폰이나 개인화 메세지에 초점을 맞춰 전달하고 있다.
이 과정에 크게는 3가지의 단계가 있는데
(데이터 마트 구축): 기존 또봇이 수집한 데이터 Log를 기반으로 참여자들의 글 제출, 메시지, 스레드 댓글, 이모지의 데이터를 집계하여 Data Mart를 만드는 과정
(지표 생성) 정보를 가지고 활동, 글, 커피챗 레벨을 지표로 만들고 친한 사람(팔로우)을 정의하는 과정
(메시지 전송) 참여자의 정보를 토대로 총합된 1,2의 정보를 불러와 각자의 지표에 맞춰 슬랙 봇으로 전송하는 과정
각자의 R&R이 나뉘고 나의 역할인 메시지 전송에서 작업을 시작했다. 이는 동민님의 Github에 잘 정리되어 있어서 답습하기 편했다. 하지만, 지니 봇은 특정 이벤트에만 작동시키는 경우가 많기 때문에 CRM 팀에서도 자주 사용하는 스크립트는 아니다. 따라서, 먼저 이를 파악하기 위해 동민님의 Github 레포를 뜯기 시작했다.
여기서 자주 사용하는 script는 /core/bigquery.py로 글또의 데이터는 빅쿼리에 적재되기 때문에 데이터를 저장하고 불러오는 등에 대한 메소드가 구현되어 있다. 빅쿼리와 슬랙을 주고 받는 기능에 대하여 개념적으로는 알지만 희미해서 새롭게 스크립트(message_basic.py)를 만들었다.
'''
genie봇이 메세지를 보내는 기본기능을 정의합니다. token은 관리자 문의
해당 스크립트를 실행하면 번호로 선택하여 실행하며, 기능을 수행할 대상에 대한 정보(user_id 혹은 스레드정보)를 전달해야합니다.
'''
import sys
import os
print(sys.version)
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
from core.bigquery import BigqueryProcessor
bigquery_client = BigqueryProcessor(
env_name="GOOGLE_APPLICATION_CREDENTIALS", database_id="geultto_10th"
)
SLACK_BOT_TOKEN = os.getenv("SLACK_BOT_TOKEN") # 윈도우 로컬 - 시스템 - 환경변수에 저장
USER_ID = "" # 메시지를 보낼 대상의 Slack User ID
#하기 두개의 인자는 스레드의 링크복사 URL에 정보가 저장되어있음
URL = ''# 개인정보 이슈 삭제
URL_LIST = URL.split('/')
CHANNEL_ID = URL_LIST[4] # 채널 ID
THREAD_TS = URL_LIST[5][1:-7] + '.' + URL_LIST[5][-7:] # 스레드 메시지의 timestamp
# WebClient 초기화
client = WebClient(token=SLACK_BOT_TOKEN)
def send_message_to_user(user_id, message):
'''
Slack 메세지 전송 함수
'''
try:
response = client.chat_postMessage(
channel=user_id,
text=message
)
# print(f"메시지가 성공적으로 전송되었습니다: {response['ts']}")
return response['ts']
except SlackApiError as e:
print(f"오류 발생: {e.response['error']}")
def send_reply_to_thread(channel_id, thread_ts, message):
'''
스레드에 댓글 남기는 함수
'''
try:
response = client.chat_postMessage(
channel=channel_id,
text=message,
thread_ts=thread_ts
)
print(f"스레드에 댓글이 성공적으로 전송되었습니다: {response['ts']}")
except SlackApiError as e:
print(f"오류 발생: {e.response['error']}")
def get_bigquery(query):
'''
단순 빅쿼리 조회용
'''
try:
result = bigquery_client.run_query_to_dataframe(query)
print('Big query 조회 성공')
# print(result)
return result
except Exception as e:
print(f'Bigquery 오류 발생 {e}')
def send_bigquery_to_slack(user_id, query):
data = get_bigquery(query)
if data:
for row in data:
message = f'Bigquery 데이터: {row}'
send_message_to_user(user_id,message)
else:
print('전송할 데이터가 없습니다.')
# 메뉴 기반 실행
if __name__ == "__main__":
print("실행할 기능을 선택하세요:")
print("1. 사용자에게 메시지 보내기")
print("2. 스레드에 댓글 달기")
print("3. Bigquery 데이터 조회")
print("4. Bigquery 데이터 사용하여 사용자에게 전송")
choice = input("번호를 입력하세요: ")
if choice == "1":
message = "안녕하세요! Slack Bot에서 보낸 메시지입니다."
send_message_to_user(USER_ID, message)
elif choice == "2":
message = "스레드에 댓글 달기 테스트중입니다."
send_reply_to_thread(CHANNEL_ID, THREAD_TS, message)
elif choice == "3":
query = 'SELECT * FROM geultto.geultto_10th.user_super_mart LIMIT 1;'
get_bigquery(query)
elif choice == "4":
query = 'SELECT * FROM geultto.geultto_10th.user_super_mart LIMIT 1;'
send_bigquery_to_slack(USER_ID, query)
else:
print("잘못된 입력입니다. 다시 실행해주세요.")
2.2. 사전 테스트 진행
최종적으로 우리가 보낼 메세지는 총 639명을 대상으로한 Direct Message이다. 사실 이정도의 많은 사람들에게 메세지를 보내는게 부담이여서 먼저 운영진 34명을 대상으로 설 일주일 전 테스트를 진행했다. 초안의 메세지는 다음과 같고 몇 가지 문제를 발견할 수 있었다.
모바일 기준 메시지, 확실히 개행이 빡빡하다.
컨텐츠 문제
들여쓰기 내여쓰기가 들쭉 날쭉함
요약하여 쓸 수 있는 내용이 서술형으로 작성 됨
1번을 고민해보자면 슬랙은 멀티플랫폼이기 때문에 데스크톱, 모바일에서의 개행이 다르다는 점이다. 특히, 사람들에게 보내는 메세지가 처음 설계에서는 짧지 않았기 때문에 불릿포인트는 들여쓰기가, 줄글은 내어쓰기가 되어 가독성이 좋지 않았다.
2번은 불릿 포인트는 개조식, 그 밖은 서술형인 형식이 무너진 탓이였다. 되도록 불릿 포인트는 요약하여 작성하고 줄글은 문단마다 맨 뒤로 빼는 한편 "인용" Syntax를 이용해서 대화형임을 강조했다. CRM 메시지이기 때문에 사람마다 줄글이 미묘하게 다르기 때문에 이를 맞추려고 말 그대로 메세지를 2시간정도 "깍았다". 밑에 메세지가 최종본인데 나름 깔끔해졌다.(활동요약 개행이 좀 거슬린다.)
정리된 메시지 최종본
두 번째로는 테크적인 관점인데, 운영진 30명 정도에 보내는 정도야 별 문제 없었지만, 639명에게 보내게 되면 꽤나 골치아픈 경우의 수가 떠올랐다.
slack api 의 초당 메세지 limit으로 인한 끊김
누구에게 어떤 메세지를 보냈다는 로그
너무 느린 속도..
1번은 API TOKEN 발급받은 동민님께 확인 받았다. 제일 좋은 tier라고 하셔서 문제는 없을 거라고 하셨고 나중에 확인해본결과 3 Tier 였다. 실제로 보내는 것도 문제가 없었긴 했는데 살짝 아슬한 정도가 아닐까 싶다. 다음에 체크해볼 것!
2번은 혹여나 전송 시 에러가 날 수 있는 상황을 가정한다면, 누구까지 보냈는가가 중요했다. 그래야 받지 못한 사람부터 다시 시작할 수 있기 때문이다. 게다가 CRM 메세지 답게 모든 사람들이 다양한 경우의 수의 메시지를 받을 수 있기 때문에 확인 수단이 필요했다. 그래서 loop안에 받은사람, 메세지 내용, 해당 스레드의 ts식별자등을 저장한 csv파일을 로컬에 저장하는 로직을 추가했다.
#코드 중 일부
#for문
cnt += 1
df_temp = pd.DataFrame({'name':name_list,'text':text_list,'ts':ts_list})
if cnt % 100 == 0:
time.sleep(1)
print(f'{cnt}명, {row[1]}까지 전달완료')
time_record = time.strftime("%Y%m%d_%H%M%S", time.localtime(time.time()))
pd.concat([df_sended,df_temp]).to_csv(f'./history/message_sended_{time_record}.csv', encoding = 'utf-8-sig')
print(f'최종 {cnt}명 전달완료')
end = time.time()
print(round(end - start), '초 소요됨')
3번은 사실 해결못한 부분이기도 했는데, 속도가 너-무 느렸다. 빅쿼리에서 데이터를 불러오는건 그렇다고 쳐도 가져온 데이터를 Pandas DataFrame을 .iterrow()로 접근하여 루프를 돌리는데 운영진 34명에게는 23초였으니 639명은 5분이 넘어갈께 뻔했다. 병렬식으로 보낼 수도 있을 것 같은데 굳이 지금 프로세스에서 바꾸고 싶지 않았다. 잘 작동하니까.. 🤣 그리고 이걸 깨달을 때 쯤이면 이미 보내기로한 due date 직전이였다.
그정도까진 아니긴해 ~
결국 6분에 걸쳐 모든 사람들에게 메시지는 정상적으로 전송되었지만 후문으로는 왜 비동기식으로 하지 않았는가 에 대한 물음이 있었다고 한다 ㅋ_ㅎ (역시 개발자분들 ! 다음엔 그렇게 해볼게요) 사실 비동기식 처리 방법에 대해서 얼마안된 수민님의 글을 읽은 적이 있는데 사람은 망각의 동물이 맞나보다.
설 전날 결국 메시지를 보내 마무리 했다. 배포하는 날 기도하는 심정이 이런걸까? 금요일날 보내기 위해서 점심부터 계속 에러는 없을까 그 짤막한 코드를 보면서 계속 체크해봤다. 점점 쌓이는 지니봇의 DM..
6분은 좀 너무하긴 하네요
정확히 저녁 8시에 보내기 시작한 봇은 무리없이 6분간 순차 전송을 마무리하였고 사람들의 반응도 좋았어서, CRM팀으로서 뿌듯함을 느꼈다.
3. 갑자기 마인크래프트크요?
3.1. 마크 활동 시작
슬랙 봇에 이어지는 이야기인데, 서론은 이렇다. 갑자기 어떤분의 권유로 마인크래프트가 흥하기 시작했다. 그것도 설연휴에. 그렇게 정수님은 GCP을 가입하신 무료 크레딧 $400달러로 서버를 팠고 약 5 일차 정도 재밌게 진행했다. 근데 마인크래프트에서는 채팅기능에 더하여 사람이 접속하는 정보나 업적달성, 죽음 표시 등이 표기된다. 어 이거 데이터잖아? 하는 생각이 들었고 GCP 내부에 접근하면 LLM을 이용해서 인게임에서 마인크래프트 공략 등을 물어보는 세팅도 가능할 것 같아서 정수님께 문의 했다.
그렇다 나는 말벌 아니 데이터 아저씨다..호다닥 🏃♂️
/Logs 파일을 확인해보니 날짜별로 분할되어있는 파일과 최신 log 2가지가 존재했다. 현재 기록들은 latest.log에 실시간으로 적히고 일정 용량이 차면 날짜별로 압축파일 .gz 파일로 저장되는 모양이였다.
Logs 디렉토리의 파일들
여기서 재밌는 생각이 난게 채팅기록을 가지고 있으니 이걸 가지고 3-4일동안 진행된 정보를 요약하면 재밌겠다는 생각이 들었다. 게임채널이지만 시간이 나지 못해 참여 못한 사람들에게 안의 상황을 공유하면서 공감대를 키우고 유입 유도를 하는 한편, 기존 참여자들에겐 정보 요약을 통해 추억을 회상하는 넛지를 줄 수 있을 것 같아서 날짜별로 로그를 이용해서 LLM 에 태웠다.
초기 요약은 깔끔하다
여기서 재밌는 이슈가 있는데, 채팅파일이 워낙 길다보니까 무료버전의 ChatGPT, Claude, 뤼튼(OPEN AI 기반이긴 하지만)에서 모두 텍스트 Length 초과로 거절당했다... 반면 Perplexity 와 Gemini는 토큰이 넉넉한지 요약은 해주더라. 결국 Gemini에게 요약하는 형식을 가지고 3일차 정도 밀어 넣어봤는데 Context Length 이슈로 3일차 정도 뒤에는 자기 멋대로 요약을 하기 시작했다... 아놔. 결국 마음편하게 ChatGPT를 다시 구독했다.
Context가 길어진 후, 응답 구조가 변경되었다.반응 감사해요 ~
좀 더 프롬프트를 깍고 일정하게 형식을 few shot해놓으면 퀄리티가 좋아지겠지만 그렇게 까지 시간을 쓰고 싶지 않았고 날짜도 5일차 정도 밖에 되지 않았기 때문에 간단하게 정리만 했다.
3.2 접속 알림 봇을 만들까?
어쩌다보니 마인크래프트 GCP 서버 관리까지 참여하고 있어서, 이 때를 틈타 CRM에서 했던 연장으로 간단하게 출석 체크 봇을 만들고 싶어졌다. 사실 생존게임이라는게 혼자하기보다는 누구랑 같이 할때 제일 재밌는거라 함께 하는게 젤 중요하다 생각했다. (그리고 2주정도 하다 말꺼니까) 그러던 중 문득 생각난 것이, 마인크래프트가 꽤나 API에 적극적이라, 실제로 인터넷 방송에서는 도네이션을 연결해서 스트리머에게 필요한 물품을 준다던가 갑자기 점프를 시킨다더가 즉사를 시킨다던가 하는 식으로 재미를 더하고 있었던 장면이 생각났다.
위 내용을 조합해서 2번을 만들면 아주 쉽게 다음과 같이 만들 수 있다. 마인크래프트와 파이썬 스크립트의 위치는 동일한 GCP 이기 때문에 Localhost를 이용해서 어렵지 않게 서버정보를 불러올 수 있다. 이렇게 정보를 가져올 수 있다는걸 확인했으니, 이제 슬랙 봇 api를 이용해서 보내기만 하면된다.
import os
import time
import requests
import json
from mcstatus import JavaServer
# 🛠 환경변수에서 Slack Bot Token 불러오기
SLACK_BOT_TOKEN = os.getenv("SLACK_BOT_TOKEN")
if not SLACK_BOT_TOKEN:
raise ValueError("❌ 환경변수 SLACK_BOT_TOKEN이 설정되지 않았습니다.")
# 🛠 Slack 설정
CHANNEL_ID = "C01XXXXXX"
THREAD_TS = "000000.00000" # 기존 메시지 thread_ts 값
# 🛠 마인크래프트 서버 설정
SERVER_IP = "127.0.0.1"
QUERY_PORT = 25565
server = JavaServer.lookup(f"{SERVER_IP}:{QUERY_PORT}")
# 🛠 ID → 이름 매핑 (등록되지 않은 경우 ID 그대로 출력)
player_name_map = {
"ID" : "이름"
}
# 🔄 이전 접속자 목록 저장
previous_players = set()
# 📌 Slack 스레드에 댓글 남기기
def send_slack_reply(message):
url = "https://slack.com/api/chat.postMessage"
headers = {"Authorization": f"Bearer {SLACK_BOT_TOKEN}", "Content-Type": "application/json"}
data = {
"channel": CHANNEL_ID,
"text": message,
"thread_ts": THREAD_TS # 기존 메시지의 스레드에 댓글 작성
}
response = requests.post(url, headers=headers, data=json.dumps(data))
response_data = response.json()
if response_data["ok"]:
print(f"✅ Slack 스레드에 댓글 작성 완료: {message}")
else:
print("❌ Slack 댓글 작성 실패:", response_data)
# 📌 서버 인원 체크 루프 실행
def monitor_minecraft_server():
global previous_players
print("🔄 마인크래프트 서버 접속 감지 시작...")
while True:
try:
# 서버 상태 가져오기
status = server.status()
current_players = set(player.name for player in status.players.sample) if status.players.sample else set()
# 🔹 새로운 플레이어 입장 확인
new_players = current_players - previous_players
left_players = previous_players - current_players
message_list = []
# ✅ 여러 명이 동시에 입장하면 한 번에 메시지 출력
if new_players:
joined_names = [player_name_map.get(player, player) for player in new_players]
joined_message = f"🎉 {', '.join(joined_names)} 님이 입장했습니다!"
message_list.append(joined_message)
# ✅ 여러 명이 동시에 퇴장하면 한 번에 메시지 출력
if left_players:
left_names = [player_name_map.get(player, player) for player in left_players]
left_message = f"👋 {', '.join(left_names)} 님이 퇴장했습니다!"
message_list.append(left_message)
# ✅ 현재 인원도 한 번만 출력
if message_list:
total_players = len(current_players)
message_list.append(f"🔹 현재 접속 인원: {total_players}명")
send_slack_reply("\n".join(message_list))
# 🔹 접속자 목록 업데이트
previous_players = current_players
except Exception as e:
print(f"❌ 서버 정보를 가져오는 중 오류 발생: {e}")
time.sleep(5)
# 🔥 실행
if __name__ == "__main__":
monitor_minecraft_server()
위 코드를 실행하여도 GCP SSH가 닫기면 떨어지기 때문에 screen을 이용해서 백그라운드에서도 돌게 만들었다.
# screen 생성
screen -S mc_slack_bot
# 파이썬 스크립트 실행
source /가상환경명/bin/activate
python monitor_sendmsg.py
# CTL + A , D를 통해 screen 빠져나오기
#스크린 확인(사용자 디렉토리 귀속)
screen -ls
완성!
4. 마무리
Slack API가 잘 되어있고 GCP같은 클라우드 환경도 언제든지 쉽게 접속할 수 있는 환경이라 쉽게 배웠다. 더불어 옆에 있는 개발자분들에게 물어가면서 배워가는 점도 한 몫하고, 아이디어만 있다면 ChatGPT 를 이용해서 코드 구현부분을 외주 줄 수 있다는 점도 좋은 것 같다. 최적화나 설계 부분에 대해서는 여전히 CS적인 지식이 필요하지만 짧게 짧게 Prototype을 만들고 구현하는 경험이 재미있었다. 예전에는 프로그램을 만들기 위해서 가능한 기능성이 높은 구조를 높게 잡고 하다보니 지치거나 포기하는 경우가 많았는데 이제 경험이 쌓이다보니 작은 단위로 Divide(&Validation) and Conquer 를 하는 개발도 익숙해진 것 같다. 남은 글또 기간이나 내 일에서도 SaaS 활용과 엔지니어링을 지속적으로 키워나가면 확실한 무기가 될 수 있을 것 같다.
흔히 데이터 분석가라는 공고의 많은 부분이 프로덕트 분석을 지향하고 A/B test 의 지식과 경험을 요합니다. 도대체 A/B test가 무엇이길래 이렇게 신봉되는 걸까요? 연구의 큰 종류인 관찰연구와 실험연구의 구분와 실험연구의 대표적인 방법인 RCT 그리고 온라인 환경에 이식된 온라인통제실험(OCE)의 흐름을 살펴보고 A/B test가 왜 중요해졌는지 알아보겠습니다.
1. 태초에 관찰과 실험이 있노라: 관찰연구
Grimes, D. A., & Schulz, K. F. (2002). An overview of clinical research: the lay of the land. The Lancet, 359(9300), 57–61. doi:10.1016/s0140-6736(02)07283-5
분석이라는 관점에서 개입유무에 따라 관찰연구(Observational Study)와 실험연구(Experimental Study) 가 존재합니다. 대부분의 분석은 집계로 부터 시작하고, SQL과 같은 쿼리문은 Descriptive하게 정보를 보는 것이 중점을 둡니다. 물론 그 과정에서 비즈니스에 적용할 수 있는 지표나 잘 정의된 문제를 보는 입장에서는 이만한 기술이 없죠. 덜 논쟁적(중요..)이고 우리는 그 정보를 가지고 어떻게 행동(Action)해야하는지 고민하면 됩니다. 집계된 데이터에 관심을 가지지 왜 이렇게 나왔는지에 대해서 논쟁하진 않거든요. 심플하고 간단하고 이해하기 쉬우니까요. 하지만 분석의 본질은 단순 집계만 머물지 않습니다.
우리는 관찰하고 인과관계를 밝혀내길 희망합니다. 관찰연구는 연구자 혹은 분석가가 직접적으로 개입하지 않고 자연스럽게 발생하는 데이터를 관찰하여 분석하는 연구 설계라고 하며, 피험자 혹은 연구 대상자에게 위해가 아주 적습니다. 흡연자와 건강 상태를 비교하는 연구, 웹사이트에서 사용자들이 자발적으로 클릭한 광고의 효과를 분석하는 경우가 해당됩니다. 이 관찰연구는 이미 주어진 데이터로 기술통계를 내거나 확인하는 연구이므로 이미 있는 데이터를 후향적(respective)으로 연구한다는 특징을 가지고 있습니다. 하지만 우리는 어떤 변화를 일으켜서 그 영향을 비교하고 싶은 것에 관심이 더 많습니다. "여기 버튼을 우측 하단으로 옮기면 어떨까?" , "만약 할인 쿠폰을 발행한다면 얼만큼 반응할까" 와 같은 관점 말입니다. 예를 들어 당근마켓의 사례를 보죠. 뱃지 획득 유무에 따른 잔존율의 상관관계를 밝혀 냈다고 합시다. 뱃지를 획득한 사람은 잔존율이 높아! 굉장히 합리적으로 들리는데요.
출처: Choi Bokyung, Medium, Simpson’s Paradox and Confounding
하지만 현실은 그렇게 녹록하지 않습니다. 상관관계를 방해하는 녀석이 있는데 이를 교란변수(Counfounding)이라고 합니다. 이게 모든 관찰연구의 한계의 근본인 녀석입니다. 아주 흉악한 녀석이죠. 우리는 교란변수를 제어(Control)하고 싶습니다. 하지만 두가지 문제가 있습니다.
먼저 교란변수는 측정되지 않는 경우가 많습니다. 회원가입할 때 성별, 나이, 전화번호부 등 마케팅 동의 다 하시나요? 애초에 데이터가 존재하지 않으면 제어하는 것이 어렵습니다. 두 번째 문제는 인과관계는 여러가지 요소가 뒤엉켜 껴안고 있어서 이거를 생선 살 발라내듯 발라내는 것이 어렵습니다. 이를 내생성 문제(Endogeneity problem)라고 합니다.
2. 교란변수 극복하기 - 인과추론(Casual Inference)
인과추론은 이 내생성 문제를 어떻게든 해결할려고 뜯어말리는 시어머니와 같습니다. 말 그대로 X-Y의 인과관계를 밝혀내는 방법입니다만, 관찰된 데이터를 가지고 교란변수를 통제하는 방법론(Ex. PS 매칭, 차분법) 등이 필수적으로 요구됩니다.인과추론은 멋진 이상향을 보여주지만 어려운 학문입니다.
그럼 교란변수에 우리는 손 들고 항복을 선언해야할까요? 그렇지 않습니다. 우리 잘생긴 피셔형님이 좋은 대책을 가져왔습니다. 바로 실험연구 중 무작위 대조실험입니다. 실험연구는 연구가 실험 대상을 처치에 따라 두 그룹으로 배정하여 결과를 비교하는 연구이며, 실험연구가 중요한 이유는 모든 변수를 통제(Control)하고 우리가 원하는 개입(Intervention)에 따른 영향을 분석할 수 있기 때문입니다. 임상시험은 대표적인 실험연구이자 무작위 대조 실험의 한 예시이며, 이를 차용하여 온라인에 적용하면 온라인 통제 실험(Online Controlled Experiment, OCE)라고 합니다.
임상시험에서 고혈압 약제를 투여한 그룹과 플라시보(Placebo)를 투여한 그룹 간의 혈압 개선 연구처럼 의학적 분야에서는 RCT(Random Clinical Trial) 라고 불리우며, 웹사이트 A/B 테스트에서 두 가지 페이지를 무작위로 보여주고 클릭율을 비교하는 연구는 OCE의 예시 라고 할 수 있겠습니다. 위 실험설계는 모두 나머지 모든 변수를 통제(Contol)하고 처치(독립변수)로 인한 결과(종속변수)에 관심이 있고 데이터를 전향적(Prospective)하게 수집하여 연구한다느 특징을 가지고 있습니다. 종합적으로 정리하자면 다음과 같습니다.
관찰연구
RCT(실험연구)
개입 여부
연구자가 직접 개입하지 않고 자연스러운 데이터 관찰
대조군과 실험군을 무작위로 할당 실험군에 개입
인과 관계 규명
어려움
가능
교란 변수 통제
교란변수 어려움, PSM 등을 통한 제어 방법 도입 必
무작위할당으로 교란변수를 통제 가능
윤리 문제
거의 없음
발생할 수 있음 (Ex 신약 개발)
실행 비용
비용이 적고 시간이 덜 소요
시간과 비용이 많이 듬
적용 예시
흡연이 건강에 미치는 영향
임상시험, 온라인 통제실험
4. 마무리
인과추론과 기존에 배웠던 지식을 합치면서 자연스럽게 Product Anlaysis 분야가 A/B test 집착하는 이유도 이해가 잘가게되었습니다. 실제로 저는 임상시험은 아니지만 공학대학에서 약 3년간 실험연구만 전공으로 했었기 때문에 이런 정리가 좀 더 와닿는 면이 있었습니다. 근본적으로 과학은 통제가능한 실험을 좋아하고 그 분야에 리소스를 많이 투여할 수 밖에 없는그만의 매력이 있는 것 같습니다.
A/B test는 아마 데이터 직무 분야에서 Gold Standard로 정말 오랫동안 영속될 것 같은 느낌이 듭니다. 반면 이런 OCE를 못하는 환경에서의 대안으로 인과추론이 역시 부상할 것이라는 것을 더 마음에 와닿게 되었습니다. 지식의 상아탑을 올라가려면 아마 꽤 오랜시간 저를 괴롭힐 것 같은데, 이미 한번 스터디를 하면서 완벽하게 이해하지 못했기 때문입니다. 국내에는 인과추론의 데이터과학 Youtube와 가짜연구소의 인과추론 스터디가 좋은 레퍼런스를 남겨주고 있는데, 더더 좋은 자료가 나올 것이라 믿고 확인하며 실업무에서 도입 사례에 대해서 연구해볼 가치가 있을 것 같네요. 그럼 다음에 깨달음의 언덕에 다다를때 쯤 다시 한번 포스팅 해보겠습니다. https://www.youtube.com/@causaldatascience
이번 글에서는 A/B 테스트를 비롯한 데이터 과학에서 자주 사용되는 신뢰구간이 등장한 이유를 알아봅니다. 또한, 신뢰구간의 t-분포 기반 방법과 부트스트래핑 기법을 비교하여 설명합니다. 부트스트래핑은 컴퓨터 자원을 활용한 현대적 방법으로, 데이터 과학에서 왜 중요한지를 알아봅시다.
1. 글목차
점추정의 한계와 구간 추정의 필요성
신뢰구간의 등장
현재 데이터과학에서 부트스트래핑의 중요성
2. 본문
2.1. 점추정의 한계와 구간 추정의 필요성
통계학의 기본은 모집단을 알아내는 방법론입니다. 하지만 모집단에 대한 전수조사가 불가능에 가깝기 때문에 표본을 가지고 모집단에 대한 특징 평균,표준편차를 구하는 것이 추론통계의 기초라고 하겠습니다 . 표본데이터로 모평균은 쉽게 구할 수 있는 법칙이 있는데 Law of Large Number 라는 대수의 법칙으로 표본 평균은 표본의 크기가 증가할수록 모평균에 수렴하는 경향을 나타낸다는 법칙입니다. 다음은 대수의 법칙을 확인하기 위하여 동전 뒤집기를 예시를 10000번 수행해가면서 평균이 0.5로 수렴하는 그래프입니다.
import random
import matplotlib.pyplot as plt
def law_of_large_numbers(n_trials):
heads_counts = []
proportions = []
for i in range(1, n_trials + 1):
flip_results = [random.randint(0, 1) for _ in range(i)] # 0: tails, 1: heads
heads_count = sum(flip_results)
heads_counts.append(heads_count)
proportions.append(heads_count / i)
plt.figure(figsize=(10, 6))
plt.plot(range(1, n_trials + 1), proportions)
plt.axhline(y=0.5, color='r', linestyle='--', label='Expected Proportion (0.5)')
plt.xlabel('Number of Trials')
plt.ylabel('Proportion of Heads')
plt.title('Law of Large Numbers Demonstration (Coin Flips)')
plt.legend()
plt.grid(True)
plt.show()
law_of_large_numbers(10000)
그런데 단순히 표본평균이 같다는 이유로 같은 분포라고 할 수 있을까? 라는게 구간 추정의 등장의 이유입니다. 데이터의 산포도(분산이나 표준편차)가 다르다면, 평균만으로는 데이터를 완전히 설명하기 어렵기 때문입니다. 구간 추정은 이러한 불확실성을 포함하여 데이터를 표현하는 방법론으로 등장하게 되었습니다. 다음은 두 평균이 비슷한 두 데이터가 산포도가 다른 예시입니다.
그렇다면 점추정된 평균을 단순히 제시하는 것보다 구간을 포함하여 표현하는게 더 Data Scientific한 방법론이라고 할 수 있겠습다. 그러면 구간을 잘 표현한다는 것은 무엇일까요? 무작위로 선택된 30명의 남성의 평균 키가 170이라고 해봅시다. 표본평균을 구했으니 구간을 포함하여 설명할 때, 남성은 0 cm ~ 200cm에 반드시 존재합니다. 라고한다면 이는 별로 좋은 표현방법이 아닐 것입니다. 따라서, 좀 더 "합리적인" 표현방법이 필요하게 되었습니다.
2.2. 신뢰구간의 등장
신뢰구간은 정규분포를 기반으로 계산되며, 이를 위해 Z-점수(Z-score)를 사용합니다. 전체 데이터의 95%정도만 설명한다고 하면 표준정규분포 Z score 를 기준으로 +- 1.96의 값에 해당하는 수치를 이용해서 신뢰구간 추정이 가능합니다.
만약 모표준편차를 안다면 다음식으로
모 표준편차를 모른다면 표본 표준편차를 가지고 대체하여 계산합니다.
코드로 구현하자면 다음과 같습니다.
import numpy as np
from scipy import stats
korean_men_heights = [
170, 172, 174, 168, 176, 175, 169, 171, 173, 177,
165, 180, 178, 174, 172, 173, 169, 168, 176, 175
]
# 표본 평균구하기
sample_mean = np.mean(korean_men_heights)
sample_std = np.std(korean_men_heights, ddof=1) # ddof=1 for sample std
# 데이터사이즈
n = len(korean_men_heights)
# 표준오차 구하기
standard_error = sample_std / np.sqrt(n)
# 95% 신뢰구간 구하기
confidence_level = 0.95
t_critical = stats.t.ppf((1 + confidence_level) / 2, df=n-1) # t critical value
margin_of_error = t_critical * standard_error
confidence_interval = (sample_mean - margin_of_error, sample_mean + margin_of_error)
# Print results
print("Point and Interval Estimation for Korean Men's Height:")
print(f"Sample Mean: {sample_mean:.2f} cm")
print(f"Sample Standard Deviation: {sample_std:.2f} cm")
print(f"Sample Size: {n}")
print(f"Standard Error: {standard_error:.2f} cm")
print(f"Margin of Error: {margin_of_error:.2f} cm")
print(f"95% Confidence Interval: ({confidence_interval[0]:.2f}, {confidence_interval[1]:.2f})")
'''
Point and Interval Estimation for Korean Men's Height:
Sample Mean: 172.75 cm
Sample Standard Deviation: 3.82 cm
Sample Size: 20
Standard Error: 0.85 cm
Margin of Error: 1.79 cm
95% Confidence Interval: (170.96, 174.54)
'''
결과적으로 신뢰구간 95%라는 뜻은 전체 모집단에서 100번 추출을 수행할 때 해당 통계량은 95번 신뢰구간에 들어온다는 뜻이다. 신뢰구간은 편리했지만 가정이 필요했습니다. 바로, 표본 데이터가 정규분포를 따르거나 충분히 큰 표본을 가지고 있어야만 계산이 가능하다는 가정이 필요했습니다. 이는 컴퓨터가 등장하기 이전의 상황이라고 할 수 있습니다. 컴퓨터파워가 높아진 현재는 부트스트래핑방법을 이용하여 신뢰구간을 구하기 시작했습니다.부트스태립(bootstrap)이란 '외부의 도움 없이 스스로'라는 사전적인 정의를 가진 방법론으로 표본 데이터에서 복원추출을 통해서 데이터셋을 생성하여 마치 모집단에 있을법 하지만 새로운 데이터셋을 만드는 방법론입니다. 대표적으로 Random Forest는 이 부트스트랩에 집계를 더한 배깅 방법으로 고안되었습니다.
자기의 일을 스스로하자!
절차는 다음과 같습니다.
데이터에서 복원추출 방식으로 크기 n인 표본을 뽑는다.(재표본추출)
재표본 추출한 표본에 대해서 평균을 구한다.
1~2번을 10000번 반복한다.
95% 신뢰구간을 구하기 위하여 10000개의 표본결과 분포 양쪽 끝 2.5%, 97.5%의 백분율을 구한다.
# 평균과 표준편차
sample_mean = np.mean(korean_men_heights)
sample_std = np.std(korean_men_heights, ddof=1) # ddof=1 for sample std
# 부트스트래핑 파라미터 정하기
n = len(korean_men_heights)
n_bootstraps = 10000
bootstrap_means = []
#부트스트래핑 시행
np.random.seed(42)
for _ in range(n_bootstraps):
bootstrap_sample = np.random.choice(korean_men_heights, size=n, replace=True)
bootstrap_means.append(np.mean(bootstrap_sample))
# 부트스트래핑으로 신뢰구간 구하기
confidence_level = 0.95
lower_bound = np.percentile(bootstrap_means, (1 - confidence_level) / 2 * 100) # 2.5%
upper_bound = np.percentile(bootstrap_means, (1 + confidence_level) / 2 * 100) # 97.5%
bootstrap_confidence_interval = (lower_bound, upper_bound)
# 결과출력
print("Bootstrap Confidence Interval for Korean Men's Height:")
print(f"Sample Mean: {sample_mean:.2f} cm")
print(f"Sample Standard Deviation: {sample_std:.2f} cm")
print(f"95% Confidence Interval (Bootstrap): ({bootstrap_confidence_interval[0]:.2f}, {bootstrap_confidence_interval[1]:.2f})")
'''
Bootstrap Confidence Interval for Korean Men's Height:
Sample Mean: 172.75 cm
Sample Standard Deviation: 3.82 cm
95% Confidence Interval (Bootstrap): (171.15, 174.35)
'''
t분포를 이용한 값과 부트스트래핑을 이용한 방법이 유사한 결과를 나타냄을 볼 수 있습니다.
2.3. 현대 데이터과학에서 부트스트래핑의 중요성
Product analyst 의 방법론 중 A/B test는 부트스트래핑의 혜택을 받은 방법 중 하나이다. 일반적으로 A/B test는 두 웹페이지의 방문 비율의 차이 등을 검정하는 경우가 많은데 비율의 차이가 항상 정규분포를 따르는 것은 아니며, 대부분 비대칭이거나 이항분포를 따르는 경우가 많습니다. 이처럼, 부트스트래핑은 데이터의 분포에 관계없이 적용가능하며, 복잡한 통계량에도 유연하게 사용할 수 있다는 점이 장점입니다. 여담으로 Seaborn 의 Error bar 튜토리얼을 보면 신뢰구간(c.i.)를 부트스트래핑으로 계산된다는 걸 확인할 수 있습니다.
통계에 관련된 책이나 분석방법을 찾다보면 자연스럽게 선형대수학에 대한 개념이 나오게 된다. 이번에는 khan Academy와 개발자를 위한 선형대수학 책을 병행학습하며 기초에 대해서 정리해보고자 한다.
1. 글목차
기본개념
데이터분석의 활용 분야
벡터
2. 본문
2.1. 기본 개념
세상에는 두가지 값이 존재한다. 크기만 존재하는 값을 의미하는 스칼라(Scala), 크기와 방향이 존재하는 벡터(Vector). 이과 전공으로 물리학에서 자주 등장하는 힘에 관련된 표기를 흔히 벡터로 표기하기 때문에 익숙하다.
선형대수학(Linear Algebra)는 이처럼 벡터와 행렬, 선형 변환과 같은 수학적인 구조를 다루는 학문이다. 통계학과에서는 2학년의 전공 필수 과목이다.
서울대학교의 통계학 전공 로드맵
선형은 직선처럼 행동하는 성질을 뜻하며, 벡터와 행렬 연산에서 입력이 변할 때 비례적으로 변하는 특성을 가짐을 의미한다. 반면 대수는 원소들의 연산 규칙을 이용하여 복잡한 수학구조를 다룰 수 있게한다. 선형대수에서는 벡터 공간안에서 행렬, 선형 변환과 같은 대수적 구조를 연구하는 학문이다.
벡터(Vector)는 크기와 방향을 갖는 수학적 개체로 n-차원 공간에 한 점을 나타나는 배열이다.
행렬(Matrix)은 숫자들을 직사각형 모양의 배열로 나타낸 수학 구조이며, 벡터의 선형변환이나 여러 방정식을 하나의 시스템으로 묶어 처리하는데 사용된다. 기하학적으로는 벡터의 선형 변환을 적용하는 도구이다.
벡터과 행렬의 연산은 다른 벡터를 생성한다.
선형 변환(Linear Transformation)은 벡터나 행렬을 변환하는 함수로, 공간에서 점들을 다른 위치로 이동시키는 방법이다.
2.2. 활용 분야
데이터는 벡터와 행렬로 표현되므로데이터분석을 하는 툴의 기본은 이런 선형대수의 이론을 기반으로 한다.
선형 회귀 분석은 가장 기본적인 예측 모델 중 하나라 선형 방정식을 기반으로 한다.
$\hat{y} = X \beta $
차원축소방법 중 하나인 주성분 분석(PCA)은 행렬의 고유값과 고유벡터의 계산을 통해 데이터의 주된 변동을 설명하는 축을 찾아내어 데이터의 차원을 줄인다. 해당 내용은 데이터과학을 위한 통계 스터디에 정리한 바 있다.
추천시스템(Recommendation System)은 행렬 분해(Matrix Factorization) 방법을 사용하여 사용자와 아이템 간이 상호작용 행렬을 분해아여 숨겨진 패턴을 발견한다. 예를들어 특이값 분해(Sigular Value Decomposition)은 추천 시스템에서 자주 사용 되는 기법이다.
클러스터링 및 분류: K-means 클러스터링과 같은 군집화는 벡터 간의 유사도를 계산하기 위하여 유클리드 거리 혹은 코사인 거리가 사용된다. 이때 연산이 선형대수 기반이다.
2.3. 벡터(vector)
표기법: 일반적으로 진한 로마자 $v$ 혹은 이탤릭체 $v$, 위쪽에 화살표 $\overrightarrow{v}$를 붙여 표기한다.
차원(dimensionality): 벡터가 가진 원소의 수. 차원은 일반적으로 $\mathbb{R}$ 으로 나타내는데 $\mathbb{R}^2$ 와 같이 표현하면 실수(Real Number) 2차원 평면(흔히 말하는 x,y축)에 존재한다는 것이다.
방향(orientation) : 열방향 혹은 행방향으로 나타낸다.
벡터는 시작점이 일반적으로 원점을 기준으로 표기하며, 방향과 크기만 같다면 모두 같은 벡터이다.
방향이 존재하기 때문에 벡터는 기하학적 공간에 표기하여 설명을 할 수 있는 장점이 있다. 대표적으로 벡터의 덧셈과 뺄쌤은 다음과 같이 표기한다.
백터의 덧셈벡터의 뺄샘
크기(norm): 는 벡터의 꼬리부터 머리까지의 거리이며 일반적으로 유클리드 공식에 따라 구한다. 크기는 양쪽에 | 2개로 표기한다.
단위 벡터: 길이가 1인 벡터로 어떤 벡터든 크기기로 나누면 길이를 1로 제한하여 만들 수 있다.
$ \left\|v \right\| =1 $
벡터의 내적(dot product)
벡터의 내적은 점곰(dot product) 혹은 스칼라 곱이라고도 표현하며, 동일한 차원의 두 벡터사이에서 수행할 수 있다. 예를 들면 [1 2] $\cdot $ [1, 3 ] = 1*1 + 2*3 = 7 이 된다. 내적한 값은 방향이 존재하지 않은 스칼라 값이 된다. 시그마 표기법을 이용한 공식은 다음과 같다.
import numpy as np
v = np.array([1,2,3])
w = np.array([1,4,5])
np.cross(v, w.T)
# array([-2, -2, 2])
외적의 기하학적 해석
외적은 3차원 공간에서 두 벡터가 만들어 내는 평면의 법선 벡터이다
직교 벡터 분해
행렬의 특징 중 하나는 분해를 통해 행렬에 숨겨진 정보를 찾을 수 있다는 점이다. 마치 실수는 정수와 소수점 부분으로 나눌 수 있고, 수는 소인수 분해를 통해서 소수로 나눌 수 있는 것 처럼
ex) 4.27 -> 4 + 0.27
ex2) 16 -> $ 2^4$
원리: 두 벡터가 있을 때, 한 벡터를 다른 벡터에 직교하는 벡터를 만들려면 어떻게 해야할까? 아래 $ \overrightarrow{b}$ 의 벡터를 $ \overrightarrow{a} $ 에 직교하도록 하려면, 상수배 ($\beta$)를 한 $ \overrightarrow{a}$를 만들고 두 벡터의 차이를 구하면 된다. 두 벡터의 차이가 직교하면 내적이 0 이되므로 다음 식이 성립한다.
$ a^{T}(b-\beta{a}) = 0 $
$\beta$를 구하기 위하여 내적이 분배 법칙이 가능하다는 점을 응용한다면 다음 식에 도달하게 된다.
$ a^{T}b - \beta a^{T}a = 0 $
$ \beta a^{T}a = a^{T}b $
$ \therefore \beta = \frac{a^{T}b}{a^{T}a} $
위식 $\beta$는 선형회귀의 $\beta$와 같은 기호이다. 위 식은 벡터 b를 a에 투영하여 그 투영 성분을 제거해 a와 직교하는 방법만 남기는 방법이다. 직교한다는 것은 기존 데이터(a벡터)를 설명하지 못하는 오차(잔차)를 말하며 바로 $ a^{T}b - \beta a^{T}a$ 벡터가 잔차 성분이다. 이처럼 행렬분해와 직교화는 최소제곱법, 통계학, 머신러닝에 걸쳐 이루는 많은 응용의 기반이 된다.
9월 2일부터 2달간 진행된 인과추론 스터디에 대한 후기를 남깁니다. 작년에 워낙 핫한 분야이기도 하였고, 마냥 쉽지 않을거라 생각했지만 이때 아니면 언제 공부해보겠는가라는 생각으로 진행했습니다. 매주 Chapter를 학습하고 개념들을 정리하면서 블로깅까지 마무리를 했습니다.
1. Keep
블로그에 Latex문법을 쓰면서 꽤나 속도가 늘었습니다. 이제는 손으로 쓰는것보다 Latex문법이 편할 정도입니다. Notion 역시 Latex 문법을 지원하기 때문에 공부 자료를 정리하기가 매우 좋아져서 만족합니다.
책에 함축된 개념들이 많아서 한 번이 아닌 이틀에 걸쳐서 반복해서 읽고 데이터의 인과추론 유튜브를 보면서 복습을 했습니다. 모호하던 개념들이 정리가 되는 점이 좋았습니다.
스터디 참여원들에게 알려주면서 스스로 깨닫게 되는 공부방식도 좋았던 것 같습니다.
2. Problem
난이도 자체가 높기 때문에, 일주일에 5-6시간 투자로는 밀도있는 읽기가 쉽지 않았습니다. 특히 통계학 학부 과정에서 당연히 알고가는 필수 개념들(ex 선형대수, 결합확률 등)을 당연히 알고 전개하는 책이라 이해하는데 시간소모를 쓰게 되었습니다.
자연스럽게 학습을 하는데 시간을 많이 쓰다보니, 놓치는 개념이나 실예제에서 어떻게 쓸지에 대한 이해도 멀어져가는 부분이 있었습니다.
3. Try
지식의 밀도에 따라 시간배분을 해야할 필요가 있겠습니다. 같은 개념을 이해할 때 다양한 소스 혹은 컨텐츠로 이해할 필요가 있다고 느꼈습니다.
돌아돌아 기본 통계 개념와 파생되는 개념에 대한 통달이 아직 되지 않은 것 같아 보완이 필요하네요. 기본서를 다시 들쳐봐야겠습니다.
종합적으로 볼 때, 할 수있는만큼 공부했고 더 나아가기 위해서 Chapter 5에서 멈추고 선형대수부터 다시 공부해볼 생각입니다. 아마 인과추론은 데이터분석분야에서 계속 저를 괴롭힐 것 같은 생각이 드네요 😂
9시 30분부터는 데이터를 확인하면서부터 문제를 대략 유추할 수 있었습니다. 예를 들어 ts라는 데이터 이름이 존재해서 시계열 문제가 나오는구나 체크하고 시계열 관련된 코드를 준비했습니다. 다른 데이터도 미리 불러오고 관련된 라이브러리도 사전에 import 하여 준비하는 것이 좋겠습니다. 어쨋든 최종적으로 워드로 작성 제출해야하기 때문에, 가독성 높은 코드는 나의 논리를 전달하기 좋으니까요~
2. 머신러닝 단원
문제 1,2,3번
머신러닝의 1~3번 문제는 1개의 데이터로 진행했고, 간염의 진행상황(0,1,2,3)를 종속변수로 하고 각종 간질환 지표를 독립변수로 하는 데이터였습니다. 다음 데이터셋과 비슷하나 독립변수가 모두 전처리된 연속형 변수 였습니다.
1-1)결측치 처리 방안 2개 제시하고, 통계량으로 비교하고, 어떤걸 사용하는게 좋을지 제시해라 1-2)이상치 판단하고, 처리해야할지 판단해서 제시하라 1-3) 위의 처리를 마친 데이터로 EDA를 실행하는데, 종속변수의 데이터 값을 기준으로 독립변수들이 다름을 확인해라 1-4)상관행렬 구하고, 상관행렬을 주성분분석하여 새로운 설명변수를 만드는데 사용할 수 있는지 판단하여 제시해라
결측치 방안을 단순대치, 알고리즘 대치 등으로 작성할 수 있었는데, 시간이 너무 없어서 단순대치만 적어야 했던게 너무 아쉽습니다... 주성분분석의 경우 해석력을 잃어버리는 단점이 존재하니까 그만큼 가치가 있는지 보기 위해서 상관관계를 확인해보면 됩니다. 만약 다중공선성 등이 발견되면 적용하면 됩니다.
2-1)클래스 불균형이 존재하는데 해결할 수 있는 방안 2가지를 제시 2-2)클래스 불균형 상황에서 분류모델의 성능을 평가하기 위한 방안 3가지를 제시(10점) 2-3)위에서 제시한 2가지 불균형 처리 방법을 사용하고, 3가지 평가지표로 결과를 평가 *행 넘버의 5의 배수로 train과 test 셋 분리 *종속변수가 0,1,2,3 4개 값을 가지는데, 0과 1,2,3의 2가지 범주로 나누어서 모델 학습 및 평가 진행
2-2 클래스 불균형문제는 10점 배점이였고 아마 방안 1개당 3점, 3개 다 적으면 10점이 되는 형식이였나 봅니다. f1_score가 precision/recall의 개념을 담고 있기에 따로 뭐가 있나 하고 고민하던 중 시간이 없어서 추가로 기입을 못했습니다 ㅠㅠ
3-1) 1에서 전처리한 데이터를 바탕으로 종속변수에서 0을 제외한 1,2,3의 데이터만 가지고 학습 및 평가 진행해라. 이 때 모델은 3개 사용. *동일하게 행 넘버의 5의 배수로 train과 test 셋 분리 3-2) 위에서 학습한 모델과 평가지표를 바탕으로 간염여부를 판단할 수 있는 지표? 정보?를 제시해라
3-2) 문제에서는 변수 중요도라는 명확한 워딩이 있던걸로 기억하는데, 아마 회귀분석이면 $/beta$ 값, 트리모델이면 Featrure importance 기준으로 나열했으면 되었을 것 같습니다.
문제 4번
두 개의 데이터를 주어주고 각각 전처리하여 조인 하는 문제였습니다. 32회 기출의 발전기와 시간에 따른 출력량이 pivot 되어있는 테이블이여서 전처리에는 크게 어렵진 않았습니다만... 중간에 함정이..
4) 지하철 이용객, 기상 데이터(aws.csv) - melt 함수 사용 - 강수량에 대해 0으로 결측치 대치 - 기초통계량 확인하여 제시 - 토,일에 해당하는 데이터 weeday 1로, 나머지 0으로 *(weekday의 단어 뜻을 모르나..? 왜 이런 변수명이..) - 두 개 csv merge
1)user 컬럼과 기후를 나타내는 컬럼들의 상관관계를 분석하고, 주말여부에 따른 user 변수의 차이를 검증하라.
2)선형 회귀모델과 머신러닝 모델을 사용하여 user 변수를 예측, 평가하라.
전처리 요구사항만 5줄인가 되어서 시간을 30분정도 썼던 걸로 기억합니다. 정말 화나는건 기상 데이터는 날짜별 시간대별 강수량, 습도 등이 적혀있는 데이터인데 2021-05-05 13시에 해당 데이터가 없어서 merge하는 과정에서 생긴 결측치가 있었습니다. 혹시 코드 수행을 잘 못했는지 디버깅 하다가 시간을 20분 추가로 날렸습니다... 크나큰 패착... inner 조인을 해야했다 생각이 들기도 하지만, 어쨋든 지하철 이용객이 Main Table이니 Left join한 건 맞다고 봅니다.
이게 악질적으로 느껴졌던건 기상데이터는 몇 만행이고 실제로 중간에 결측치라고 표현되지도 않습니다. 다음과 같이 표현되기 때문이죠.
date
humaditiy
2021-05-05 11:00
20
2021-05-05 12:00
11
2021-05-05 14:00
12
애초에 date가 날라가 있기 때문에 데이터 자체로만 보면 결측치인지 알 수 가 없습니다.. date를 시작날짜와 끝 날짜로 dummy table을 만들어서 공란이 있는지 확인을 해볼 수 도 있을 것 같지만.. 시간이 이렇게 부족한 시험에서 이렇게 만들었어야 했나 싶습니다.
3. 통계분석
문제 5번
데이터: A,B 공장의 수율 데이터(defective.csv) - 불량률(100-수율)에 대해 검정진행이므로 새로운 컬럼 생성 필요 - (A B 공장간 불량률) 차이 평균 표준편차 - 차이 검정에 대한 귀무/대립가설 제시하고 검정 진행해라.
간단한 2그룹 수치형 검정에 대한 문제입니다. 데이터가 600개가 넘었던걸로 기억하는데, 정작 kolmogorov 검정을 해보니 p-value가 0.000 이하로 낮게 나와서 맨-휘트니 검정으로 진행하고 차이가 있다고 결론 지었습니다. 정말 이상했던 건, 이단 이 통계 검정 뿐 아니라 다른 2그룹 연속형 수치 검정 문제들이 p_value가 매우 다들 낮게 나와서 제가 잘못했나 다시 확인하기도 했습니다.
문제 6번
데이터: 기차연착(delay.csv) 데이터 호선 4개 주어지고, 연착횟수(5분, 10분, 15분, 20분, 25분? 5분 단위 6개 컬럼) 1)지하철 노선 1번 데이터의 연착횟수가 포아송 따르는지 검정하는 2가지 방법 제시 2)시간에 따른 연착횟수를 3개 컬럼으로 재처리(10분미만, 10~20분? 20분 초과?)하고, 지하철 노선 별 연착횟수가 차이가 있는지 검정 진행(분할표 만들고, 2개 Column씩 합친 다음 카이제곱 검정 진행)
결과적으로 노션별 연착 구분별 crosstab을 만들고 카이제곱을 진행하면 되는 쉬운 문제입니다.
문제 7번
- 데이터: 단일컬럼 시계열(ts.csv) - 컬럼명 없이 데이터만 존재 했음 - 1) acf, pacf의 개념을 설명하고 모델을 적합해라 - 2)Acf pacf를 그리고, 해당 Plot data를 기반으로 ARMA(p,q)모형 제시
여기서 또 마음에 안드는게 단일 시계열 데이터인데 컬럼명이 안 적혀있어서 단순히 pd.read_csv 불러오면 첫 번째 데이터가 컬럼명으로 들어옵니다. 물론 header = None을 넣으면 되지만 이렇게 일관성 없게 데이터를 줘야만 했나 생각이듭니다. 문제 자체는 쉬워서 pmarima로 자동 적합을하고 pacf, acf 그래프를 그려서 해석을 붙였습니다.
4. 마무리
선형계획법과 같은 문제는 나오지 않고 전처리의 비중이 다소 크고 통계문제는 평이했던 것 같습니다. 하지만 시간이 너무나도 부족하기 때문에 9시 30분~10시에 주어지는 데이터와 jupyter notebook 환경에 미리 세팅해놓는게 좋겠습니다. 또한, 머신러닝 문제는 1번 ~ 3번 과 같이 묶여서 진행 되기 때문에 재사용 가능한 함수형으로 모델을 구축해놓는게 좋겠습니다.문제의 모든 부분이 코딩으로 수행해야하는 것은 아니며, "데이터 불균형에 적합한 평가지표를 작성하라", "ACF와 PACF 정의를 작성해라" 등의 텍스트로 수행할 수 있는 문제도 있으니 문제를 받으면 해당 문제의 답은 미리 작성하는게 좋겠습니다.
전반적으로 뭔가 문제를 고찰하고 다양한 실험을 해볼 수 있는 시험은 아니라는 걸 깨달았고, 그때 그때 생각나는 답변을 적어서 최소한의 점수를 확보하는게 유효할 거라 생각됩니다. 나름 선방할 수 있는 기회였는데 많이 아쉽네요 ㅠㅠ . 결과를 지켜봐야겠습니다.