데이터베이스 데이터 수집

데이터베이스 데이터 수집 #

개요 #

데이터베이스는 구조화된 데이터의 저장소로서, 기업의 핵심 데이터 자산이 저장된 곳이다. 데이터베이스에서의 데이터 수집은 SQL을 통한 쿼리 실행, API 연동, ETL 프로세스 등 다양한 방법으로 이루어지며, 데이터의 정확성과 일관성이 보장된다는 장점이 있다.

관계형 데이터베이스 (RDBMS) #

주요 RDBMS 종류 #

  • MySQL: 오픈소스, 웹 애플리케이션에 널리 사용
  • PostgreSQL: 고급 기능 지원, 복잡한 쿼리 처리에 적합
  • Oracle: 대기업용, 높은 성능과 안정성
  • SQL Server: Microsoft 생태계 통합
  • SQLite: 경량화, 모바일 및 임베디드 시스템

Python을 통한 데이터베이스 연결 #

MySQL 연결

import mysql.connector
import pandas as pd

def connect_mysql(host, user, password, database):
    try:
        connection = mysql.connector.connect(
            host=host,
            user=user,
            password=password,
            database=database
        )
        return connection
    except Exception as e:
        print(f"MySQL 연결 오류: {e}")
        return None

def fetch_data_from_mysql(connection, query):
    try:
        df = pd.read_sql(query, connection)
        return df
    except Exception as e:
        print(f"데이터 조회 오류: {e}")
        return None

# 사용 예시
conn = connect_mysql('localhost', 'user', 'password', 'mydb')
query = "SELECT * FROM customers WHERE created_date >= '2023-01-01'"
data = fetch_data_from_mysql(conn, query)

PostgreSQL 연결

import psycopg2
import pandas as pd
from sqlalchemy import create_engine

def connect_postgresql(host, port, database, user, password):
    try:
        engine = create_engine(
            f'postgresql://{user}:{password}@{host}:{port}/{database}'
        )
        return engine
    except Exception as e:
        print(f"PostgreSQL 연결 오류: {e}")
        return None

def execute_query(engine, query):
    try:
        with engine.connect() as connection:
            df = pd.read_sql(query, connection)
        return df
    except Exception as e:
        print(f"쿼리 실행 오류: {e}")
        return None

# 사용 예시
engine = connect_postgresql('localhost', 5432, 'mydb', 'user', 'password')
query = """
SELECT 
    product_id,
    product_name,
    category,
    price,
    stock_quantity
FROM products 
WHERE category IN ('Electronics', 'Books')
ORDER BY price DESC
"""
products = execute_query(engine, query)

R을 통한 데이터베이스 연결 #

DBI 패키지 활용

library(DBI)
library(RMySQL)
library(dplyr)

# MySQL 연결
con <- dbConnect(
  RMySQL::MySQL(),
  host = "localhost",
  user = "username",
  password = "password",
  dbname = "database_name"
)

# 쿼리 실행
query <- "
SELECT 
  customer_id,
  order_date,
  total_amount,
  status
FROM orders 
WHERE order_date >= '2023-01-01'
"

orders <- dbGetQuery(con, query)

# 연결 종료
dbDisconnect(con)

RODBC 패키지 활용

library(RODBC)

# ODBC 연결
channel <- odbcDriverConnect("
  Driver={SQL Server};
  Server=localhost;
  Database=SalesDB;
  Trusted_Connection=yes;
")

# 데이터 조회
sales_data <- sqlQuery(channel, "
  SELECT 
    s.sale_id,
    s.sale_date,
    s.amount,
    c.customer_name,
    p.product_name
  FROM sales s
  JOIN customers c ON s.customer_id = c.customer_id
  JOIN products p ON s.product_id = p.product_id
  WHERE s.sale_date >= DATEADD(month, -6, GETDATE())
")

# 연결 종료
odbcClose(channel)

NoSQL 데이터베이스 #

MongoDB 연결 및 데이터 수집 #

Python을 통한 MongoDB 접근

from pymongo import MongoClient
import pandas as pd
from datetime import datetime

def connect_mongodb(uri, database_name):
    try:
        client = MongoClient(uri)
        db = client[database_name]
        return db
    except Exception as e:
        print(f"MongoDB 연결 오류: {e}")
        return None

def query_mongodb(db, collection_name, query, limit=1000):
    try:
        collection = db[collection_name]
        cursor = collection.find(query).limit(limit)
        data = list(cursor)
        return pd.DataFrame(data)
    except Exception as e:
        print(f"MongoDB 쿼리 오류: {e}")
        return None

# 사용 예시
db = connect_mongodb('mongodb://localhost:27017/', 'ecommerce')

# 최근 30일 주문 데이터 조회
from datetime import datetime, timedelta
thirty_days_ago = datetime.now() - timedelta(days=30)

query = {
    'order_date': {'$gte': thirty_days_ago},
    'status': {'$in': ['completed', 'shipped']}
}

orders = query_mongodb(db, 'orders', query)

Aggregation Pipeline 활용

def aggregate_mongodb(db, collection_name, pipeline):
    try:
        collection = db[collection_name]
        result = list(collection.aggregate(pipeline))
        return pd.DataFrame(result)
    except Exception as e:
        print(f"Aggregation 오류: {e}")
        return None

# 월별 매출 집계
pipeline = [
    {
        '$match': {
            'order_date': {'$gte': datetime(2023, 1, 1)}
        }
    },
    {
        '$group': {
            '_id': {
                'year': {'$year': '$order_date'},
                'month': {'$month': '$order_date'}
            },
            'total_sales': {'$sum': '$amount'},
            'order_count': {'$sum': 1}
        }
    },
    {
        '$sort': {'_id.year': 1, '_id.month': 1}
    }
]

monthly_sales = aggregate_mongodb(db, 'orders', pipeline)

Redis 데이터 수집 #

import redis
import json
import pandas as pd

def connect_redis(host='localhost', port=6379, db=0):
    try:
        r = redis.Redis(host=host, port=port, db=db, decode_responses=True)
        return r
    except Exception as e:
        print(f"Redis 연결 오류: {e}")
        return None

def get_user_sessions(redis_client, pattern='session:*'):
    try:
        keys = redis_client.keys(pattern)
        sessions = []
        
        for key in keys:
            session_data = redis_client.hgetall(key)
            sessions.append(session_data)
        
        return pd.DataFrame(sessions)
    except Exception as e:
        print(f"Redis 데이터 조회 오류: {e}")
        return None

# 사용 예시
r = connect_redis()
user_sessions = get_user_sessions(r)

빅데이터 플랫폼 #

Apache Spark를 통한 데이터 수집 #

PySpark 활용

from pyspark.sql import SparkSession
from pyspark.sql.functions import col, when, count, avg, sum as spark_sum

def create_spark_session(app_name="DataCollection"):
    try:
        spark = SparkSession.builder \
            .appName(app_name) \
            .config("spark.sql.adaptive.enabled", "true") \
            .config("spark.sql.adaptive.coalescePartitions.enabled", "true") \
            .getOrCreate()
        return spark
    except Exception as e:
        print(f"Spark 세션 생성 오류: {e}")
        return None

def read_from_database(spark, jdbc_url, table, properties):
    try:
        df = spark.read \
            .format("jdbc") \
            .option("url", jdbc_url) \
            .option("dbtable", table) \
            .options(**properties) \
            .load()
        return df
    except Exception as e:
        print(f"데이터베이스 읽기 오류: {e}")
        return None

# 사용 예시
spark = create_spark_session()

jdbc_url = "jdbc:postgresql://localhost:5432/mydb"
properties = {
    "user": "username",
    "password": "password",
    "driver": "org.postgresql.Driver"
}

# 대용량 테이블 읽기
large_table = read_from_database(spark, jdbc_url, "large_transactions", properties)

# 데이터 집계
summary = large_table \
    .groupBy("category") \
    .agg(
        count("*").alias("transaction_count"),
        spark_sum("amount").alias("total_amount"),
        avg("amount").alias("avg_amount")
    ) \
    .orderBy(col("total_amount").desc())

summary.show()

Delta Lake 활용

from delta.tables import DeltaTable

def read_delta_table(spark, delta_path):
    try:
        df = spark.read.format("delta").load(delta_path)
        return df
    except Exception as e:
        print(f"Delta 테이블 읽기 오류: {e}")
        return None

def incremental_data_collection(spark, delta_path, checkpoint_column, last_checkpoint):
    try:
        df = spark.read.format("delta").load(delta_path)
        incremental_df = df.filter(col(checkpoint_column) > last_checkpoint)
        return incremental_df
    except Exception as e:
        print(f"증분 데이터 수집 오류: {e}")
        return None

# 증분 데이터 수집
last_processed_timestamp = "2023-12-01 00:00:00"
new_data = incremental_data_collection(
    spark, 
    "/path/to/delta/table", 
    "updated_at", 
    last_processed_timestamp
)

데이터 웨어하우스 #

Amazon Redshift 연결 #

import redshift_connector
import pandas as pd

def connect_redshift(host, port, database, user, password):
    try:
        conn = redshift_connector.connect(
            host=host,
            port=port,
            database=database,
            user=user,
            password=password
        )
        return conn
    except Exception as e:
        print(f"Redshift 연결 오류: {e}")
        return None

def query_redshift(connection, query):
    try:
        df = pd.read_sql_query(query, connection)
        return df
    except Exception as e:
        print(f"Redshift 쿼리 오류: {e}")
        return None

# 복잡한 분석 쿼리 실행
complex_query = """
WITH monthly_metrics AS (
    SELECT 
        DATE_TRUNC('month', order_date) as month,
        customer_segment,
        COUNT(*) as order_count,
        SUM(total_amount) as revenue,
        AVG(total_amount) as avg_order_value
    FROM orders o
    JOIN customers c ON o.customer_id = c.customer_id
    WHERE order_date >= DATEADD(month, -12, CURRENT_DATE)
    GROUP BY 1, 2
),
growth_metrics AS (
    SELECT 
        *,
        LAG(revenue, 1) OVER (PARTITION BY customer_segment ORDER BY month) as prev_month_revenue,
        revenue / LAG(revenue, 1) OVER (PARTITION BY customer_segment ORDER BY month) - 1 as growth_rate
    FROM monthly_metrics
)
SELECT * FROM growth_metrics
ORDER BY month DESC, customer_segment
"""

conn = connect_redshift('cluster-endpoint', 5439, 'mydb', 'user', 'pass')
results = query_redshift(conn, complex_query)

Google BigQuery 연결 #

from google.cloud import bigquery
import pandas as pd

def create_bigquery_client(project_id, credentials_path=None):
    try:
        if credentials_path:
            client = bigquery.Client.from_service_account_json(
                credentials_path, 
                project=project_id
            )
        else:
            client = bigquery.Client(project=project_id)
        return client
    except Exception as e:
        print(f"BigQuery 클라이언트 생성 오류: {e}")
        return None

def query_bigquery(client, query):
    try:
        query_job = client.query(query)
        results = query_job.result()
        df = results.to_dataframe()
        return df
    except Exception as e:
        print(f"BigQuery 쿼리 오류: {e}")
        return None

# BigQuery 표준 SQL 사용
bq_query = """
SELECT 
    FORMAT_DATE('%Y-%m', date) as month,
    country,
    SUM(sessions) as total_sessions,
    SUM(pageviews) as total_pageviews,
    SUM(transactions) as total_transactions,
    SUM(revenue) as total_revenue
FROM `project.dataset.ga_sessions`
WHERE date >= DATE_SUB(CURRENT_DATE(), INTERVAL 6 MONTH)
GROUP BY 1, 2
ORDER BY 1 DESC, total_revenue DESC
"""

client = create_bigquery_client('my-project-id')
ga_data = query_bigquery(client, bq_query)

데이터 수집 최적화 #

파티셔닝 활용 #

def partition_aware_query(engine, table_name, partition_column, start_date, end_date):
    """
    파티션을 고려한 효율적인 쿼리
    """
    query = f"""
    SELECT *
    FROM {table_name}
    WHERE {partition_column} >= '{start_date}'
      AND {partition_column} < '{end_date}'
    """
    
    return pd.read_sql(query, engine)

# 파티션별 병렬 처리
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, timedelta

def parallel_partition_query(engine, table_name, partition_column, start_date, end_date, chunk_days=7):
    """
    파티션별 병렬 쿼리 실행
    """
    current_date = start_date
    date_ranges = []
    
    while current_date < end_date:
        next_date = min(current_date + timedelta(days=chunk_days), end_date)
        date_ranges.append((current_date, next_date))
        current_date = next_date
    
    def query_partition(date_range):
        return partition_aware_query(engine, table_name, partition_column, 
                                   date_range[0], date_range[1])
    
    with ThreadPoolExecutor(max_workers=4) as executor:
        results = list(executor.map(query_partition, date_ranges))
    
    return pd.concat(results, ignore_index=True)

쿼리 최적화 #

def analyze_query_performance(engine, query):
    """
    쿼리 성능 분석
    """
    import time
    
    # 실행 계획 분석
    explain_query = f"EXPLAIN ANALYZE {query}"
    
    start_time = time.time()
    with engine.connect() as conn:
        # 실행 계획 확인
        explain_result = conn.execute(explain_query)
        print("실행 계획:")
        for row in explain_result:
            print(row)
        
        # 실제 쿼리 실행
        result = pd.read_sql(query, conn)
    
    end_time = time.time()
    execution_time = end_time - start_time
    
    print(f"실행 시간: {execution_time:.2f}초")
    print(f"결과 행 수: {len(result)}")
    print(f"메모리 사용량: {result.memory_usage(deep=True).sum() / 1024 / 1024:.2f} MB")
    
    return result

인덱스 활용 #

-- 효율적인 인덱스 생성 예시
CREATE INDEX idx_orders_date_customer 
ON orders (order_date, customer_id);

-- 복합 인덱스를 활용한 쿼리
CREATE INDEX idx_products_category_price 
ON products (category, price DESC);

-- 부분 인덱스 (조건부 인덱스)
CREATE INDEX idx_active_users 
ON users (last_login_date) 
WHERE status = 'active';

실시간 데이터 수집 #

Change Data Capture (CDC) #

import time
from datetime import datetime

class DatabaseCDC:
    def __init__(self, engine, table_name, timestamp_column):
        self.engine = engine
        self.table_name = table_name
        self.timestamp_column = timestamp_column
        self.last_sync = datetime.min
        
    def get_changes(self):
        """
        마지막 동기화 이후 변경된 데이터 조회
        """
        query = f"""
        SELECT *
        FROM {self.table_name}
        WHERE {self.timestamp_column} > '{self.last_sync}'
        ORDER BY {self.timestamp_column}
        """
        
        changes = pd.read_sql(query, self.engine)
        
        if not changes.empty:
            self.last_sync = changes[self.timestamp_column].max()
        
        return changes
    
    def start_monitoring(self, callback_function, interval=60):
        """
        주기적으로 변경사항 모니터링
        """
        while True:
            try:
                changes = self.get_changes()
                if not changes.empty:
                    callback_function(changes)
                
                time.sleep(interval)
            except Exception as e:
                print(f"CDC 오류: {e}")
                time.sleep(interval)

def process_changes(changes):
    """
    변경된 데이터 처리
    """
    print(f"새로운 변경사항 {len(changes)}건 감지")
    # 데이터 처리 로직
    pass

# CDC 시작
cdc = DatabaseCDC(engine, 'orders', 'updated_at')
cdc.start_monitoring(process_changes, interval=30)

데이터베이스 트리거 활용 #

-- 데이터 변경 로그 테이블 생성
CREATE TABLE data_change_log (
    log_id SERIAL PRIMARY KEY,
    table_name VARCHAR(100),
    operation VARCHAR(10),
    record_id INT,
    changed_data JSONB,
    change_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 트리거 함수 생성
CREATE OR REPLACE FUNCTION log_data_changes()
RETURNS TRIGGER AS $$
BEGIN
    IF TG_OP = 'INSERT' THEN
        INSERT INTO data_change_log (table_name, operation, record_id, changed_data)
        VALUES (TG_TABLE_NAME, TG_OP, NEW.id, row_to_json(NEW));
        RETURN NEW;
    ELSIF TG_OP = 'UPDATE' THEN
        INSERT INTO data_change_log (table_name, operation, record_id, changed_data)
        VALUES (TG_TABLE_NAME, TG_OP, NEW.id, row_to_json(NEW));
        RETURN NEW;
    ELSIF TG_OP = 'DELETE' THEN
        INSERT INTO data_change_log (table_name, operation, record_id, changed_data)
        VALUES (TG_TABLE_NAME, TG_OP, OLD.id, row_to_json(OLD));
        RETURN OLD;
    END IF;
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

-- 트리거 생성
CREATE TRIGGER customers_change_trigger
    AFTER INSERT OR UPDATE OR DELETE
    ON customers
    FOR EACH ROW
    EXECUTE FUNCTION log_data_changes();

데이터 품질 모니터링 #

데이터 프로파일링 #

def profile_database_table(engine, table_name):
    """
    데이터베이스 테이블 프로파일링
    """
    # 기본 정보 조회
    basic_info_query = f"""
    SELECT 
        COUNT(*) as total_rows,
        COUNT(DISTINCT *) as unique_rows
    FROM {table_name}
    """
    
    basic_info = pd.read_sql(basic_info_query, engine)
    
    # 컬럼별 정보 조회
    column_info_query = f"""
    SELECT 
        column_name,
        data_type,
        is_nullable
    FROM information_schema.columns
    WHERE table_name = '{table_name}'
    """
    
    column_info = pd.read_sql(column_info_query, engine)
    
    # 각 컬럼의 통계 정보
    stats = {}
    for column in column_info['column_name']:
        stats_query = f"""
        SELECT 
            COUNT(*) as total_count,
            COUNT({column}) as non_null_count,
            COUNT(DISTINCT {column}) as unique_count
        FROM {table_name}
        """
        
        column_stats = pd.read_sql(stats_query, engine)
        stats[column] = column_stats.iloc[0].to_dict()
    
    return {
        'basic_info': basic_info,
        'column_info': column_info,
        'column_stats': stats
    }

데이터 품질 체크 #

def check_data_quality(engine, table_name, quality_rules):
    """
    데이터 품질 검사
    """
    quality_report = {}
    
    for rule_name, rule_query in quality_rules.items():
        try:
            result = pd.read_sql(rule_query.format(table_name=table_name), engine)
            quality_report[rule_name] = result
        except Exception as e:
            quality_report[rule_name] = f"오류: {e}"
    
    return quality_report

# 품질 규칙 정의
quality_rules = {
    'null_check': """
        SELECT 
            column_name,
            COUNT(*) as null_count,
            COUNT(*) * 100.0 / (SELECT COUNT(*) FROM {table_name}) as null_percentage
        FROM (
            SELECT column_name
            FROM information_schema.columns
            WHERE table_name = '{table_name}'
        ) cols
        CROSS JOIN {table_name}
        WHERE column_name IS NULL
        GROUP BY column_name
    """,
    'duplicate_check': """
        SELECT COUNT(*) as duplicate_count
        FROM (
            SELECT *, COUNT(*) as cnt
            FROM {table_name}
            GROUP BY *
            HAVING COUNT(*) > 1
        ) dups
    """
}

quality_report = check_data_quality(engine, 'customers', quality_rules)

데이터베이스에서의 데이터 수집은 구조화된 데이터에 대한 안정적이고 효율적인 접근을 제공한다. 적절한 연결 관리, 쿼리 최적화, 품질 모니터링을 통해 신뢰할 수 있는 데이터 수집 파이프라인을 구축할 수 있다.