최근 Airflow 이 3.0으로 판올림 되었습니다. Web UI도 세부 탭으로 변경되고 유틸적인 부분이 더 늘어났습니다.  docker를 이용하여 airlfow 3 설치도 해보고 실습 예제도 구성해볼겸 다음날 온도데이터를 supabase(PostgresSQL) 에 저장하는 예제를 작성해보겠습니다. airflow, 기상 API, discord, supabase 연결을 각각 테스트 해보고 종합적인 파이프라인도 정리해보겠습니다.


1. 파이프라인 소개

3일간 단기 특보를 이용하여 구로구의 내일 정오 날씨를 알려주는 앱을 만든다고 생각해봅시다. 이를 반영하기 위하여 이 파이프라인은 매일 오후 11시에 실행되어 그다음날 구로구의 정오 온도(12시)를 저장하고 알림을 주며, 데이터에 저장합니다. 

전체 Schema

본 글에서는 (1) 기상 API 데이터 처리 (2) Airflow 설치 및 실행  (3) Supabase 저장 (4) Discord 알림 각 단계를 간단하게 세팅하는 것을 3단원에서 소개합니다. 마지막으로 최종 파이프라인을 위한 환경세팅과 코드를 4단원에서 기술합니다. 데이터는 기상청 API 허브의 예특보 - 4. 동네예보 - 3. 단기예보 조회를 활용할 예정입니다. 3일간의 기상 상황에 대한 정보로 특정 위치(x,y)좌표를 기준으로 기온, 풍속 하늘 상태 등에 대한 정보를 전달해 줍니다. 

 

데이터 출처: 기상청 API허브
데이터베이스: Supabase 화면

 

2. Pre-requisite

2.1. 기술 스택과 지식

기술 스택

  • Python 3.10
  • Airflow 3.1.0
  • Visaul studio Code
  • Docker (Desktop)
  • Discord 

 필요한 지식

  • Python
  • Linux & Docker 가상환경 설정 
  • Airflow DAG 구성
  • [윈도우] WSL

 

2.2. Airflow 3.1.0. 설치: Docker compose 이용

출처: https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html

  • 윈도우의 경우 wsl 설치 후 운영체제 진입하여 실행
#윈도우 Command

#배포판 조회
wsl -l -v

#배포판 진입
wsl -d {배포판명}
  • 맥북의 경우 작업 디렉토리 생성 후 Terminal에서 실행
#터미널
mkdir airflow3 && cd airflow3

#docker-compose.yaml 로드
#docker로 airflow 설정시 정보가 담김
curl -LfO 'https://airflow.apache.org/docs/apache-airflow/3.1.0/docker-compose.yaml'

#기본 디렉토리 설정 및 uid 설정
mkdir -p ./dags ./logs ./plugins ./config
echo -e "AIRFLOW_UID=$(id -u)" > .env

#airflow.cfg 설정파일 초기화
docker compose run airflow-cli airflow config list


#데이터베이스 초기화
docker compose up airflow-init


#도커 실행
docker compose up

#터미널 새로 열어 서비스 확인
docker ps
  • docker ps 출력 내용

  • [선택] Docker Desktop 에서 contanier 확인

[Airflow3  UI 변경 점]

이제 바로 Dags 탭이 나오지 않고 Home 화면이 보이며,  Dag 성공,실패 유무가 먼저 확인 가능. 각 컨테이너(MetaDatabase, Scheduler, Triggerer, Dag Processor)의 health check 가 상단에 나옵니다. 그 밖에도 Browse - Xcom 에서 Xcom 데이터가 보이며, Admin에서 환경변수(Variable)설정, 연결(Connections) 관리가 가능합니다.

개인적으로 Xcom은 WebUI로 보는 것보다 DBeaver 같이 어플리케이션으로 조회하는 것이 사용성 측면에서는 더 좋았습니다. 이 경우 airflow.cfg 파일에서 MetaDatabase의 포트를 외부(5432)로 열어줘야 연결 가능합니다.

#도커 컴포즈 내리기
docker compose down

# airflow.cfg파일변경
postgres:
  # ... 기존 설정
  ports:
    - "5432:5432"  # 이 줄 추가    
    
# 도커 재시작
docker compose up

DBeaver 로 xcom 테이블을 조회한 모습

3. 단위 테스트 실습

3.1 Airflow DAG 실행 테스트

  • 다음코드를 /dags 에 저장, 30초 정도 뒤에 Web UI Dag탭에서 조회가능
    • [꿀팁] Scheduler가 dags 폴더를 스캔하는 시간이 기다리기 싫다면 apiserver 컨테이너로 진입하여 강제 스캔
      docker exec -it {컨테이너명} bash
      airflow dags reseriealize
  • Dags 파이썬 코드
from datetime import datetime, timedelta
# 내장함수 -> 펜듈럼
from airflow import DAG
from airflow.operators.python import PythonOperator
# Operator 종류(Bash, Python, Email)

def print_hello():
    print("Hello World from Airflow!")  #로그 출력
    return "Hello World!"       #Xcom: aiflow 저장하는 변수 -> 다른 dag/task 받아서쓸수잇음

with DAG(
    dag_id='hello_world_dag', # GUI에 출력되는 이름명
    start_date=datetime(2025, 1, 1), #시작
    schedule=timedelta(days=1),      #실행주기
    catchup=False,                   #최초 실행했을때 이전날짜부터 실행여부
    tags=['example', 'hello'],       #태그(검색용이))
) as dag:
    
    hello_task = PythonOperator(  # t1,t2 선언도 가능
        task_id='print_hello_world',
        python_callable=print_hello,  #() 소괄호가 없음
    )

 

Dag 탭에 들어가 hello_world_dag를 검색하여 우측 상단 Trigger를 눌러줍니다. 여기서 Airflow3.0 변경점으로 Single Run과 Backfill 선택할 수 있는 팝업이 뜹니다. Single Run을 클릭합니다. 

Trigger 선택화면

정상적으로 실행되었다면 Task에서의 print는 로그에 Return은 xcom에서 조회할 수 있습니다.

Hello World From Airflow! 로그
Xcoms 기록

 

3.2. 기상 API 가져오기 및 전처리 테스트

기상 API를 가져오는 파이썬 스크립트 입니다.

  • get_weather: request하는 함수
  • parser_weather_xml: xml 자료를 정리해주는 함수
  • convert_to_dateframe: Pandas DataFrame으로 변환시켜주는 함수
import requests
import xml.etree.ElementTree as ET
import pandas as pd
import datetime
import json
import os
from dotenv import load_dotenv


def get_weather(api_key, today):
    '''
    기상청 동네예보: 단기예보를 조회하는 함수
    '''
    base_url = "https://apihub.kma.go.kr/api/typ02/openApi/VilageFcstInfoService_2.0/getVilageFcst"

    params = {
        "pageNo": 1,
        "numOfRows": 1000,
        "dataType": "XML",
        "base_date": today,
        "base_time": "0500",
        "nx": 58,
        "ny": 125,
        "authKey": api_key 
    }

    response = requests.get(base_url, params=params)
    return response.text

def parse_weather_xml(xml_string):
    """XML 문자열을 파싱하여 <item> 요소의 데이터를 리스트(딕셔너리)로 반환"""
    data_list = []
    try:
        root = ET.fromstring(xml_string)
        items_element = root.find('./body/items')
        
        if items_element is None:
            return data_list
            
        for item in items_element.findall('item'):
            item_data = {}
            for child in item:
                item_data[child.tag] = child.text
            data_list.append(item_data)
            
    except ET.ParseError:
        print("XML 파싱 오류: 수신된 데이터가 유효한 XML 형식이 아닙니다.")
    except Exception as e:
        print(f"데이터 파싱 중 예상치 못한 오류 발생: {e}")
        
    return data_list

def convert_to_dataframe(xml_data, today):
    '''
    XML 데이터를 불러와 전처리를 함께하는 함수
    '''
    weather_data_list = parse_weather_xml(xml_data)

    if weather_data_list:
        df = pd.DataFrame(weather_data_list)
        
        if 'fcstValue' in df.columns:
            df['fcstValue'] = pd.to_numeric(df['fcstValue'], errors='coerce')
    
    cat_map = {
        "POP": "강수확률",
        "UUU": "동서바람성분",
        "VVV": "남북바람성분",
        "VEC": "풍향",
        "WSD": "풍속",
        "SKY": "하늘상태",
        "PTY": "강수형태",
        "WAV": "파고",
        "PCP": "강수량",
        "REH": "습도",
        "SNO": "적설",
        "TMX": "최고기온",
        "TMN": "최저기온",
        "TMP": "기온",
    }
    
    df['category'] = df['category'].map(cat_map)
    df['loc'] = '구로구'
    
    target_categories = ['기온']
    cond1 = df['category'].isin(target_categories)
    cond2 = df['fcstTime'] == '1200'
    cond3 = df['fcstDate'] == today
    value = df[cond1 & cond2 & cond3]['fcstValue']

    return value

def main():
    """메인 실행 함수"""
    # 환경변수 로드
    load_dotenv()
    
    # 환경변수에서 API 키와 Webhook URL 가져오기
    api_key = os.getenv('WEATHER_API_KEY')
    webhook_url = os.getenv('DISCORD_WEBHOOK_URL')
    
    # 환경변수 확인
    if not api_key:
        raise ValueError("WEATHER_API_KEY 환경변수가 설정되지 않았습니다.")
    if not webhook_url:
        raise ValueError("DISCORD_WEBHOOK_URL 환경변수가 설정되지 않았습니다.")
    
    # 오늘 날짜
    today = datetime.datetime.now().strftime('%Y%m%d')
    
    # 날씨 데이터 가져오기
    print(f"{today} 날씨 데이터를 가져오는 중...")
    xml_data = get_weather(api_key, today)
    
    # 데이터 변환
    value = convert_to_dataframe(xml_data, today)
    
    print(f"오늘 구로구의 기온은 {value.values[0]}도 입니다.")

if __name__ == "__main__":
    main()
```

**사용 방법:**

1. **`.env` 파일 생성** (프로젝트 루트 디렉토리에):
```
WEATHER_API_KEY=your_api_key_here
DISCORD_WEBHOOK_URL=your_webhook_url_here

 

3.3. Python -> Discord: Webhook 설정 테스트

  1. 채널 생성 - 일반 채널 오른쪽 톱니바퀴 클릭
  2. 연동 - 웹후크 - 새 웹후크 - 봇 생성 - 웹 후크 URL 복사

  • Python으로 메시지 보내기 
import requests
import json

def send_discord_message(webhook_url, message):
    """
    docstring
    디스코드 봇으로 메시지를 보내는 함수입니다.
    discord 채널에서 생성한 webhook_url과 메세지를 전달받습니다.
    """
    data = {
        "content": message,
        "username" :"디스코드 봇"
    }

    response = requests.post(
        webhook_url,
        data = json.dumps(data),
        headers = {"Content-Type":"application/json"}
    )
    if response.status_code == 204:
        print('메시지 전달 완료!')
        print(response.text)
    else:
        print('문제발생!')
        print(response.status_code)

if __name__ == "__main__":
    webhook_url = 'https://discord.com/api/webhooks/1430385186058801214/g45PEv-CE790IOOLnmNAKtT4m0VnW-BpMXaAz4kAiILcJsOZLceGWNwjoMYchNnR3mcO'
    # message = "테스트!!"
    message = input()
    send_discord_message(webhook_url,message)

 

3.4. Python -> Supabase 테스트

1. Supabase 프로젝트 생성

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric
);

-- 위치 전달인자처럼 컬럼의 순서를 맞춰 넣기
INSERT INTO products VALUES (1, 'Cheese', 9.99);

-- 데이터 여러줄 넣기
INSERT INTO products (product_no, name, price) VALUES
    (1, 'Cheese', 9.99),
    (2, 'Bread', 1.99),
    (3, 'Milk', 2.99);

2. Python - supabase 연결을 위한 환경 변수 가져오기

  • Data API 키, Public API 설정
    • 왼쪽 하단 Project Setting - Data API - URL 복사: supabase_url 로 저장
    • 왼쪽 탭 API Keys - anon public 키 복사: supabase_key 로 저장

  • 로컬 .env 파일 저장
# .env
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_KEY=your-key
  • Python 코드 실행
from dotenv import load_dotenv
import os
from supabase import create_client
import pandas as pd

load_dotenv()
SUPABASE_URL = os.getenv("SUPABASE_URL")
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
# print(SUPABASE_URL,'\n', SUPABASE_KEY)

supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
type(supabase)
def get_data():
    result = supabase.table("products").select('*').execute()
    return result
get_data()

def insert_data():
    data = {"product_no":4, 'name': "치킨", 'price': 20.00}
    result = supabase.table("products").insert(data).execute()
    print(result.data)
insert_data()

삽입 성공

4. Python -> Discord: Webhook 설정 테스트

4.1. Airflow 환경 변수 설정

Airflow 에서 환경변수 설정에는 3가지 방법이 있습니다.

  1. Airflow 프로젝트 루트에 .env 파일 생성
  2. docker-compose.yaml 에 작성
  3. Web UI - Admin - Variable 에서 설정

3번이 제일 쉽고 편합니다.

Variable 탭

이렇게 설정한 환경변수는 Variable.get 메소드를 이용해 불러올 수 있습니다. 

[여기서 팁: 전역변수 설정하면 안되나요?] : 파이썬 스크립트에서 전역변수로 사용하면 될 것 같지만 Dags 하부의 task 는 Airflow 아키텍쳐에서 각각 다른 worker에게 할당될 수 있습니다. 따라서 전역변수로 설정하기보다 환경변수 설정을 해야합니다.

  • 환경 변수 불러오기 예제 Dags
from airflow import DAG
from airflow.operators.python import PythonOperator
from datetime import datetime, timedelta
from airflow.models import Variable


def print_variable():
    my_value = Variable.get("SUPABASE_URL", default_var="url 로드 안됌!")
    print(f"Supabase URL 값은 : {my_value}")

with DAG(
    dag_id="get_variable",
    start_date=datetime(2025, 10, 23),
    schedule=None,
    catchup=False
) as dag:

    t1 = PythonOperator(
        task_id="print_variable_task",
        python_callable=print_variable
)

4.2. Supabase  테이블 생성

Supabase 내에서 온도 정보를 담을 테이블을 선언해줍니다.

-- SQL 코드 시작 
-- weather_forecast 테이블 생성
CREATE TABLE weather_forecast (
    id BIGSERIAL PRIMARY KEY,
    forecast_date VARCHAR(8) NOT NULL,
    forecast_time VARCHAR(4) NOT NULL,
    temperature DECIMAL(5,2) NOT NULL,
    location VARCHAR(50) NOT NULL,
    nx INTEGER NOT NULL,
    ny INTEGER NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    CONSTRAINT unique_forecast UNIQUE (forecast_date, forecast_time, location)
);

-- 인덱스 생성 (검색 성능 향상)
CREATE INDEX idx_forecast_date ON weather_forecast(forecast_date);
CREATE INDEX idx_location ON weather_forecast(location);
CREATE INDEX idx_created_at ON weather_forecast(created_at);

-- 코멘트 추가
COMMENT ON TABLE weather_forecast IS '날씨 예보 데이터';
COMMENT ON COLUMN weather_forecast.forecast_date IS '예보 날짜 (YYYYMMDD)';
COMMENT ON COLUMN weather_forecast.forecast_time IS '예보 시간 (HHMM)';
COMMENT ON COLUMN weather_forecast.temperature IS '기온 (섭씨)';
COMMENT ON COLUMN weather_forecast.location IS '위치명';
COMMENT ON COLUMN weather_forecast.nx IS '격자 X 좌표';
COMMENT ON COLUMN weather_forecast.ny IS '격자 Y 좌표';
COMMENT ON COLUMN weather_forecast.created_at IS '데이터 생성 시간';

4.3. Airflow 내 Supabase 모듈 설치

Supabase는 airflow에 등록되어있지 않는 패키지 이기 때문에 Dockerfile과 docker-compose.yaml 파일 수정이 필요합니다.

  • requirments.txt 작성
    • Python -> Supabase 실행한 가상환경에서 pip freeze > requirements.txt 실행
certifi==2025.10.5
charset-normalizer==3.4.4
idna==3.11
numpy==2.3.4
pandas==2.3.3
python-dateutil==2.9.0.post0
pytz==2025.2
requests==2.32.5
six==1.17.0
tzdata==2025.2
urllib3==2.5.0
supabase==2.11.0
  • Dockerfile 작성
FROM apache/airflow:3.1.0

# requirements.txt 복사 및 설치
COPY requirements.txt /requirements.txt
RUN pip install --no-cache-dir -r /requirements.txt
  • docker-compose.yaml  내 build 명령어 추가
airflow-apiserver:
    <<: *airflow-common
    build: .
    command: api-server
    ports:

 

4.4. Dags 작성

태스크 정의

  1. API 불러오기(get_weather)
  2. xml 데이터 처리(parse_weather_xml)
  3. 데이터 전처리(convert_to_dataframe)
  4. supabase에 데이터 저장(insert_to_supabase)
  5. 디스코드 완료 메시지 전송(send_discord_message)
from airflow import DAG
from airflow.operators.python import PythonOperator
from datetime import datetime, timedelta
from airflow.models import Variable
import requests
import json
import xml.etree.ElementTree as ET
import pandas as pd
from supabase import create_client, Client
import pendulum

kst = pendulum.timezone('Asia/Seoul') # start_date에 timezone 설정용

'''
t1 -> t2 -> t3 -> t4 -> t5
api 코드 -> xml 파싱 -> 데이터프레임 변환 & 정보 -> Supabase 적재 -> 디스코드 전송
'''

def get_weather(**context):
    '''기상청 동네예보 단기예보 조회'''
    api_key = Variable.get("API_KEY")
    today = datetime.now().strftime('%Y%m%d')
    tmr = (datetime.now() + timedelta(days=1)).strftime('%Y%m%d')  # 내일 날짜 계산
    
    base_url = "https://apihub.kma.go.kr/api/typ02/openApi/VilageFcstInfoService_2.0/getVilageFcst"
    
    params = {
        "pageNo": 1,
        "numOfRows": 1000,
        "dataType": "XML",
        "base_date": today,
        "base_time": "0500",
        "nx": 58,
        "ny": 125,
        "authKey": api_key 
    }

    response = requests.get(base_url, params=params)
    
    # XCom에 데이터 저장
    context['ti'].xcom_push(key='xml_data', value=response.text)
    context['ti'].xcom_push(key='today', value=today)
    context['ti'].xcom_push(key='tmr', value=tmr)
    
    print(f"날씨 데이터 조회 완료: {today}, 내일: {tmr}")


def parse_weather_xml(**context):
    '''XML 파싱하여 리스트로 반환'''
    xml_string = context['ti'].xcom_pull(key='xml_data', task_ids='get_weather')
    
    data_list = []
    root = ET.fromstring(xml_string)
    items_element = root.find('./body/items')
    
    if items_element is None:
        raise ValueError("XML에 items 요소가 없습니다.")
        
    for item in items_element.findall('item'):
        item_data = {}
        for child in item:
            item_data[child.tag] = child.text
        data_list.append(item_data)
    
    context['ti'].xcom_push(key='parsed_data', value=data_list)
    print(f"XML 파싱 완료: {len(data_list)}개 항목")


def convert_to_dataframe(**context):
    '''데이터 전처리 및 온도 추출'''
    weather_data_list = context['ti'].xcom_pull(key='parsed_data', task_ids='parse_weather_xml')
    tmr = context['ti'].xcom_pull(key='tmr', task_ids='get_weather')
    
    if not weather_data_list:
        raise ValueError("날씨 데이터가 없습니다.")
    
    df = pd.DataFrame(weather_data_list)
    df['fcstValue'] = pd.to_numeric(df['fcstValue'], errors='coerce')
    
    # 카테고리 매핑
    cat_map = {
        "POP": "강수확률",
        "UUU": "동서바람성분",
        "VVV": "남북바람성분",
        "VEC": "풍향",
        "WSD": "풍속",
        "SKY": "하늘상태",
        "PTY": "강수형태",
        "WAV": "파고",
        "PCP": "강수량",
        "REH": "습도",
        "SNO": "적설",
        "TMX": "최고기온",
        "TMN": "최저기온",
        "TMP": "기온",
    }
    df['category'] = df['category'].map(cat_map)
    
    # 필터링: 내일 12시 기온
    filtered_df = df[
        (df['category'] == '기온') & 
        (df['fcstTime'] == '1200') & 
        (df['fcstDate'] == tmr)
    ]
    
    if filtered_df.empty:
        raise ValueError("기온 데이터를 찾을 수 없습니다.")
    
    temperature = float(filtered_df['fcstValue'].values[0])
    
    context['ti'].xcom_push(key='temperature', value=temperature)
    context['ti'].xcom_push(key='forecast_date', value=tmr)
    print(f"온도 데이터 추출 완료: {temperature}도")


def insert_to_supabase(**context): #xcom이랑 주고받는 태스크는 아무튼 context 받아야함
    '''Supabase에 날씨 데이터 적재'''
    supabase_url = Variable.get("SUPABASE_URL_WEATHER")
    supabase_key = Variable.get("SUPABASE_KEY_WEATHER")
    
    temperature = context['ti'].xcom_pull(key='temperature', task_ids='convert_to_dataframe')
    forecast_date = context['ti'].xcom_pull(key='forecast_date', task_ids='convert_to_dataframe')
    
    # Supabase 클라이언트 생성
    supabase: Client = create_client(supabase_url, supabase_key) #type hint
    
    # 삽입할 데이터 준비
    insert_data = {
        "forecast_date": forecast_date,#20251023
        "forecast_time": "1200",
        "temperature": temperature,
        "location": "구로구",
        "nx": 58,
        "ny": 125
    }
    
    try:
        # 데이터 삽입 (upsert 사용 - UNIQUE 제약조건 처리)
        response = supabase.table("weather_forecast").upsert( #update + insert
            insert_data,
            on_conflict="forecast_date,forecast_time,location"
        ).execute()
        print(f"Supabase 데이터 적재 완료: {insert_data}")
        
        # 적재된 데이터를 Discord로 전달하기 위해 XCom에 저장
        context['ti'].xcom_push(key='db_insert_success', value=True)
        
        return response
    except Exception as e:
        raise Exception(f"Supabase 데이터 적재 실패: {str(e)}")


def send_discord_message(**context):
    '''Discord로 메시지 전송 (DB 적재 후)'''
    webhook_url = Variable.get("DISCORD_WEBHOOK_URL")
    temperature = context['ti'].xcom_pull(key='temperature', task_ids='convert_to_dataframe')
    db_success = context['ti'].xcom_pull(key='db_insert_success', task_ids='insert_to_supabase')
    
    if not db_success:
        raise Exception("DB 적재가 완료되지 않았습니다.")
    
    data = {
        "content": f"✅ DB 적재 완료!\n내일 구로구의 기온은 {temperature}도 입니다.",
        "username": "디스코드 봇"
    }

    response = requests.post(
        webhook_url,
        data=json.dumps(data),
        headers={"Content-Type": "application/json"}
    )
    
    if response.status_code == 204:
        print('메시지 전달 완료!')
    else:
        raise Exception(f"Discord 메시지 전송 실패: {response.status_code}")


# DAG 정의
with DAG(
    'discord_weather_supa',
    description='구로구 기온 정보를 Supabase에 저장하고 Discord로 전송하는 DAG',
    schedule='0 23 * * *',
    start_date=datetime(2025, 10, 21, tzinfo=kst),
    catchup=False
) as dag:
    
    t1 = PythonOperator(
        task_id='get_weather',
        python_callable=get_weather
    )
    
    t2 = PythonOperator(
        task_id='parse_weather_xml',
        python_callable=parse_weather_xml
    )
    
    t3 = PythonOperator(
        task_id='convert_to_dataframe',
        python_callable=convert_to_dataframe
    )
    
    t4 = PythonOperator(
        task_id='insert_to_supabase',
        python_callable=insert_to_supabase
    )
    
    t5 = PythonOperator(
        task_id='send_discord_message',
        python_callable=send_discord_message
    )
    
    # 태스크 의존성: t1 -> t2 -> t3 -> t4 -> t5
    t1 >> t2 >> t3 >> t4 >> t5

5. 소감

  • Slack 봇 보다 Discord 봇이 설정이 훨씬 쉬워서 간단한 알림 만들기에 강점
  • Supbase 설정도 너무 편하다.
  • Airflow 3.0 Web UI 변한것 쓰다보면 편하긴하다. 하지만 개발자 감성이 물씬드는 투박한 UI
  • Docker는 신이다.

이렇게 데이터 출처로부터 데이터베이스 저장과 알람까지의 파이프라인을 구축해보았습니다. 많은 도움되길 바랍니다!

n8n은 노코드로 자동화를 구축할 수 있는 툴로 페어소스 정책을 따르는 오픈소스 툴입니다. Make와 비슷하지만 셀프호스팅 등 개발자를 위한 운영이 가능하며 비용도 절약할 수 있다는 장점이 있습니다. n8n을 공부하는 김에 간단하게 만들어본 장보기 지원 AI Agent 개발 후기와 Product 관점에서의 후기를 남겨봅니다.


1. 개요

한 2년전부터 꾸준히 농수산물 공공데이터(kamis)를 실습 데이터로 사용하고 있습니다. api에 대한 기본기를 소개하기도 좋고, 일상 생활의 데이터라는 점에서 몰입도가 좋아서 선호합니다. 하지만 대시보드로서의 Product를 꾸준히 개발하진 못했는데, 저의 게으름이 한 몫했습니다. 이번에 n8n 이라는 도구를 알게 되면서 AI agent를 손쉽게 구축할 수 있다고 들어서 실습 겸 해보았습니다.

2. 개발기

2.1. 문제 정의: 신혼 주부의 콜드스타트 문제

결혼 후 처음 장보기를 시작했을 때의 일입니다. 마트에서 애호박 1개가 1,500원에 팔고 있었고, 저는 고민없이 구매했습니다. 그런데 몇 개월 후, 같은 애호박이 700원에 팔리는 것을 보고 깨달았습니다. 농산물 가격은 계절, 날씨, 공급량에 따라 크게 변동하는데, 초보 주부는 이런 시장 정보가 없다면 '비싼 타이밍'에 구매할 수밖에 없었던 것입니다. 정보 비대칭의 문제였습니다.

"관심있는 품목의 집계 정보만 있어도 좋을텐데.."라는 현실의 문제를 인지했습니다.

다시 생각해보니 "아게 바로 주부의 콜드스타트 문제구나!" 라고 생각 했고, 농수산식품유통공사(aT)가 운영하는 kamis API를 알게 된 것도 이때 였습니다. 하지만, 당시에는 서비스화 하기에는 코딩 지식도 부족했고, AI 도구도 지금처럼 발달하지 않아 막연한 아이디어로만 남겨두었습니다.

2.2. 해결방안: No-code n8n으로 AI Agent 구현

지난번 데이터 파이프라인을 위한 치트시트를 공유한 것 처럼, n8n은 이런 간단한 데이터 파이프라인을 구축할 수 있습니다. 특히, Chat Trigger와 AI Agent 노드의 조합으로 챗봇을 만들 수 도 있습니다. 저는 지난 숙원 사업을 어제 반나절 정도에 완성하여, 요즘 제철 과일, 채소, 육류 제품을 쉽게 조회하고 제철 품목을 제안하는 보고서를 생성하는 AI Agent "FarmData"를 만들었습니다.

🤖 AI Agent란?
AI Agent란 특정 목표를 달성하기 위해 스스로 의사결정을 하고 다양한 Tool을 이용하여 행동할 수 있는 인공지능 시스템을 뜻합니다. "뇌"에 해당하는 Large Language Model, "기억"에 해당하는 Memory(DB), "손과 발"에 해당하는 Tool(Google Sheest, 웹 검색) 3가지 구성 요소가 기본 뼈대입니다. 기존 LLM은 워크플로우가 선형적이고 예측 가능한 반면 (A-> B -> C) 반면 AI Agent는 목표를 달성하기 위한 비선형적 워크플로우를 구성(A -> B -> A -> C)하여 더 복잡하고 다양한 업무를 수행할 수 있습니다.

저는 이 개념을 손쉽게 n8n으로 No-code로 구현하였습니다. 채팅 인터페이스로 Interactive하게 사용할 수 있는 것은 물론, 실제로 노드들의 상호작용을 하거나 중간 Inout/output 내용을 확인 할 수 도 있어 과정을 이해하기도 쉬웠습니다. 또한 데이터 분석을 위해서 로깅 기능을 추가했습니다.

챗봇 구현 화면

3. 운영기

3.1. 로그 분석

한달동안 쌓인 로그들

링크드인에 포스팅한 뒤로 약 한달이 지났습니다. 포스팅 노출은 18,112회이며 그 이후로  98개의 세션이 실행되었습니다.

첫 포스팅 8월 29일 이후로 지속적으로 세션이 감소하는 자연스러운 모습이 보입니다만, 일부 링크드인의 알고리즘 때문인지 튀는 값들이 조금 보이는 형태입니다. 반등을 한 건 9월 3일(배포 후 일주일), 9월 8일(일요일) 정도에 상승하는 모습을 보이나, 세션의 모수가 작아서 링크드인의 알고리즘 때문인지는 판단하기 어려운 부분이 있습니다.

 

 

 

시간대는 꽤 정직한 모습을 보이는데, 출근 시간인 7시-9시, 저녁시간인 21~22시에 높은 모습을 보입니다. 의외인 점은 퇴근 시간대 많이 세션이 잡히지 않았는데, 퇴근하고는 자기계발 위주의 SNS는 보기 싫은게 아닌가 🤣 라는 생각이 드네요

3.2. 운영의 문제점

  • 데이터가 조회되지 않는 문제

기본 프로덕트는 엄청 간단하게 만들어서 헛점이 많았습니다. 먼저 사용자가 챗봇으로 요청할 때마다 농수산물 품목에 대한 조회가 재현성이 없다는걸 확인했습니다. 아무래도 직거래 시장에서 발생한 데이터를 조회하다 보니 제철이 아닌 과일인 경우 조회하지 못하는 문제가 발생했습니다. 사실 이걸 보완하기 위해서 프롬프트로 "만약 조회 결과가 없다면 이전의 결과를 조회해"라는 식으로 넣었지만 아무래도 조건문이 아닌 프롬프트로 만들다보니 재현성이 떨어졌습니다.

  • API 안정성 문제

지난 실행에 대한 히스토리 확인

공공 API다 보니 이따금 HTTP 요청이 제대로 수립되지 않은 문제가 있었습니다. 게다가 위 질문에 대한 리포트를 만드는데 있어서 HTTP 요청이 여러번 실행되는 경우도 있었는데, 반복되는 요청에서 역시 실패하는 경우도 발생했습니다. 사실 이 문제는 사전에 기본적인 데이터를 Database에 저장해놓고 거기에 쿼리를 요청하는게 구조적으로 맞는 것 같습니다. 그 외에 필요한 결과가 있으면 그때 API를 요청하는게 맞는 방법이라는걸 깨달았습니다.

  • 사전 테스트

사실 위 내용을 방지할 수 있는 방법은 사전 테스트를 실행해보는 것입니다.  n8n에서는 Evaluations이라는 탭을 제공해주는데 이걸 한번 테스트해봤다면 이런 문제를 방지할 수 있었을 것 같습니다.

n8n Evaluation 가이드

4. 나가며

AI Agent를 쉽게 만들 수 있다는 점에서는 입문으로 n8n만한게 없는 것 같습니다. PoC로 사용하면 매우 좋을 것 같고, 비개발자들도 쉽게 접근할 수 있다는 점에서 매력적인 툴인 것 같습니다. 보다 더 수준 높은 Agent 구축을 위해서는 Langchain/LangGraph 프레임워크를 사용하면 되겠다는 생각이 들었습니다. 반면, PoC를 만들긴 쉽지만 완성도 있는 프로덕트를 만드는 것은 역시 설계/평가 관점에서 고려할 점이 있다는 걸 깨달은 프로젝트 였습니다. 또, n8n을 만드는 과정에서 데이터 파이프라인을 위한 cheatsheets를 만들었는데 처음으로 github star를 30개 넘게 받아서 기분이 좋았네요. 앞으로는 지속적으로 유즈케이스를 만들어서 github를 채워볼 생각입니다.

5. 레퍼런스

  • n8n cheatsheet for data pipeline: 깃허브링크
  • 장보기 지원 AI Agent 소스 - Github
  • 반나절만에 n8n을 이용한 장보기 지원 AI Agent 개발기 - 링크드인

오픈소스답게 Redash API는 직관적인 endpoint로 손쉽게 데이터를 가져올 수 있습니다. 이번 글에서는 n8n 노코드 툴을 사용하여 redash 데이터를 가져오는 방법을 알아봅니다.


1. Redash api 문서

https://redash.io/help/user-guide/integrations-and-api/api/

 

API

 

redash.io

 

API 키 발급은 쿼리 탭의 우측 상단 Show API Key를 통해 확인. cURL 형식의 Endpoint 주소도 존재. 이때 쿼리를 반드시 publish 해야지 endpoint가 잘 작동

모든 api는 기본 접속 주소에 endpoint를 불러서 작동 기본 URL(https://redash.example.com) 이 있다면 뒤에 /api/queries 를 붙여 redash에 저장되어있는 queries 목록을 가져올 수 잇음

  • /api/queries: 쿼리 객체 페이지 가져오기
  • /api/queries/<id>:  GET(개별 쿼리), POST(새로운 쿼리 생성)
  • /api/queries/<id>/results: GET(개별 쿼리)의 값 가져오기, POST(새 결과 요청하기)

쿼리 id는 url에 적혀 있음

가끔 게임 분야에 관심있는 분들이 데이터분석 포트폴리오로 만들만 한 주제를 추천해달라고 하면 저는 riot API를 추천합니다. 대중성있고 리그오브레전드,룬테라, TFT 등 API도 다양하게 많이 제공해주기 때문입니다. 이번 글은 롤 API를 가져오는 핵심 Endpoint를 정리해보겠습니다.


1. riot api 로그인

https://developer.riotgames.com/app-type

 

Sign in with your Riot account

 

authenticate.riotgames.com

Product 등록입니다. 왼쪽은 Prouduction 레벨의 API 이며, 요청횟수가 더 많거나 더 많은 정보를 줍니다만, 승인 기간이 느리고 절차가 필요합니다. 우측 Personal API Key를 발급 받으시면 됩니다.

Personal 키의 경우 매번 갱신해야합니다. 또한 요청 횟수가 1초에 20건, 2분에 100건으로 제한되어있기 때문에 꾸준한 데이터 수집을 위해서는 반드시 배치형식의 데이터 수집을 고려해야합니다.

 

2. 핵심 API 3가지

보통 riot api라고하면 리그오브레전드 게임 내의 게임 정보에 대한 데이터를 생각합니다. 해당 데이터는 match - timeline api에 정보가 있습니다. 이 정보를 가져오려면 matchi id를 가져와야하고 또 이 match id를 구하려면 유저 id(puuid)가 필요합니다. 다음은 이 3가지 api를 cascading 하게 가져오기 위한 각 api의 구조입니다.



 

1.  AWS 회원가입

  • 기존메일로 로그인시 폐쇄 되었다고 나올 수 있음
    • 당황하지말고 기존 메일+{추가용어} @ gmail.com 과 같이 아이디 부분을 변경하여  신규회원가입

 

2. Redash 설치하기

다음 블로그 참고

https://yeonnys.tistory.com/entry/AWS-EC2-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EC%9C%84%EC%97%90-Redash-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0

 

[AWS] EC2 인스턴스 위에 Redash 설치하기

데이터를 분석한 결과를 시각화하여 보여주기 위해서는 대시보드에 나타내는 방법이 있다. Tableau, Power BI 등 다양한 대시보드가 있긴 하지만, 모든 기능을 자유롭게 사용하기 위해서는 오픈소

yeonnys.tistory.com

  • OS: Ubuntu 22.04 LTS
  • 스펙: t1.medium

완료

 

Claude에게 Redash 물어보기

Redis의 장점:

  1. 극도로 빠른 성능 - 메모리 기반 저장으로 마이크로초 단위 응답시간
  2. 다양한 데이터 구조 지원 - String, Hash, List, Set, Sorted Set, Stream 등
  3. 영속성 옵션 - RDB 스냅샷, AOF 로그를 통한 데이터 보존
  4. 확장성 - 클러스터링, 마스터-슬레이브 복제 지원
  5. 캐싱 최적화 - LRU, LFU 등 다양한 만료 정책
  6. Pub/Sub 메시징 - 실시간 메시징 시스템 구축 가능
  7. 원자적 연산 - 동시성 제어가 필요한 카운터, 랭킹 시스템에 적합

ERD 관련: Redis는 NoSQL 키-값 저장소이므로 관계형 데이터베이스처럼 테이블 간의 관계나 스키마가 없습니다. 따라서:

  • ERD 개념이 적용되지 않음 - 테이블, 외래키, 관계가 존재하지 않음
  • DBeaver 같은 ERD 도구 불필요 - 데이터 구조가 단순한 키-값 쌍
  • Redis 전용 관리 도구 사용:
    • Redis Commander (웹 기반)
    • RedisInsight (공식 GUI 도구)
    • Medis (macOS)
    • Redis Desktop Manager

Redis는 관계형 DB의 대안이 아니라 캐시, 세션 저장, 실시간 기능에 특화된 보완적 역할을 합니다.

3. 참고링크

https://brunch.co.kr/@nuli/5

 

Redash 대시보드 구축하기 101

#대시보드 구축하기 #대시보드 구축 프로젝트 | 안녕하세요~ 데이터 분석가 누리입니다. 오늘은 대시보드 작업을 위해 필요한 사전작업인 대시보드 구축하는 것에 대해 다뤄보도록 하겠습니다~

brunch.co.kr

https://redash.io/help/open-source/setup/

 

Setting up a Redash Instance

 

redash.io

 

이번에는 나만의 서비스를 Claude code를 이용한 바이브 코딩한 절차와 교훈에 대해서 작성해봅니다. 늘 나만의 서비스를 하나씩 만들고 싶었는데 좋은 핑계(?)가 생겨서 하루 이틀 반짝해서 만들어봤습니다. 이게 되네?라고 느끼는 프로젝트였고 웹개발을 직접 핸즈온으로 배우고 구현하는 재미가 있어서 좋은 경험이였습니다. 이 글을 읽고 덜 시행착오를 겪으시고 결과를 만드시길 바랍니다. 


1. 문제 정의

커리어의 목표를 위해서 방송통신대에 통계.데이터학과에 25년 2학기에 입학했습니다! 자연스럽게 3학년 편입이라 전공과목을 4학기만에 수강해야하는 상황입니다. 더해서 추후 CS 대학원을 목표로 하고 있어서 컴퓨터과 과목도 넣게되었습니다. 그러다 보니 한학기에 5과목씩 전공을 듣게 되었네요. 많이 듣는건 좋은데  일도 해야하고, 방통대 특성상 자유롭게 들을 수 있는 인강 형태라 미루다가 강의 폭탄 맞을 것 같아서 대책이 필요 했습니다.

한 강좌당 15개가 존재하며 데드라인은 3개월 남짓이다!

 

제가 해결하고 싶은 문제는 꾸준히 수업을 들을 수 있는 시스템을 만들자에 집중했습니다.

 

2. 해결 방법 1 - 노션으로 대시보드 만들기

제일 좋은 방법은 수업이 같거나 비슷한 학우들을 모아서 서로의 진척 사항을 공유하는 것입니다. 그래서 노션의 데이터베이스 기능을 이용하려했습니다. 요구사항은 다음과 같습니다.

  • 랜딩 페이지는 학생마다 수강률에 대한 친적사항이 프로그레스바로 보여줄 것
  • 수강기록 페이지는 손쉽게 접근할 수 있는 체크박스형태로 만들 것

그래서 다음과 같은 템플릿을 만들었습니다.

전체 과목의 진척사항 확인
개별 강의의 수강 체크

노션에서는 "관계형"이라는 컬럼이 존재합니다. 두 테이블을 관계로 묶고,  수강현황판의 한행은 수강기록 N개의 강의와  1:N 관계이기 때문에 수강기록 데이터베이스 페이지 하나가 수강현황판을 참조하는 식으로 구성했습니다. 또한, 수강현황판에서 수강완료는 "롤업"이라는 컬럼으로 수강기록의 체크박스의 수를 기록하는 형식입니다. 마지막으로 수강 현황판의 "진행률"은 같은 행의 수강완료 / 전체 강의 수의 비율을 시각적으로 나타내었습니다.

반나절정도 노션 데이터베이스를 레퍼런스를 찾고 만들었는데, 생각보다 이 만드는 과정에 "노션식 관계형"에 대한 이해가 없으면 많이 허덕이는 부분입니다. 그래서 동영상으로 소개 영상을 만들었습니다. 맨 밑에 링크 남겨놓겠습니다. 

하지만 노션은 신규등록자 입장에서 되게 불편했습니다. 가이드 영상을 고사하더라도, 나의 새로운 과목을 추가할때 15개씩 수강기록을 남겨야했고(자동화를 통해 1~15회차가 자동으로 만들어지지만) 체크박스 하나씩 클릭하자고 수강기록을 만드는 것도 썩 맘에 들지 않았습니다. 그래서 그냥 웹서비스를 하나 만들자라는 결론에 도달했습니다.

 

3. KNOU Tracker 사이트

4가지 세부 페이지를 구상했습니다.

  • 대시보드: 수업 진행 현황을 볼 수 있는 랜딩페이지
  • 개인 현황: 수업 진행률을 체크할 개인 페이지
  • 신규 등록: 신규 진입자를 위한 등록 페이지
  • 관리 

 

 

이렇게 하여 신규사용자가 스스로 자신을 등록하고 기록을하며 전체 진척사항을 서로 공유하여 동기부여를 얻을 수 있는 서비스가 만들어졌습니다.

 

4. 시행착오들

4.1. PRD 문서 작성

개발쪽에서는 PRD라는 문서가 있다는 걸 알게되었습니다. Claude에게 PRD를 만들기 위한 방법으로 질의응답으로 나에게 질문을 해달라고 부탁했고 개발하기전 LLM에게 해당 사항을 읽고 구현하라고 시켰습니다. 나중에 README.md 에 작성하기에도 좋으니 사전에 설정해놓으면 사전 준비작업이 빨라집니다.

README.md 일부

 

4.2. 스택 선택

Claude code를 요즘 간단한 개발할 때 잘 쓰고 있었습니다. 그래서 다양한 조합을 Claude에게 물어봤습니다.

  1. Python Only(streamlit)
  2. Vanillia Javascript 
  3. React,Vue 등 최신 프론트 스택

사실 웹 개발, 특히 프론트쪽은 잘 모르기에 유지보수에 대한 걱정으로 Streamlit을 추구했습니다.

 

하지만 반나절 정도 해보니 내가 원하는 디자인이 계속 나오지 않았습니다. 특히 좌측 한바닥을 차치하는 세부 페이지 드롭다운,  핵심 페이지의 공간 비효율성이 마음에 들지 않았습니다. 그래서 그냥 빠르게 접었습니다. 3번은 투머치였고, 그냥 Vanilla Js로 하기로 결정하고 만들었습니다. 또한 Github Pages로 배포를, 데이터베이스는 요즘 유명하다는 Superbase를 사용했습니다.

4.2. Claude Code 사용

Claude Code Pro 요금제를 사용하고 있어서 웹개발을 계속 시키면 거의 2시간 만에 토큰이 모두 소모됩니다. 시간이 지나면 다시 한도가 돌아가는 시스템인데 Claude-monitor 를 이용해 중간에 개발하다가 끊키는 일이 없도록 했습니다. 한 3번정도 iteration 끝에 웹페이지를 만든 것 같습니다. Claude-monitor 링크는 하단에 넣어놓겠습니다.

 

여기서보면 정확히는 Token과 Message 한도가 있는데, Token은 100%가 도달하면 얄짤 없이 더이상 프롬프팅이 불가능한 반면 Message는 100%가 도달 해도 어느정도 명령을 수행해줍니다.  초반에는 Limit이 걸리면 그냥 손놓고 있었는데 요즘 Gemini CLI이나 Codex CLI 라는 대체 툴도 많아서, 손놓고 놀지말고 도구를 바꿔서 사용하면 연속성있게 사용할 수 있을 것 같습ㄴ다.

 

4.3. 추후 개선할 기능들

빠른 결과를 추구하다보니 비 필수적이 기능들을 아직 구현하지 못했습니다. 차차 구현해볼 생각입니다.

  • 크롬 확장 브라우저를 통한 자동 연동    -> 사실 이게 젤 하고 싶습니다. 근데 지식이 전무해서 찾아봐야하는...
  • 오픈 서비스로 만들기 위한 로그인 기능
  • 사용자의 분석을 위한 로그시스템
  • 버그 리포팅

5. 후기

처음만 해도 이정도의 퀄리티가 하루 반나절만에 나오리라고 생각하지 못했습니다. 간단한 개발은 확실히 접근성이 좋아진 것 같습니다. 하지만 그럼에도 개발을 위한 몇가지 사전 지식이 필요하다는걸 깨달았습니다.

  • 명확한 요구사항

PRD같은 문서의 존재를 아는 것도 중요하지만 내가 정확히 어떤 프로덕트를 만들고 싶은지 소구하는 것이 중요했습니다. 모호한 목표는 모호한 결과물을 만들기 마련이니까요.

  • 백엔드에 대한 기본적인 지식

 배포시에 데이터베이스를 만들어야한다는 개념은 아는데 이걸 Superbase에서 어떻게 연결해야하는지는 스스로 공부해야하고, 찾아낼 줄 알아야합니다. 이번에 LocalStorage가 브라우저에 내장될 수 있다는 걸 처음 알았습니다. 

  • 웹개발에 대한 기본지식

직접 github 블로그를 만들어서 알았던 부분이여서 완전히 모르는 상태로하진 않았지만 index.html을 비롯한 html에 대한 이해, 웹브라우저에서 개발자도구 - 콘솔의 오류를 체크하는 부분을 몰랐다면 시행착오를 많이 겪었을 것 같습니다. 물론 Claude Code에게 물어보면 방법을 알려주긴 하지만 전혀 개발 생태계를 모른다면 이런 점이 병목이 될 것 같습니다.

  • CLI  AI Assitant는 신이다.

저는 연동하는 방법을 찾지 못해서 Github 배포도, Superbase MCP 설정도 못하고 일일히 손으로 했지만 앞으로는 이런것들이 기본 기능으로 들어오면서 정말 손쉽게 결과물을 만들어낼 것 같습니다. 결국 기술은 발전할 것이니 빠르게 Proto Typing하고 기획하는것 그리고 이런 프로세스를 지속 가능한 시스템으로 내재화하는 것이 중요하겠구나 깨달은 프로젝트 였습니다.

 

6. 참고링크

Youtube - 방통대 트래커 노션 사용법 안내

Notion - 방통대 트래커 노션 템플릿

 

방통대 수업 트래커 | Notion

Made with Notion, the all-in-one connected workspace with publishing capabilities.

bellepoque7.notion.site

Github - 방통대 트래커 웹서비스

 

GitHub - ggplab/knou_tracking2

Contribute to ggplab/knou_tracking2 development by creating an account on GitHub.

github.com

Github - Claude-code Moinitor

 

GitHub - Maciek-roboblog/Claude-Code-Usage-Monitor: Real-time Claude Code usage monitor with predictions and warnings

Real-time Claude Code usage monitor with predictions and warnings - Maciek-roboblog/Claude-Code-Usage-Monitor

github.com

Youtube - 생활코딩 Supabase

 

지난번에 언급한 NVIDIA prompt engineering 자격증을 획득하기 위한 과정에서 배웠던 개념들을 나열합니다.


1. LLM 전반

1.1. LLM 의 원리

  • LLM은 다음 단어를 예측하도록 작동함
  • 이 과정에서 LLM이 단어를 이해도록 임베딩하는 과정을 거침
  • 임베딩은 단어간의 유사성을 계산할 수 있도록 만들어 추론하기 위한 준비

    출처: 오늘의 코드 Youtube - 토큰화, 벡터화, 임베딩 무슨 차이일까?

    1.2. one-hot encoding과 vectorization의 차이

    • 전자는 단어간의 관계를 전혀 나타내지 못하고 후자는 단어간의 의미적 유사성의 관계를 나태내는 것
    • one-hot: 희소 벡터의 특징, 고차원임 (단어의 갯수 만큼)
    • vecotr: 단어 임베딩은 의미를 압축한 밀집벡터로 표현하는 기술

    1.3. Transformer

    • LLM의 각 층은 Transfomer로 구성됨
    • 어텐션: 각 단어는 문장 내의 다른 단어들을 살펴보고 자신의 의미를 명확히하게 함, 자신의 정보(키벡터)와 자신이 찾고 있는 정보(쿼리 벡터)를 비교.
    • 피드 포워드: 어텐션 단계에서 수집된 정보를 바탕으로 각 단어 벡터를 개별적으로 분석 다음 단어를 예측하려고 시도.

     1.4. 온도와 소프트 맥스 활성함수의관계

    • 소프트 맥스 활성함수는 신경망의 마지막 계층에서 출력 함수로 로지스틱 함수의 일반화
    • 실수 벡터를 입력으로 받아 확률 분포로 정규화
    • 출력은 입력과 같은 차원이며 각 원소의 범위는 0과1, 모든 합은 1
      • 소프트: 함수가 하드 최대 함수보다 더 부드로운 확률 분포를 생성
      • 맥스: 입력 벡터에서 가장 가능성이 높은 값을 소프트 하고 확률적인 방식으로 선택함
      • 온도 매개변수는 출력 확률 분포의 부드러움, 피크성을 제어어
        • T = 1 표준 소프트 맥스 출력과 동일
        • chatgpt는 무작위성과 창의성을 제어하기위해서 0과 2사이의 값을 가지고 있음. 기본값 1
    • 다음 단어를 예측하는 과정에서 소프트맥스 활성화 함수를 사용. 이때 온도라는 매개변수를 도입할 수 있는데 온도가 높으면 분포가 평탄해져 어떤 결과는 나올 수 있는 창의성이 발현되고, 낮으면 분포가 뾰죡해져 대답이 일관되게 나옴

    출처: What Is and Why Use Temperature in Softmax?

    https://www.baeldung.com/cs/softmax-temperature

    1.5. Fine tunning과 RAG

    출처: Fine-Tuning LLMs: A Guide With Examples

    • Fine tunning 의 함정
      • 과적합: 에포크를 과도하게 늘리면 학습데이터셋에서는 높은 정확도를 보이지만 새로운 데이터셋에서 일반화 되지 못할 수 있음
      • Catastophic Forgetting: 특정한 작업을 위한 미세조정 과정에서 처음에 습득한 광범위한 지식을 잃을 위험
      • 데이터 유출: 훈련 및 검증 데이터 세트가 중복됨
    • RAG
      • 정의: 검색기반 모델과 생성 모델의 장점을 결합, 입력 쿼리를 기반으로 대규모 데이터베이스 또는 지식 기반을 검색하여 관련 정보를 검색
      • 동적 지식 통합: 외부 소스의 실시간 정보를 통합하여 최신 지식이나 특정 지식이 필요한 작업에 적합
      • 문맥적 연관성: 검색된 문서에서 추가적인 맥락을 제공하여 생성 모델의 응답을 향상
      • 다재다능: 더 광범위한 쿼리를 처리할 수 있음

     

    2. Langchain

    LLM 기반 어플리케이션 개발을 간소화하고 유연하게 만드는 핵심적인 역할

    2.1 Runnable

    • Chain을 구성하는 모든 구성요소를 연결하고 실행하기 위한 표준 인터페이스
    • 표준화된 메소드: invoke, batch, stream 표준 호출 메소드를 제공
    • 모든 구성요소가 공통된 메서드를 가지게 하는 것

    2.2 LCEL

    • LangChainExpressionLanguage Runnable을 체인으로 구성하여 유닉스 파이프 연산자처럼 선언적인 방식 제공
    • Prompt | model | output_parser 형식으로 구성
    • 프롬프트 엔지니어링 기법 지원
      • 역할 기반 메시지: ChatPromptTemplate.from_messages 메소드를 사용하여 Human, AI, System 와 같은 역할 기반 메시지를 명시적으로 정의
      • 퓨삿 프롬프팅: FewShotChatMessagePromptTemplate 모델 동작에 영향에 미치는 예시를 사전에 제공할 수 있음

    출처: Prompt Engineering vs. RAG vs. Finetuning: What’s the Difference?

    •  StringOutputParser
      • 언어모델의 출력값을 다루기 쉬운 문자열로 변환하기 위해 사용함
      • LLM이나 챗봇 모델은 단순히 텍스트만 변환하는 것이 아니라 AIMessage, ChatMessage와 같은 복잡한 객체 형태로 출력하기 때문에 순수한 텍스트 내용을 추출하기 위한 방법

    3. 기타

    3.1. Pydantic 사용하는 이유

    • 파이썬은 동적언어여서 자료형이 사전에 선언되지 않음
    • 타입 힌트는 힌트일 뿐이며 데이터 유효성 검사를 위해 Pydantic를 사용하여 출력 자료형을 구체하하는것

    3.2. RAG을 사용해야하는 이유

    • 지식의 단절, 특정 시점까지만 LLM이 학습하기 때문
    • context window의 한계, 기억 메모리가 무한하지 않음
    • 벡터 DB는 기존 DB가 정확히 요청한 단어만 찾는것과 달리, 유사한 단어를 찾아 응답결과를 좀더 풍성하게 해줌
    • 벡터(색인)과 원본데이터(원자료)를 모두 저장하여 유사한 색인을 찾아 결과를 반환하는 형태로 구현됨

    3.3. 메모리를 기억하기 위한 방법

    이 부분 확실하지 않음. 애초에 LLM은 stateless 하기 떄문에 정보를 저장하는게 중요.chat_message 메소드에 이전 대화 기록을 AI message, Human Message등을 저장하고 멀티턴으로 구현하는 방법도 있으나 context limt 때문에 적절하지 않을 수 있음. 검색한 결과 다음 메서드가 있는 것을 확인

    https://wikidocs.net/235581

     

    08. RunnableWithMessageHistory

    .custom { background-color: #008d8d; color: white; padding: 0.25em 0.5…

    wikidocs.net

    3.4. 서로 다른 api를 Langchain에 통합하기 위한 방법

    RunnableParallel

    3.5. Langchain과 LangGraph의 차이

    • Langchain은 단방향(프롬프트 → 모델 → 파서)
    • LangGraph: 순환가능하고 복잡하고 예측 불가능한 상황에 대응하는데 특화, 결과를 평가한뒤 다음 행동을 스스로 결정해야할때 유용함

    https://wikidocs.net/261585

     

    1-1-2. LangGraph와 LangChain의 차이점

    ## LangGraph와 LangChain의 차이점 LangGraph와 LangChain은 둘 다 LLM(대규모 언어 모델) 애플리케이션 개발을 위한 강력한 도구이지만, 각각의…

    wikidocs.net

     

    5. 종합적인 느낌

    • state-of-art의 정보보다는 핵심 개념을 질의하는 자격증
    • 장문의 대답보다는 핵심적으로 요구하는 단어, 용어, 클래스, 함수를 중심으로 설명하는게 좋음
    • 기본적인 LLM에 대한 작동 기전에 더하여 prompt engineering에 사용할 수 있는 Langchain 메서드를 공부 
    • 기출예시
      • 토큰 vs 벡터, Langchain, LCEL, Temperature(범위, 창의적 vs 일관성 있는 결과에 대한 스케일 범위), chatgpt api의 temperature 범위, Pydantic, LLM의 memory 기억 방법, LangGraph 등

    6. 출처

    이번 글은 컴퓨터 밑바닥의 비밀 스터디를 추진하고 수행한 회고를 작성해봅니다. 스터디를 기획하려는 분이나 CS 공부 계획이 있으신 분들이 참고할 만한 글을 작성하는 것이 글의 목적입니다.


    컴퓨터 밑바닥의 비밀

     

    컴퓨터 밑바닥의 비밀 : 알라딘

    코드를 실행하면 컴퓨터 내부에서는 어떤 일이 벌어질까? 운영 체제부터 CPU, 동기화, 입출력을 구현하는 원리까지, 내 코드를 바꿔줄 컴퓨터 밑바닥의 비밀을 들여다보자.

    www.aladin.co.kr

     

    1. 진행 방식

    • 일정: 5월 11일(일) ~ 7월 20일(일)
    • 일시: 매주 일요일 오후 10시, 온라인미팅(허들 & 줌)
    • 총 진도: 6단원, 12주(3달)
    • 규칙
      • 매주 한 단원의 절반씩 진행
      • 자료작성, 블로글 ok, 책 밑줄 그으면서 설명해도되고, 자료(스샷)가져와서 판서하면서해도됨
      • 발표자: 매주 3명 발표 필수(설명을 하는 5분-10분)
      • 참여자: 3줄 요약으로 경험 공유

    • 디포짓 5만원 선입금
      • 발표 안하면 만원 차감
      • 참여 안하면 오천원 차감, 1회 패스권 사용 가능
    • 시스템
      • 발표 순서 정리

    • 예치금 현황판

    총 6단원으로 이루어진 책이기 때문에 12명을 구인했습니다. 각 단원을 절반씩 쪼개 1주차당 3명의 발표를 의무적으로 잡았습니다. 단순히 읽고 논의하기 라는 느슨한 규칙을 세우면 책을 안 읽을 수 도 있기 때문입니다. 하지만 발표의 결과물 형태를 까다롭게 하지 않았습니다. 각자의 개인노션이나 블로그에 작성한 화면 그대로를 발표로 해도 되고 관심있게 본 단어나 구절만 발표하는 식으로도 가능하게 했습니다. 실제로 이렇게 진행하니 총 36회의 발표에서는 미발표는 2번, 미참석은 2번이였습니다.(참석 패스는 9명 사용)

    3. 스터디 회고

    다음은 12주간 스터디 진행 후 KPT 템플릿을 이용한 후기를 Gemini Pro 2.5를 통해 요약한 결과 입니다.


    Keep (성과 및 유지할 점)

    • 성공적 스터디 시스템
      • 주 3인 발표 제도로 책임감 부여 및 꾸준한 학습 동기 마련
      • 발표자 외 '3줄 요약' 규칙으로 참여도 유지
      • 예치금 제도로 완주 의지 강화
      • 자율적 발표 형식으로 다양한 시각(블로그, 시각화 등) 공유 및 학습 효과 증대
    • 긍정적 학습 경험
      • 500p 분량의 기술 서적 완주 경험 획득
      • 동료 학습을 통해 혼자서는 어려웠을 개념(캐시라인, 콜백 등) 명확히 이해
      • 다양한 발표 스타일 덕분에 다각적이고 입체적인 학습 가능
      • AI(Claude, LLM)를 활용한 시각화, 내용 검증 등 새로운 학습법 시도

    Problem (문제점 및 아쉬움)

    • 스터디 운영의 비효율
      • 3인 발표 시 내용이 중복되어 집중력 저하 및 비효율 발생
      • 발표자가 아닐 경우, 학습 우선순위가 밀려 수동적 참여에 그치는 경향 발생
      • 발표 중심의 단방향 소통으로 상호작용(질문, 토론) 부족
    • 학습 깊이의 한계
      • C/C++ 코드 예제를 눈으로만 훑고 넘어가 실질적인 체득 부족
      • 어려운 주제 발생 시 깊게 파고들지 못하고 수박 겉핥기식으로 학습
      • 개인 실습 및 학습 내용을 블로그 등으로 정리하는 Output 활동 부족

    Try (향후 시도할 점)

    • 스터디 운영 방식 개선
      • (역할 분담) 발표 전, 이론/코드분석/심화탐구 등 파트를 나눠 중복 방지
      • (상호작용 강화) 챕터별 질의응답 시간을 의무화하거나, 나무위키처럼 공동 학습 노트 작성
      • (참여 유도) 비발표자도 참여할 수 있는 퀴즈나 토론 주제 제시
    • 개인 학습 전략 강화
      • (코드 실습) 예제 코드를 직접 실행하거나, 익숙한 언어(Python 등)로 변환하여 실습
      • (지식 내재화) 스터디 후 학습 내용을 블로그에 정리하거나 자신만의 방식으로 시각화
      • (심화 학습) 책 외의 레퍼런스를 추가로 찾아보고, 궁금증을 해결하며 학습 깊이 더하기
      • (시간 관리) 스터디 학습 시간을 미리 계획하여 꾸준함 유지

    3. 발표자료들

    오픈 링크로 공개된 것들을 참고 자료로 남깁니다.

    1단원

     

    [Study][컴퓨터 밑바닥의 비밀] 1. 프로그래밍 언어부터 프로그램 실행까지, 이렇게 진행된다.

    이 책의 첫 단원인 프로그래밍 언어부터 프로그램 실행까지, 이렇게 진행된다. 에서는 제목에서 부터 알 수 있듯이 인간이 작성하는 고수준 언어(C, C++, Java 등)이 어떻게 저수준 언어로 번역이

    velog.io

     

     

    컴퓨터밑바닥: Chapter 1 프로그램 언어부터 실행까지, 이렇게 진행된다.

    데이터 중심 어플리케이션 설계에 이어 컴퓨터 밑바닥의 비밀이라는 책을 스터디 합니다. 1장은 프로그래밍 언어가 작동하는 기본을 설명하는 단원으로 컴파일러와 링커 그리고 추상화에에 대

    snowgot.tistory.com

    2단원

     

    [컴퓨터 밑바닥의 비밀] 프로그래밍 개념 파헤치기

    프로그래밍의 기본 동기와 비동기프로그래밍을 시작하면서 가장 많이 듣는 개념 중 하나인 동기와 비동기에 대해 알아봅시다. 동기 개념은 일상 속에서도 쉽게 접할 수 있습니다. 여러분이 건

    codediary21.tistory.com

    3단원

     

    컴퓨터 밑바닥 Chapter 3: 메모리라는 사물함에서부터 시작해보자

    이전 단원에서는 CPU가 하는 일과 그와 함께 등장하는 스레드, 코루틴, 콜백함수와 동기/비동기 등에 대해서 알아봤습니다. 이번 장에서는 CPU가 데이터를 끌어오는 장소 메모리에 대해서 어떻게

    snowgot.tistory.com

     

    메모리 할당

     

    worms-argue-981.craft.me

    4단원

     

    [컴퓨터 밑바닥의 비밀] CPU 진화론

    CPU의 진화CPU 관점의 프로그램포토샵, 크롬, helloWorld 출력 등 cpu 입장에서는 기계 명령어일 뿐이다. 명령어의 양이 다를 뿐기계어는 실행 파일에 저장된다. 프로그램이 실행되면 실행 파일에 있

    codediary21.tistory.com

    5단원

     

    [컴퓨터 밑바닥의 비밀] 메모리 장벽과 잠금 프로그래밍

    컴파일러와 POoECPU는 프로그래머가 코드를 작성한 순서대로 명령어를 실행하지 않음 (성능을 높이기 위함)비순차적 실행 단계기계 명령어를 생성하는 단계: 컴파일 중 명령어 정렬CPU가 명령어를

    codediary21.tistory.com

     

    컴퓨터 밑바닥 Chapter 5: 작은 것으로 큰 성과 이루기, 캐시

    이번 장에서는 캐시에 대해서 기술하며 책의 5번째 여정입니다. 다시 복기하자면 컴파일러 -> 프로세스/스레드 -> 메모리 -> CPU에 이은 장이며, 컴퓨터가 캐싱을 하기 위한 방법과 문제에 대해서

    snowgot.tistory.com

    6단원

     

    입출력

     

    worms-argue-981.craft.me

    4. 개인적인 경험

    데이터 중심 어플리에케이션 설계(a.k.a 대중애설, DDIA) 스터디 이후 다양한 스터디원들의 공통 관심사인 CS로 책을 선정하게 되었습니다. 처음에는 중국인 저자여서 선입견을 가지고 시작했는데, 각 개념에 필요한 적절한 그림과 명확한 설명에 한 번 놀라고, 컴파일러부터 시작하여 메모리,프로세스, 스레드, 캐시, cpu, 입출력까지 차근히 지식의 상아탑을 쌓아가는 것을 보고 한 번 더 놀랐습니다. 앞으로 이 저자의 책은 고민안하고 읽어볼 것 같습니다.

     

    필자는 데이터 분석가로 일을 시작했고 컴퓨터과학개론만 1학년 때 수강했으므로 CS의 지식은 거의 없지만, 하드웨어를 만지는걸 좋아해서 용어나 개념에 대한 이해가 막 어색하거나 낯설진 않았습니다. 오히려 희미한 지식에서 가끔 들리는 프로세스/스레드  코루틴, 동기/비동기와 같은 개념을 한 번 잡아볼 수 있는 기회여서 스터디에 대한 만족도는 높았습니다. 본인이 CS에 대한 이해도가 부족하거나 한 번 정리하길 원한다면 일독을 추천드립니다. 

     

    개인적으로 인상깊게 보았던 개념이 "데이터 일관성"에 대한 개념입니다. 사실 설계 개발자의 관점에서 중요한 부분이긴 하나,  프로세스/스레드에서 데이터 일관성의 이슈가 분산 시스템에서도 이식되어 똑같은 문제상황이 일어나고 유사한 방법으로 해결하려고 한다는 점이 흥미로웠습니다. 시대가 발전해도 고전적인 프로그래밍의 설계와 철학은 그 이유가 있므르로 역시 과거는 미래의 등대라는 생각이 듭니다. 여기에 더하여 AI agent에서는 서로 다른 기능을 하는 Agent가 공통된 Context를 유지하기 위한 시스템 프롬프트를 공유하는 부분이 중요하다고 들었는데 나아가 이런 데이터 일관성에 대한 부분이 새로운 기술에도 적용되는 뼈대구나 라고 생각이 들면서도 기본을 알면 여기저기에 써먹을 것이 많구나 라는 생각도 동시에 들었습니다. 

     

    종합적으로 볼 때, 장기적인 저의 커리어 목표(Chief Data Owner)에 엔지니어링 축이 될 수 있는 바탕이 되었고, 앞으로 진행할 상아탑에도 튼튼한 버팀목이 되었으면 좋겠습니다. 🙂

     

     

     

     

    개발자에게는 코딩 테스트를 요구하는 것이 당연하게 여겨집니다. 하지만 데이터 직군(분석, 과학, AI) 직무는 회사 상황이나 요구사항에 따라 Python 알고리즘부터, 라이브러리를 이용한 문제 해결, 과제 전형 등 다양한 방식으로 진행됩니다. 이런 상황에서 데이터 직군은 알고리즘을 알아야할까요? 이번 글은 문제를 실제로 보면서 푸는 로직을 공유하고 실제로 데이터 과학자에게 알고리즘 공부가 필요한지 논의 해보는 글입니다. 


    1. 질문

    https://school.programmers.co.kr/learn/courses/30/lessons/135808

     

    프로그래머스

    SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

    programmers.co.kr

    def solution(k, m, score):
        score.sort(reverse=True) #사과 점수 내림차순으로 정렬
        result = 0
        while len(score) >= m:
          box = score[:m] #사과를 점수가 높은 순부터 4개 뽑기
          result += min(box)*m #4개 뽑은 것 중에 가장 낮은 점수 곱하기 * 사과개수 
          del score[:m] #꺼낸 사과 제거
        return result
    • 실행 결과

    • 학생질문) 시간 초과로 일부 테스트는 실패라고 뜨는데 왜 그런지 궁금합니다. 그럼 이 문제는 다른 방법으로 풀어야 하는 것일까요?

    2. 나의 대답 전문

    2.1 알고리즘 채점 방식

    파이썬 알고리즘 문제는 후반으로 갈수록 좀 정교한 방식으로 채점됩니다. 문제를 해결하기 위한 논리와 효율성이 그것입니다. 위 코드에서는 효율성이 문제가 됩니다.보통 알고리즘 문제는 1초안에 수행되어야합니다. 위 코드를 돌려보면 6번 문제부터 100ms가 소요되는 걸 확인할 수 있어요. 그렇습니다. 코드 어딘가에 굉장히 속도가 많이 걸리는 것을 알 수 있습니다.

    2.2 현 코드의 문제

    결론만 말하자면, 반복문 혹은 정렬 등에 관련된 구문이 속도를 잡아 먹습니다. 위 코드에서는 del score[:m] 구문입니다. score가 가장 큰 경우는 100만개입니다. 반면 최소값은 m이 3입니다. 이런 최악의 경우 다음과 같이 진행됩니다.

    1. 사과 상자를 정렬함 (<- 사실 이 정렬속도도 매우 큽니다.)
    2. 100만개의 사과 중 3개를 뽑아냄. 그 이후 나머지 사과를 앞으로 3칸씩 "당김"
    3. 999,997 개의 사과중 3개를 뽑아냄. 그 이후 나머지 사과를 앞으로 3칸씩 "당김"
    4. 반복

    del 연산이 자체적으로 데이터를 앞 당기는 상황입니다. 100만개를 모두요! 언제까지? while문이 끝날 때 까지. 그래서 문제가 되고 실제로 다음 코드를 돌려보면 시간이 매우 걸리는 것을 확인할 수 있습니다. 문제 11번 부터 시간초과라고 뜨는 이유가 해당 테스트에서 score길이가 길어서 그래요! 더 깊게 알고 싶다면 시간복잡도 개념을 검색해보시면 됩니다.

    2.3. 개선 코드

    기존 코드를 바꾸는 것도 고민해봤지만, for문으로 변형하는게 제일 합리적입니다. 일정한 사과의 갯수를 포장하는 것은 range(start, end, step) 함수를 이용해서 만들 수 있거든요. 

    def solution(k, m, score):
        score.sort(reverse=True)
        result = 0
    
        # m-1 인덱스부터 리스트 끝까지 m씩 건너뛰며 반복
        # 이 인덱스들이 각 상자의 최저 점수가 됨
        for i in range(m - 1, len(score), m):
            result += score[i] * m
        return result
    위 코드를 돌리면 기존 문제 6번이 100ms 에서 6ms 로 16배 빨라졌습니다!

    2.4. 검증해보기

    100만개의 사과를 테스트 해보면 시간이 너무 오래걸려서 10만개로 가정하고 테스트해봅니다. chagpt한테 만들어달라고 하면 금방 만들어줘요

    import time
    import random
    
    # 1. 기존의 while문 방식
    def solution_original(k, m, score):
        score.sort(reverse=True) #사과 점수 내림차순으로 정렬
        result = 0
        while len(score) >= m:
          box = score[:m] #사과를 점수가 높은 순부터 4개 뽑기
          result += min(box)*m #4개 뽑은 것 중에 가장 낮은 점수 곱하기 * 사과개수 
          del score[:m] #꺼낸 사과 제거
        return result
    
    # 2. 개선된 for문 방식
    def solution_improved(k, m, score):
        score.sort(reverse=True)
        result = 0
        for i in range(m - 1, len(score), m):
            result += score[i] * m
        return result
    
    # 테스트 데이터 생성
    k = 9 # 사과 최대 점수 (예시)
    m = 3 # 한 상자에 담는 사과 개수
    # 1~k 사이의 점수를 가진 100만개의 사과 리스트 생성
    score_list = [random.randint(1, k) for _ in range(100000)]
    
    # --- 기존 방식 시간 측정 ---
    # 원본 리스트를 복사해서 사용 (sort가 리스트를 변경하므로)
    score_copy_1 = score_list[:] 
    start_time_1 = time.time()
    solution_original(k, m, score_copy_1)
    end_time_1 = time.time()
    print(f"🐢 기존 방식 실행 시간: {end_time_1 - start_time_1:.6f}초")
    
    
    # --- 개선된 방식 시간 측정 ---
    score_copy_2 = score_list[:]
    start_time_2 = time.time()
    solution_improved(k, m, score_copy_2)
    end_time_2 = time.time()
    print(f"🚀 개선된 방식 실행 시간: {end_time_2 - start_time_2:.6f}초")
    
    '''
    🐢 기존 방식 실행 시간: 0.202289초
    🚀 개선된 방식 실행 시간: 0.004085초
    대략 50배정도 빠름!
    '''

     

    2.5. 이렇게까지 해야하나...

    이런 해설을 보면 사실 이렇게까지 공부해야하나 싶으실거에요. 사실 알고리즘은 소위 개발자를 위한 테스트 시험이고, 우리가 sql을 공부하는 것처럼 준비해야하는 하나의  관문입니다. 분야도 depth 도 깊어요!  전공과목으로 있을 정도니까요근데 데이터 분석가의 지원하는 기업에 따라 일부 파이썬 기본문제를 요구하는 곳이있어요. 이런 "완전 구현" 문제(LG전자 전형)나 scikit learn을 활용한 유클리드 거리 계산(게임회사 크래프톤)이나 pandas를 활용한 문제들 출제합니다. 정말 중구 난방이긴해요 ㅋㅋ알고리즘을 푸는 것은 좋지만 너무 매달리고 개발자까지 공부할 필요는 없다는게 제 결론입니다. 완전구현, 이진트리, BFS, DFS 정도면 충분합니다.  이것도 사실 대기업 기준...  하지만  파이썬을 구현하고 더 깊게 팔수록 회사에서 기회가 더 많이 주어진다는건 명확해요. 요즘같은 LLM이 assist를 해주는 요즘 분석가와 같은 semi- IT직군이 LLM을 등에 타고 훨훨 날아가는 건 시간문제이니까요~ 잘 쓰려면? 잘 이해해야한다.말이 길었네요~ 여튼 대부분 알고리즘을 포기하는 지금까지 파이썬 문제를 잡고있다는건 노력도 많이하시고 열심히 하고자는 배경이 있다는 것이니까 꼭꼭 이점을 장점으로 가져가서 엔지니어링 스택도 가져가는 희귀한 데이터분석가가 되셧으면 좋겠습니다.  혹 또 궁금한거 있으면 찾아오세요~

    3. 위 대답을 하게 된 이유

    3.1. 시대적 배경

    회사에서 나와서 좀 더 넓은 시야로 데이터 분야를 보다보니 직군에 대한 이해가 명확해졌다. 사실 데이터 분석가라는 직군은 정의된지 10년도 채 되지 않아서, 테크기업은 활발히 채용하고 있지만 대기업의 경우 이제 데이터 팀이 만들어지고 데이터 리터러시 문화가 도입되는 중이다. 

    이런 흐름에서 사실 데이터 직군은 높으신 분들에서는 그저 IT직군 중 하나이다. 마치 병원에 제약사에 누가 방문해도 영원사원1, 2 처럼 의사 선생님이 생각하시는 것 처럼.. 이런 상황에서 데이터 팀은 자신의 specialty는 추구미로 유지하되, 회사와 시대가 요구하는 사항에 유연하게 pivot 할 필요가 있다. 다시 말해 "저 분석 만 할거에요!"라고 자신의 기능을 제한하기 보다는 "데이터로 볼 수 있는 문제를 해결하는 사람"으로 추구미를 갖는 것이 장기적으로 유익하다. 그런 관점에서 데이터 잡부라고 속칭 자책하는 분들은 어찌보면 다양성을 갖춰가는 과정일 수 있다. 

    그렇다면 "데이터로 문제를 해결하는 사람이"되기 위해서는 기능과 도구에 국한하면 안된다. Python으로 할 수 있는 것들이 굉장히 다양하므로 웹 프로그래밍, 자동화, 분석, ML, 통계, CS 등 하나씩 즐겨찾기를 해놓는 것이 좋다. 나는 이 관점을 가지게 된 이유가 LG 데이터사이언스 캠프로 튜터로 일했던 2023년이고 지금도 그 방향을 향해 하나씩 분할 정복해가고 있다. 당시 공부방향을 작성해놓았다.

    https://blog.naver.com/bellepoque7/223107480964

     

    [글또] 네? 데분 멘토링왔는데 알고리즘 2달 알려주라고요?

    0. 글의 목적과 독자 지난 4개월간 Data Science 멘토링을 하면서 스스로 배웠던 것은 바로 Learning C...

    blog.naver.com

    3.2. 데이터사언스와 잡마켓

    데이처 직군이 잡마켓에 잘 적용하기 위한 방안에 대한 다음 글을 추천해본다. 유료 글이라 요약하면 다음과 같다.

    https://medium.com/@sohail_saifi/why-your-data-science-degree-is-becoming-worthless-and-what-companies-actually-want-177913b7b706

     

    Why Your Data Science Degree Is Becoming Worthless (And What Companies Actually Want)

    Do you recall when it was declared that “data science is the sexiest job of the 21st century”? Yes, the honeymoon period is officially…

    medium.com

     

    1. 변화의 배경
    과거: 2020년 팬데믹 시기에 기업들은 너도나도 데이터 과학자를 고용했지만, 이들 중 상당수가 이론에만 강하고 실제 서비스에 모델을 적용하지 못하는 문제를 겪었습니다. 현재 기업들은 이론적인 모델이 아닌, 실제 문제를 해결하고 비즈니스에 가치를 창출하는 실용적인 솔루션을 원하게 되었습니다. 이로 인해 많은 데이터 과학자들이 해고되었고, 채용 기준도 바뀌었습니다.

    2. 학위의 한계와 요구되는 기술
    학위의 문제점: 기존 데이터 과학 학위 과정은 확률 분포나 통계적 추론 같은 이론에 치우쳐 있어, 실제 현장에서 필요한 코딩이나 모델 배포 등의 기술을 제대로 가르치지 못합니다.

    기업이 원하는 기술: 기업들은 데이터 파이프라인 구축, 클라우드 인프라(AWS, Azure 등), 컨테이너(Docker, Kubernetes), API 개발 등 실무에 바로 투입될 수 있는 기술을 가진 전문가를 찾고 있습니다. 특히, AI와 자연어처리(NLP) 관련 기술에 대한 수요가 급증하고 있습니다.

    3. '만능 데이터 과학자' 시대의 종말
    전문성의 중요성: 이제 기업들은 모든 것을 아는 만능형 데이터 과학자 대신, 특정 분야에 특화된 전문가를 원합니다. 머신러닝 엔지니어, 데이터 엔지니어, AI/ML 전문가, 분석 엔지니어 등으로 역할이 세분화되고 있습니다.

    4. 살아남기 위한 전략
    데이터 과학 분야에서 성공하려면 학위만으로는 부족합니다.
    - 실제 배포 경험 쌓기: Docker, 클라우드 플랫폼(AWS, Azure 등), API 배포 등을 직접 경험해야 합니다.
    - 데이터 엔지니어링 기본기 다지기: SQL, 데이터 웨어하우징, Apache Spark 등 데이터 처리 기술을 익혀야 합니다.
    - 비즈니스 문제 해결에 집중: 기술을 비즈니스 문제 해결에 적용하고, 비전문가에게 결과를 명확히 설명하는 능력을 길러야 합니다.
    - 특화된 분야 선택: LLM/NLP, 컴퓨터 비전, MLOps 등 수요가 높은 분야를 선택해 전문성을 키워야 합니다.

    결론적으로, 데이터 과학 분야는 여전히 유망하지만, 이론적 지식에만 머물지 않고 실제 문제를 해결할 수 있는 실무 역량을 갖춘 인재를 필요로 하는 방향으로 진화하고 있습니다.

    3.3. 그래서 알고리즘을 알아야하는가? 라는 질문에는

    상황에 따르지만 나는 "한번쯤 공부해볼 만하다" 라고 대답한다. 우리의 세부 전공은 회사와 시대에 따라 달라질 수 있고, 그 기반이 되는 것이 튼튼한 기초 지식이다. 사실 이건 알고리즘에 국한하는 것이 아니라 CS, 통계, 클라우드 등 데이터로 문제를 해결하기 위한 앞뒤의 기반들을 포괄한다. 종합적인 관점에서 만약, 구직/이직하는 회사가 코테를 요구하면 최소한의 '완전 구현' 유형이라도 이직 준비할겸 해보길 바란다.  대기업과 같은 일반적인 역량을 요구하는 곳일 수록 더 알고리즘 테스트를 요구할 확률이 높다. 왜냐? 대기업은 한 명을 뽑아서 장기적으로 사용하는 것을 고민하기 때문이다. 일반적인 역량이 있는 사람을 좋아한다. 또한, 본인이 장기적으로 데이터 직군의 과도기를 거쳐 매니저 레벨까지 가고 싶으면 해보아라. 기본기에 가까운 CS와 같은 내용이 좋다. 나는 이런 책들을 하나씩 읽고 기록해나고 있다.

    https://snowgot.tistory.com/194

     

    DataScience를 위한 엔지니어링 책 추천

    Last Updated 2025.05.21.지난 통계학/데이터사이언스 책 추천에 이어서 엔지니어링 관련 책 추천을 해보려 합니다. 데이터 사이언스에서 CS, 백엔드, 프론트, 네트워크, 데이터 모델링 등 필수적이진

    snowgot.tistory.com

    한국농수산물유통공사에서는 kamis라는 데이터 정보 공개 시스템이 있습니다. 이따금 실습용으로 데이터를 불러와서 과일, 채소 시세를 만들어보는 목적으로 데이터를 수집해보는 편입니다. 과거에는 직접 python 코드로 작성해서 데이터를 불러왔는데, 서버를 띄우지 않아서 파이프라인까지는 완성하지 못했습니다. 이번에는 n8n을 공부하면서 low code를 no code 툴로 바꿔서 수집해보고 그 차이에 대해서 한번 고찰해봅니다. 


    1. 개요

    1.1. 과거 데이터 분석 결과 

    과거 월간 데이터 노트에서 수행했던 결과를 한장의 포스터로 저장했습니다.

    클릭하면 이미지가 커집니다!

     

    1.2. 왜 이 분석은 아직 성불하지 못했는가?

    위 분석은 사실 n년전 사이드 프로젝트를 하면서 구현한 결과입니다. 장바구니 대시보드라는 이 주제는 사실 지속성있게 데이터를 수집해야만 의미를 가집니다. 하지만 당시 로컬에서 retrospective 그러니까 현재 기준 과거 날짜에 대한 데이터만 수집했습니다. 지속가능한 프로덕트를 위해서는 서버에 올려서 자동화 및 파이프라인을 구축해합니다. 그래야 대시보드의 본질을 찾을 수 있기 때문입니다.

    하지만 당시 이리저리 우선 순위에 밀려 단적인 그래프를 그리고 마치게됩니다. 클라우드에 서버를 빌리고 수집하는 과정이 어려운 일은 아니지만 번거로웠기 때문입니다. 그렇게 시간이 지나 월간 데이터 노트라는 스터디에서 다시 한번 부활하게 되었습니다. n8n이라는 자동화 도구를 이용하면 좀 더 편하게 데이터를 수집할 수 있을까요? 

    2. 결과:  workflow, 데이터

    Google Sheet 에서 데이터를 주고 받으려면 n8n 클라우드에서는 노드만 추가하고 구글 로그인을 하면 됩니다. 하지만 셀프 호스팅의 경우 인증을 위한 사전 절차가 필요합니다. 다음 n8n 문서에 잘 나와있어서 따라하기만 하면 됩니다.

    https://docs.n8n.io/integrations/builtin/credentials/google/oauth-generic/#google-cloud-app-becoming-unauthorized

    완성된 work flow
    결과

    3 . n8n 도입 후기

    장점1:  간단한 세팅

    기본적으로 n8n은 오픈소스여서 내 컴퓨터에 설치해서 실행할 수 있습니다. 하지만 이 과정이 조금 복잡하기 때문에 14일간의 무료 trial 기간을 제공합니다. 셀프호스팅을 하지 않고 진행할 수 있는 점이 장점입니다. 로컬에서 수집하려면 파이썬 설치, 가상환경 세팅 등이 번거롭습니다. 하지만 n8n 은 node로 이루어진 구성 요소를 선택하고 연결함으로써 쉽게 원하는 목적을 달성할 수 있습니다. 

    장점2: 시각화, 쉬운 모듈화

    이따금 복잡한 서비스를 만들다 보면 코드레벨에 있는 스키마를 보여주기 위하여 figjam과 같은 3rd party tool이용해서 그려야합니다. 하지만 n8n은 그 자체로 로직이 보이기 때문에 굳이 스키마를 만들기 위한 시간과 노력을 기울이지 않아도 됩니다. 또한 노드들 하나 하나의 input ouput이 명확하게 보여서 어떻게 작동하는지 파악하기 쉬웠습니다.

    Input -> 개별 노드의 파라미터 -> Output

    장점3: 좋은 레퍼런스들

    입문자들에게는 n8n에서 제공해주는 혹은 강의나 블로그에서 제공해주는 .json형식의 workflow를 따라하는 것만으로도 도움이 됩니다. 공식 문서는 제가 근래 본 오픈 소스 중에 가장 이해하기 편했으며, 포럼도 잘 활성화 되어있습니다. 서비스 안에서는 사이드바에서 LLM assist를 받을 수 있습니다. 생각해보니 LLM assist는 클라우드에서만 제공되는 단점이 있네요.

    (좌) Docs (우) Forum
    workflow 화면 사이드에 chatbot이 존재

     

    단점1: 어쨋든 알아하는 작동 방식

    노코드 툴이 가지는 전체 문제점이기도 하나, 각 개별 노드가 작동하는 방식을 어렴풋히라도 인지 하지 않으면 설계 자체가 불가능합니다. HTTP 노드에서 수행하는 POST/GET 요청의 차이 받아 처리하는 JSON 자료형, API의 개요, 구글 시트를 연결하기 위한 인증 과정들에 대한 이해가 있거나 해결하려는 마음이 있어야 원하는 결과물을 얻을 수 있습니다. LLM에서 "해줘"라고 요청했을 때 바로 수행하는 그런 경험을 기대했다면 실망할 수 있습니다.

    단점2: 셀프호스팅의 할 경우 올라가는 러닝커브

    n8n을 최초 회원가입하면 14일간의 무료 Trial을 주지만 결국 끝나면 유료 결재를 하게 될 것입니다. 비용을 피하고 싶다면 셀프 호스팅을 해야하는데 docker등에 대한 도구를 사용하므로 자연스럽게 러닝커브가 올라갑니다. 개발에 익숙하면 어렵겠지만 결국 노코드의 장점을 일부 잃어버린다고 할 수 있겠습니다.

    4. 짤막한 후기

    로우 코드로 직접 구현하고 만드는 과정이 꽤나 귀찮았는데 n8n으로 하니 파이프라인도 쉽게 만들 수 있는 장점이 있는 것 같습니다. 비개발자를 위한 툴이기도 하지만 개발자들도 쉽게 만들 수 있는 툴인 것 같고 Make, Zaiper 등이 가지고 있는 자동화 라인에서 굳건히 자리를 지킬 것 같네요! 종종 쓰게 될 것 같습니다.

    5. 출처

    6. 기타 팁

    • 로컬 docker에서 구글 시트와의 정보를 주고 받으려면 인증 진행 필요
    • n8n 클라우드의 경우 인증 버튼이 바로 있지만 셀프호스팅은 그렇지 않기 때문에 Outh 인증을 진행

    • OAuth Redirect URL:  구글 인증 플랫폼 - 클라이언트 - 승인된 리디렉션 URL에 넣을 것,  로그인 요청시 받은 API 엔드포인트 경로인듯
    • Client ID / Secret: GCP 위 경로에서 웹 어플리케이션으로 생성하면 나오는 ID/PW

     

    본 주제는 매달 한 번씩 호기심을 주제로 분석하는 모임 <월간 데이터 노트>의 결과입니다. 관심이 있으시면 다음 링크를 확인해 보세요!!

    + Recent posts