KR_Memcached - somaz94/python-study GitHub Wiki

Python Memcached ๊ฐœ๋… ์ •๋ฆฌ


1๏ธโƒฃ ๊ธฐ๋ณธ ์—ฐ๊ฒฐ

Memcached ์„œ๋ฒ„์™€์˜ ๊ธฐ๋ณธ์ ์ธ ์—ฐ๊ฒฐ์„ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

from pymemcache.client.base import Client
from pymemcache.client.hash import HashClient
from typing import Any, Optional, Dict, List, Tuple
import json
import pickle
import socket

class MemcachedClient:
    def __init__(
        self,
        host: str = 'localhost',
        port: int = 11211,
        connect_timeout: float = 1.0,
        timeout: float = 0.5
    ):
        """Memcached ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”
        
        Args:
            host: Memcached ์„œ๋ฒ„ ํ˜ธ์ŠคํŠธ
            port: Memcached ์„œ๋ฒ„ ํฌํŠธ
            connect_timeout: ์—ฐ๊ฒฐ ์‹œ๊ฐ„ ์ œํ•œ(์ดˆ)
            timeout: ์ž‘์—… ์‹œ๊ฐ„ ์ œํ•œ(์ดˆ)
        """
        self.client = Client(
            (host, port),
            connect_timeout=connect_timeout,
            timeout=timeout
        )
    
    def ping(self) -> bool:
        """์„œ๋ฒ„ ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ"""
        try:
            return self.client.stats() is not None
        except (socket.timeout, ConnectionRefusedError) as e:
            print(f"์„œ๋ฒ„ ์—ฐ๊ฒฐ ์‹คํŒจ: {e}")
            return False
        except Exception as e:
            print(f"๊ธฐํƒ€ ์˜ค๋ฅ˜: {e}")
            return False
    
    def close(self):
        """์—ฐ๊ฒฐ ์ข…๋ฃŒ"""
        try:
            self.client.close()
            print("Memcached ์—ฐ๊ฒฐ์ด ์ •์ƒ์ ์œผ๋กœ ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค")
        except Exception as e:
            print(f"์—ฐ๊ฒฐ ์ข…๋ฃŒ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
    
    def get_stats(self) -> Dict:
        """์„œ๋ฒ„ ํ†ต๊ณ„ ์ •๋ณด ์กฐํšŒ"""
        try:
            return self.client.stats()
        except Exception as e:
            print(f"ํ†ต๊ณ„ ์ •๋ณด ์กฐํšŒ ์˜ค๋ฅ˜: {e}")
            return {}
    
    def flush_all(self) -> bool:
        """๋ชจ๋“  ์บ์‹œ ๋ฐ์ดํ„ฐ ์‚ญ์ œ"""
        try:
            return self.client.flush_all()
        except Exception as e:
            print(f"์บ์‹œ ์‚ญ์ œ ์˜ค๋ฅ˜: {e}")
            return False

โœ… ํŠน์ง•:

  • ์„œ๋ฒ„ ์—ฐ๊ฒฐ ๊ด€๋ฆฌ
  • ์ƒํƒœ ํ™•์ธ
  • ์—ฐ๊ฒฐ ์ข…๋ฃŒ ์ฒ˜๋ฆฌ
  • ํƒ€์ž„์•„์›ƒ ์„ค์ •
  • ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ
  • ์„œ๋ฒ„ ํ†ต๊ณ„ ์ •๋ณด ์กฐํšŒ

Memcached๋ž€?

Memcached๋Š” ๊ณ ์„ฑ๋Šฅ ๋ถ„์‚ฐ ๋ฉ”๋ชจ๋ฆฌ ์บ์‹ฑ ์‹œ์Šคํ…œ์œผ๋กœ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ค๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.

  • ์ธ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ถ€ํ•˜๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ
  • ๋ถ„์‚ฐ ์•„ํ‚คํ…์ฒ˜: ์—ฌ๋Ÿฌ ์„œ๋ฒ„์— ๊ฑธ์ณ ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ๊ตฌ์กฐ
  • ๊ฐ„๋‹จํ•œ ํ‚ค-๊ฐ’ ์ €์žฅ์†Œ: ๋ณต์žกํ•œ ์—ฐ์‚ฐ ์—†์ด ๋น ๋ฅธ ์•ก์„ธ์Šค ์ œ๊ณต
  • LRU(Least Recently Used): ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๋ถ€์กฑํ•  ๋•Œ ๊ฐ€์žฅ ์˜ค๋ž˜๋œ ํ•ญ๋ชฉ๋ถ€ํ„ฐ ์ œ๊ฑฐ
  • ์›์ž์  ์—ฐ์‚ฐ: ๋‹ค์ค‘ ์Šค๋ ˆ๋“œ/ํ”„๋กœ์„ธ์Šค ํ™˜๊ฒฝ์—์„œ ์•ˆ์ „ํ•œ ๋ฐ์ดํ„ฐ ์•ก์„ธ์Šค


2๏ธโƒฃ ๊ธฐ๋ณธ ์ž‘์—…

Memcached์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋Š” ๊ธฐ๋ณธ ์ž‘์—… ๋ฐฉ๋ฒ•์ด๋‹ค.

class MemcachedOperations:
    def __init__(self, client: MemcachedClient):
        self.client = client.client
    
    def set_value(
        self,
        key: str,
        value: Any,
        expire: int = 0,
        noreply: bool = False
    ) -> bool:
        """๊ฐ’ ์„ค์ •
        
        Args:
            key: ์ €์žฅํ•  ํ‚ค
            value: ์ €์žฅํ•  ๊ฐ’
            expire: ๋งŒ๋ฃŒ ์‹œ๊ฐ„(์ดˆ), 0์€ ๋งŒ๋ฃŒ ์—†์Œ
            noreply: ์„œ๋ฒ„ ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š์Œ
        
        Returns:
            bool: ์„ฑ๊ณต ์—ฌ๋ถ€
        """
        try:
            return self.client.set(
                key,
                pickle.dumps(value),
                expire=expire,
                noreply=noreply
            )
        except Exception as e:
            print(f"๊ฐ’ ์„ค์ • ์˜ค๋ฅ˜: {e}")
            return False
    
    def get_value(self, key: str) -> Optional[Any]:
        """๊ฐ’ ์กฐํšŒ
        
        Args:
            key: ์กฐํšŒํ•  ํ‚ค
            
        Returns:
            Optional[Any]: ์ €์žฅ๋œ ๊ฐ’ ๋˜๋Š” None
        """
        try:
            value = self.client.get(key)
            return pickle.loads(value) if value else None
        except Exception as e:
            print(f"๊ฐ’ ์กฐํšŒ ์˜ค๋ฅ˜: {e}")
            return None
    
    def delete(self, key: str, noreply: bool = False) -> bool:
        """๊ฐ’ ์‚ญ์ œ
        
        Args:
            key: ์‚ญ์ œํ•  ํ‚ค
            noreply: ์„œ๋ฒ„ ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š์Œ
            
        Returns:
            bool: ์„ฑ๊ณต ์—ฌ๋ถ€
        """
        try:
            return self.client.delete(key, noreply=noreply)
        except Exception as e:
            print(f"๊ฐ’ ์‚ญ์ œ ์˜ค๋ฅ˜: {e}")
            return False
    
    def add(
        self,
        key: str,
        value: Any,
        expire: int = 0
    ) -> bool:
        """ํ‚ค๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์„ ๋•Œ๋งŒ ๊ฐ’ ์ถ”๊ฐ€
        
        Args:
            key: ์ถ”๊ฐ€ํ•  ํ‚ค
            value: ์ถ”๊ฐ€ํ•  ๊ฐ’
            expire: ๋งŒ๋ฃŒ ์‹œ๊ฐ„(์ดˆ)
            
        Returns:
            bool: ์„ฑ๊ณต ์—ฌ๋ถ€
        """
        try:
            return self.client.add(
                key,
                pickle.dumps(value),
                expire=expire
            )
        except Exception as e:
            print(f"๊ฐ’ ์ถ”๊ฐ€ ์˜ค๋ฅ˜: {e}")
            return False
    
    def incr(self, key: str, value: int = 1) -> Optional[int]:
        """์ •์ˆ˜ ๊ฐ’ ์ฆ๊ฐ€
        
        Args:
            key: ์ฆ๊ฐ€์‹œํ‚ฌ ํ‚ค
            value: ์ฆ๊ฐ€๋Ÿ‰
            
        Returns:
            Optional[int]: ์ฆ๊ฐ€ ํ›„ ๊ฐ’ ๋˜๋Š” None
        """
        try:
            return self.client.incr(key, value)
        except Exception as e:
            print(f"๊ฐ’ ์ฆ๊ฐ€ ์˜ค๋ฅ˜: {e}")
            return None
    
    def decr(self, key: str, value: int = 1) -> Optional[int]:
        """์ •์ˆ˜ ๊ฐ’ ๊ฐ์†Œ
        
        Args:
            key: ๊ฐ์†Œ์‹œํ‚ฌ ํ‚ค
            value: ๊ฐ์†Œ๋Ÿ‰
            
        Returns:
            Optional[int]: ๊ฐ์†Œ ํ›„ ๊ฐ’ ๋˜๋Š” None
        """
        try:
            return self.client.decr(key, value)
        except Exception as e:
            print(f"๊ฐ’ ๊ฐ์†Œ ์˜ค๋ฅ˜: {e}")
            return None
    
    def get_multi(self, keys: List[str]) -> Dict[str, Any]:
        """์—ฌ๋Ÿฌ ํ‚ค ๊ฐ’ ๋™์‹œ ์กฐํšŒ
        
        Args:
            keys: ์กฐํšŒํ•  ํ‚ค ๋ชฉ๋ก
            
        Returns:
            Dict[str, Any]: ํ‚ค-๊ฐ’ ์Œ ๋”•์…”๋„ˆ๋ฆฌ
        """
        try:
            values = self.client.get_multi(keys)
            return {k: pickle.loads(v) if v else None for k, v in values.items()}
        except Exception as e:
            print(f"๋‹ค์ค‘ ๊ฐ’ ์กฐํšŒ ์˜ค๋ฅ˜: {e}")
            return {}

โœ… ํŠน์ง•:

  • ๋ฐ์ดํ„ฐ ์ €์žฅ/์กฐํšŒ
  • ์ง๋ ฌํ™” ์ฒ˜๋ฆฌ
  • ์—๋Ÿฌ ์ฒ˜๋ฆฌ
  • ์›์ž์  ์ฆ๊ฐ€/๊ฐ์†Œ
  • ๋‹ค์ค‘ ํ‚ค ์กฐํšŒ
  • ์กฐ๊ฑด๋ถ€ ์ถ”๊ฐ€

Memcached ์ž‘์—… ํƒ€์ž…:

Memcached์—์„œ ์ง€์›ํ•˜๋Š” ๋‹ค์–‘ํ•œ ์ž‘์—… ํƒ€์ž…๊ณผ ํŠน์ง•์ด๋‹ค.

์ž‘์—… ์„ค๋ช… ํŠน์ง•
set ๊ฐ’ ์„ค์ • ํ‚ค๊ฐ€ ์กด์žฌํ•˜๋ฉด ๋ฎ์–ด์”€
get ๊ฐ’ ์กฐํšŒ ์—†์œผ๋ฉด None ๋ฐ˜ํ™˜
delete ๊ฐ’ ์‚ญ์ œ ์„ฑ๊ณต ์—ฌ๋ถ€ ๋ฐ˜ํ™˜
add ์ƒˆ ๊ฐ’ ์ถ”๊ฐ€ ํ‚ค๊ฐ€ ์กด์žฌํ•˜๋ฉด ์‹คํŒจ
replace ๊ฐ’ ๋Œ€์ฒด ํ‚ค๊ฐ€ ์กด์žฌํ•ด์•ผ ์„ฑ๊ณต
incr ๊ฐ’ ์ฆ๊ฐ€ ์ •์ˆ˜ ๊ฐ’๋งŒ ๊ฐ€๋Šฅ
decr ๊ฐ’ ๊ฐ์†Œ ์ •์ˆ˜ ๊ฐ’๋งŒ ๊ฐ€๋Šฅ
append ๊ฐ’ ๋’ค์— ์ถ”๊ฐ€ ๊ธฐ์กด ๊ฐ’ ํ•„์š”
prepend ๊ฐ’ ์•ž์— ์ถ”๊ฐ€ ๊ธฐ์กด ๊ฐ’ ํ•„์š”
get_multi ๋‹ค์ค‘ ํ‚ค ์กฐํšŒ ๋„คํŠธ์›Œํฌ ์ตœ์ ํ™”


3๏ธโƒฃ ์บ์‹ฑ ๊ตฌํ˜„

ํ•จ์ˆ˜ ๊ฒฐ๊ณผ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์บ์‹ฑํ•˜๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ํŒจํ„ด ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์ด๋‹ค.

from functools import wraps
import hashlib
import time
import inspect
import json

class MemcachedCache:
    def __init__(self, client: MemcachedClient):
        self.ops = MemcachedOperations(client)
    
    def _generate_cache_key(self, func, args, kwargs):
        """ํ•จ์ˆ˜ ํ˜ธ์ถœ์— ๋Œ€ํ•œ ๊ณ ์œ  ์บ์‹œ ํ‚ค ์ƒ์„ฑ"""
        # ํ•จ์ˆ˜ ์ด๋ฆ„๊ณผ ๋ชจ๋“ˆ ํฌํ•จ
        key_parts = [func.__module__, func.__name__]
        
        # ์œ„์น˜ ์ธ์ž ์ถ”๊ฐ€
        for arg in args:
            key_parts.append(str(arg))
        
        # ํ‚ค์›Œ๋“œ ์ธ์ž ์ถ”๊ฐ€ (์ •๋ ฌํ•˜์—ฌ ์ผ๊ด€์„ฑ ๋ณด์žฅ)
        sorted_kwargs = sorted(kwargs.items())
        for k, v in sorted_kwargs:
            key_parts.append(f"{k}={v}")
        
        # ํ‚ค ๋ฌธ์ž์—ด ์ƒ์„ฑ ๋ฐ ํ•ด์‹œ
        key_str = ":".join(key_parts)
        return f"cache:{hashlib.md5(key_str.encode()).hexdigest()}"
    
    def cache_decorator(self, expire: int = 300):
        """ํ•จ์ˆ˜ ๊ฒฐ๊ณผ๋ฅผ ์บ์‹œํ•˜๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ
        
        Args:
            expire: ์บ์‹œ ๋งŒ๋ฃŒ ์‹œ๊ฐ„(์ดˆ)
            
        Returns:
            Callable: ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜
        """
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                # ์บ์‹œ ํ‚ค ์ƒ์„ฑ
                key = self._generate_cache_key(func, args, kwargs)
                
                # ์บ์‹œ๋œ ๊ฒฐ๊ณผ ํ™•์ธ
                cached_result = self.ops.get_value(key)
                if cached_result is not None:
                    return cached_result
                
                # ์บ์‹œ ๋ฏธ์Šค: ํ•จ์ˆ˜ ์‹คํ–‰ ๋ฐ ๊ฒฐ๊ณผ ์บ์‹ฑ
                start_time = time.time()
                result = func(*args, **kwargs)
                exec_time = time.time() - start_time
                
                # ๊ฒฐ๊ณผ ์บ์‹ฑ
                self.ops.set_value(key, result, expire)
                
                print(f"์บ์‹œ ์ €์žฅ: {key} (์‹คํ–‰ ์‹œ๊ฐ„: {exec_time:.4f}์ดˆ)")
                return result
            return wrapper
        return decorator
    
    def invalidate_cache(self, func, *args, **kwargs):
        """ํŠน์ • ํ•จ์ˆ˜ ํ˜ธ์ถœ์— ๋Œ€ํ•œ ์บ์‹œ ๋ฌดํšจํ™”
        
        Args:
            func: ์บ์‹œ๋ฅผ ๋ฌดํšจํ™”ํ•  ํ•จ์ˆ˜
            *args, **kwargs: ํ•จ์ˆ˜ ์ธ์ž
            
        Returns:
            bool: ์„ฑ๊ณต ์—ฌ๋ถ€
        """
        key = self._generate_cache_key(func, args, kwargs)
        return self.ops.delete(key)
    
    def memoize(self, expire: int = 300):
        """๋ฉ”์„œ๋“œ ๊ฒฐ๊ณผ๋ฅผ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ํ•˜๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ
        
        Args:
            expire: ์บ์‹œ ๋งŒ๋ฃŒ ์‹œ๊ฐ„(์ดˆ)
            
        Returns:
            Callable: ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜
        """
        return self.cache_decorator(expire)

โœ… ํŠน์ง•:

  • ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ํŒจํ„ด
  • ํ‚ค ์ƒ์„ฑ ๋กœ์ง
  • ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ๊ด€๋ฆฌ
  • ์บ์‹œ ๋ฌดํšจํ™”
  • ์‹คํ–‰ ์‹œ๊ฐ„ ์ธก์ •
  • ๋ฉ”๋ชจ์ด์ œ์ด์…˜

์บ์‹ฑ ํ™œ์šฉ ์‚ฌ๋ก€:

Memcached ์บ์‹ฑ์ด ํšจ๊ณผ์ ์ธ ์ผ๋ฐ˜์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€์ด๋‹ค.

  1. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ: ์ž์ฃผ ์‹คํ–‰๋˜๋Š” ์ฟผ๋ฆฌ์˜ ๊ฒฐ๊ณผ ์บ์‹ฑ
  2. API ์‘๋‹ต: ์™ธ๋ถ€ API ํ˜ธ์ถœ ๊ฒฐ๊ณผ ์บ์‹ฑ์œผ๋กœ API ์ œํ•œ ๋ฐ ์ง€์—ฐ ์‹œ๊ฐ„ ๊ฐ์†Œ
  3. ์„ธ์…˜ ๋ฐ์ดํ„ฐ: ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์‚ฌ์šฉ์ž ์„ธ์…˜ ์ •๋ณด ์ €์žฅ
  4. ํŽ˜์ด์ง€ ๋ Œ๋”๋ง: ๋™์  ์›น ํŽ˜์ด์ง€์˜ HTML ์ถœ๋ ฅ ์บ์‹ฑ
  5. ๊ณ„์‚ฐ ๋น„์šฉ์ด ๋†’์€ ์ž‘์—…: ๋ณต์žกํ•œ ์—ฐ์‚ฐ ๊ฒฐ๊ณผ ์บ์‹ฑ
  6. ์ ‘๊ทผ ์ œ์–ด ํ† ํฐ: ์ž„์‹œ ์•ก์„ธ์Šค ํ† ํฐ ๋ฐ ์ธ์ฆ ์ •๋ณด ์ €์žฅ


4๏ธโƒฃ ์„ธ์…˜ ๊ด€๋ฆฌ

Memcached๋ฅผ ์‚ฌ์šฉํ•œ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ธ์…˜ ๊ด€๋ฆฌ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์ด๋‹ค.

import uuid
from datetime import datetime, timedelta
import json
from typing import Optional, Dict, Any

class MemcachedSession:
    def __init__(self, client: MemcachedClient):
        self.ops = MemcachedOperations(client)
        self.session_prefix = "session:"
    
    def create_session(
        self,
        user_id: str,
        data: dict,
        expire: int = 3600
    ) -> Optional[str]:
        """์ƒˆ ์„ธ์…˜ ์ƒ์„ฑ
        
        Args:
            user_id: ์‚ฌ์šฉ์ž ID
            data: ์„ธ์…˜์— ์ €์žฅํ•  ๋ฐ์ดํ„ฐ
            expire: ์„ธ์…˜ ๋งŒ๋ฃŒ ์‹œ๊ฐ„(์ดˆ)
            
        Returns:
            Optional[str]: ์ƒ์„ฑ๋œ ์„ธ์…˜ ID ๋˜๋Š” None
        """
        session_id = str(uuid.uuid4())
        session_key = f"{self.session_prefix}{session_id}"
        
        # ์„ธ์…˜ ๋ฐ์ดํ„ฐ ๊ตฌ์„ฑ
        session_data = {
            "user_id": user_id,
            "created_at": datetime.now().isoformat(),
            "last_accessed": datetime.now().isoformat(),
            "expire_at": (datetime.now() + timedelta(seconds=expire)).isoformat(),
            "data": data
        }
        
        # ์„ธ์…˜ ์ €์žฅ
        if self.ops.set_value(session_key, session_data, expire):
            return session_id
        return None
    
    def get_session(self, session_id: str) -> Optional[Dict[str, Any]]:
        """์„ธ์…˜ ์กฐํšŒ
        
        Args:
            session_id: ์„ธ์…˜ ID
            
        Returns:
            Optional[Dict[str, Any]]: ์„ธ์…˜ ๋ฐ์ดํ„ฐ ๋˜๋Š” None
        """
        session_key = f"{self.session_prefix}{session_id}"
        session_data = self.ops.get_value(session_key)
        
        if session_data:
            # ๋งˆ์ง€๋ง‰ ์ ‘๊ทผ ์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ
            session_data["last_accessed"] = datetime.now().isoformat()
            self.ops.set_value(session_key, session_data)
        
        return session_data
    
    def update_session(
        self,
        session_id: str,
        data: Dict[str, Any],
        extend_expire: bool = True,
        expire: int = 3600
    ) -> bool:
        """์„ธ์…˜ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ
        
        Args:
            session_id: ์„ธ์…˜ ID
            data: ์—…๋ฐ์ดํŠธํ•  ๋ฐ์ดํ„ฐ
            extend_expire: ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ์—ฐ์žฅ ์—ฌ๋ถ€
            expire: ์ƒˆ ๋งŒ๋ฃŒ ์‹œ๊ฐ„(์ดˆ)
            
        Returns:
            bool: ์„ฑ๊ณต ์—ฌ๋ถ€
        """
        session_key = f"{self.session_prefix}{session_id}"
        session_data = self.ops.get_value(session_key)
        
        if not session_data:
            return False
        
        # ์„ธ์…˜ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ
        session_data["data"].update(data)
        session_data["last_accessed"] = datetime.now().isoformat()
        
        if extend_expire:
            session_data["expire_at"] = (datetime.now() + timedelta(seconds=expire)).isoformat()
            return self.ops.set_value(session_key, session_data, expire)
        else:
            return self.ops.set_value(session_key, session_data)
    
    def delete_session(self, session_id: str) -> bool:
        """์„ธ์…˜ ์‚ญ์ œ
        
        Args:
            session_id: ์„ธ์…˜ ID
            
        Returns:
            bool: ์„ฑ๊ณต ์—ฌ๋ถ€
        """
        session_key = f"{self.session_prefix}{session_id}"
        return self.ops.delete(session_key)
    
    def is_valid_session(self, session_id: str) -> bool:
        """์„ธ์…˜ ์œ ํšจ์„ฑ ํ™•์ธ
        
        Args:
            session_id: ์„ธ์…˜ ID
            
        Returns:
            bool: ์œ ํšจ ์—ฌ๋ถ€
        """
        session_data = self.get_session(session_id)
        return session_data is not None

โœ… ํŠน์ง•:

  • ์„ธ์…˜ ์‹๋ณ„์ž ์ƒ์„ฑ
  • ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ์„ค์ •
  • ์„ธ์…˜ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ
  • ์ž๋™ ๊ฐฑ์‹ 
  • ์œ ํšจ์„ฑ ๊ฒ€์ฆ
  • ์‚ฌ์šฉ์ž ์—ฐ๊ฒฐ


5๏ธโƒฃ ๋ถ„์‚ฐ ์บ์‹œ

์—ฌ๋Ÿฌ Memcached ์„œ๋ฒ„๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ถ„์‚ฐ ์บ์‹œ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์ด๋‹ค.

from typing import List, Tuple, Dict, Any, Optional
import hashlib

class DistributedCache:
    def __init__(self, servers: List[Tuple[str, int]]):
        """์—ฌ๋Ÿฌ Memcached ์„œ๋ฒ„ ์—ฐ๊ฒฐ
        
        Args:
            servers: (ํ˜ธ์ŠคํŠธ, ํฌํŠธ) ํ˜•ํƒœ์˜ ์„œ๋ฒ„ ๋ชฉ๋ก
        """
        self.clients = [
            MemcachedClient(host, port)
            for host, port in servers
        ]
        self.n_servers = len(servers)
        self.server_info = servers
    
    def _get_client(self, key: str) -> MemcachedOperations:
        """ํ•ด์‹œ ๊ธฐ๋ฐ˜์œผ๋กœ ์„œ๋ฒ„ ์„ ํƒ
        
        Args:
            key: ํ‚ค
            
        Returns:
            MemcachedOperations: ์„ ํƒ๋œ ์„œ๋ฒ„์˜ ์ž‘์—… ๊ฐ์ฒด
        """
        hash_value = int(hashlib.md5(key.encode()).hexdigest(), 16)
        server_index = hash_value % self.n_servers
        return MemcachedOperations(self.clients[server_index])
    
    def set(self, key: str, value: Any, expire: int = 0) -> bool:
        """๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ ๊ฐ’ ์„ค์ •
        
        Args:
            key: ํ‚ค
            value: ๊ฐ’
            expire: ๋งŒ๋ฃŒ ์‹œ๊ฐ„(์ดˆ)
            
        Returns:
            bool: ์„ฑ๊ณต ์—ฌ๋ถ€
        """
        client = self._get_client(key)
        return client.set_value(key, value, expire)
    
    def get(self, key: str) -> Optional[Any]:
        """๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ ๊ฐ’ ์กฐํšŒ
        
        Args:
            key: ํ‚ค
            
        Returns:
            Optional[Any]: ๊ฐ’ ๋˜๋Š” None
        """
        client = self._get_client(key)
        return client.get_value(key)
    
    def delete(self, key: str) -> bool:
        """๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ ๊ฐ’ ์‚ญ์ œ
        
        Args:
            key: ํ‚ค
            
        Returns:
            bool: ์„ฑ๊ณต ์—ฌ๋ถ€
        """
        client = self._get_client(key)
        return client.delete(key)
    
    def get_stats_all(self) -> Dict[Tuple[str, int], Dict]:
        """๋ชจ๋“  ์„œ๋ฒ„์˜ ํ†ต๊ณ„ ์ •๋ณด ์กฐํšŒ
        
        Returns:
            Dict: ์„œ๋ฒ„๋ณ„ ํ†ต๊ณ„ ์ •๋ณด
        """
        stats = {}
        for i, client in enumerate(self.clients):
            stats[self.server_info[i]] = client.get_stats()
        return stats
    
    def close_all(self):
        """๋ชจ๋“  ์„œ๋ฒ„ ์—ฐ๊ฒฐ ์ข…๋ฃŒ"""
        for client in self.clients:
            client.close()

โœ… ํŠน์ง•:

  • ๋‹ค์ค‘ ์„œ๋ฒ„ ๊ด€๋ฆฌ
  • ํ•ด์‹œ ๊ธฐ๋ฐ˜ ๋ถ„์‚ฐ
  • ํ™•์žฅ์„ฑ ์ง€์›
  • ์„œ๋ฒ„ ์„ ํƒ ์•Œ๊ณ ๋ฆฌ์ฆ˜
  • ํ†ตํ•ฉ ์ธํ„ฐํŽ˜์ด์Šค
  • ๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ

๋ถ„์‚ฐ ์บ์‹œ ์•Œ๊ณ ๋ฆฌ์ฆ˜:

Memcached์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์ฃผ์š” ๋ถ„์‚ฐ ์•Œ๊ณ ๋ฆฌ์ฆ˜๊ณผ ํŠน์ง•์ด๋‹ค.

  1. ์ผ๊ด€๋œ ํ•ด์‹ฑ(Consistent Hashing): ์„œ๋ฒ„ ์ถ”๊ฐ€/์ œ๊ฑฐ ์‹œ ์ตœ์†Œํ•œ์˜ ํ‚ค ์žฌ๋ฐฐ์น˜
  2. ๋ชจ๋“ˆ๋Ÿฌ ํ•ด์‹ฑ(Modular Hashing): ํ‚ค ํ•ด์‹œ๋ฅผ ์„œ๋ฒ„ ์ˆ˜๋กœ ๋‚˜๋ˆˆ ๋‚˜๋จธ์ง€๋กœ ์„œ๋ฒ„ ๊ฒฐ์ •
  3. ๊ฐ€์ค‘์น˜ ๊ธฐ๋ฐ˜ ๋ฐฐํฌ: ์„œ๋ฒ„ ์šฉ๋Ÿ‰์— ๋น„๋ก€ํ•˜์—ฌ ํ‚ค ๋ฐฐํฌ
  4. ๋ ˆํ”Œ๋ฆฌ์นด(Replica): ๋™์ผํ•œ ํ‚ค๋ฅผ ์—ฌ๋Ÿฌ ์„œ๋ฒ„์— ๋ณต์ œํ•˜์—ฌ ๊ฐ€์šฉ์„ฑ ํ–ฅ์ƒ
  5. ์ƒค๋”ฉ(Sharding): ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„ํ• ํ•˜์—ฌ ์—ฌ๋Ÿฌ ์„œ๋ฒ„์— ๋ถ„์‚ฐ


์ฃผ์š” ํŒ

โœ… ๋ชจ๋ฒ” ์‚ฌ๋ก€:

  • ํ‚ค ๋„ค์ด๋ฐ ์ „๋žต ์ˆ˜๋ฆฝ: ๊ด€๋ จ ๋ฐ์ดํ„ฐ๋ฅผ ๊ทธ๋ฃนํ™”ํ•˜๋Š” ์ผ๊ด€๋œ ํ‚ค ์ ‘๋‘์‚ฌ ์‚ฌ์šฉ
  • ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ์ ์ ˆํžˆ ์„ค์ •: ๋ฐ์ดํ„ฐ ์œ ํ˜•๊ณผ ๊ฐฑ์‹  ๋นˆ๋„์— ๋งž๊ฒŒ ์„ค์ •
  • ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๋ชจ๋‹ˆํ„ฐ๋ง: ๊ณผ๋„ํ•œ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ์œผ๋กœ ์ธํ•œ ํ•ญ๋ชฉ ์ œ๊ฑฐ ๋ฐฉ์ง€
  • ๋ถ„์‚ฐ ์ฒ˜๋ฆฌ ๊ณ ๋ ค: ์ผ๊ด€๋œ ํ•ด์‹ฑ์„ ํ†ตํ•œ ํšจ์œจ์ ์ธ ์„œ๋ฒ„ ํ™•์žฅ
  • ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๊ตฌํ˜„: ๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜์™€ ์„œ๋ฒ„ ๋‹ค์šด์— ๋Œ€ํ•œ ๋Œ€๋น„
  • ์„ฑ๋Šฅ ์ตœ์ ํ™”: ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™” ๋น„์šฉ ์ตœ์†Œํ™” ๋ฐ ๋ฐฐ์น˜ ์ž‘์—… ํ™œ์šฉ
  • ๋ฐฑ์—… ์ „๋žต ์ˆ˜๋ฆฝ: ์ฃผ์š” ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ๋ฐฑ์—… ๋ฉ”์ปค๋‹ˆ์ฆ˜ ๊ตฌํ˜„
  • ๋ณด์•ˆ ์„ค์ • ํ™•์ธ: ๋ฐฉํ™”๋ฒฝ ์„ค์ • ๋ฐ ์ธ์ฆ ๋ฉ”์ปค๋‹ˆ์ฆ˜ ํ™œ์„ฑํ™”
  • ๋Œ€์šฉ๋Ÿ‰ ๊ฐ์ฒด ๋ถ„ํ• : ํฐ ๊ฐ์ฒด๋Š” ์—ฌ๋Ÿฌ ์กฐ๊ฐ์œผ๋กœ ๋‚˜๋ˆ„์–ด ์ €์žฅ
  • ํ•ซํ‚ค ๋ฌธ์ œ ํ•ด๊ฒฐ: ์ž์ฃผ ์ ‘๊ทผํ•˜๋Š” ํ‚ค์˜ ๋ถ€ํ•˜ ๋ถ„์‚ฐ ์ „๋žต ์ˆ˜๋ฆฝ


โš ๏ธ **GitHub.com Fallback** โš ๏ธ