A2A 메모리 레이어: AI 에이전트를 위한 공유 두뇌 구축하기

A2A 메모리 레이어: AI 에이전트를 위한 공유 두뇌 구축하기

모든 것의 시작이 된 문제

우리를 계속 괴롭히던 것이 있었습니다: 왜 AI 에이전트는 대화가 끝나는 순간 모든 것을 잊어버릴까요?

한번 생각해보세요. 챗봇에게 전화보다 이메일을 선호한다고 말합니다. 다음 세션에서는요? 마치 처음 만난 것처럼 행동합니다. 한 상담원에게 주문 문제를 설명하고, 다른 상담원에게 연결되면, 갑자기 처음부터 다시 시작해야 합니다.

우리 모두 겪어본 일입니다. 사용자로서 답답하고, 솔직히 엔지니어로서는 매력적인 문제입니다.

그래서 우리는 스스로에게 물었습니다: 만약 AI 에이전트가 실제로 기억할 수 있다면? 그리고 단순히 기억하는 것을 넘어서, 서로 배운 것을 공유할 수 있다면?

이것이 A2A 메모리 레이어가 탄생한 배경입니다.


잠깐, 정확히 무엇을 만드는 건가요?

기술적인 내용으로 들어가기 전에, 이 프로젝트가 실제로 무엇을 하는지 명확히 해봅시다:

A2A 메모리 레이어는 여러 AI 에이전트가 다음을 할 수 있게 하는 공유 메모리 시스템입니다:

  • 대화를 넘어서 사실을 기억 (단일 채팅 내에서만이 아닌)
  • 실시간으로 서로 지식을 공유
  • 전문 지식을 결합하여 복잡한 작업에 협력
  • 수동으로 정보를 복사하지 않고도 동기화 유지

다음과 같은 고객 서비스 팀을 상상해보세요:

  • Alex (코디네이터)는 문제를 라우팅하는 방법을 알고 있습니다
  • Sam (주문 전문가)은 배송과 환불에 대해 모든 것을 알고 있습니다
  • Jordan (기술 지원)은 모든 시스템 문제를 해결할 수 있습니다

이제 이들이 모두 같은 노트북을 공유한다고 상상해보세요. Sam이 고객 #12345가 이메일을 선호한다는 것을 알게 되면, Alex와 Jordan도 즉시 알게 됩니다. 이것이 우리가 만든 것입니다.


그런데 왜 이걸 만들었나요? LLM에는 이미 메모리가 있지 않나요?

좋은 질문입니다. 그리고 그 답은 LLM이 어떻게 작동하는지에 대한 근본적인 것을 드러냅니다.

LLM은 상태가 없습니다(stateless). 메시지를 보낼 때마다 모델은 새롭게 시작합니다. 명시적으로 알려주지 않는 한 이전 대화에서 무슨 일이 있었는지 전혀 모릅니다. 흔히 듣는 컨텍스트 윈도우? 그것은 메모리가 아닙니다—매 세션이 끝나면 지워지는 메모장에 더 가깝습니다.

Google의 "Context Engineering" 백서(2025년 11월)는 이 문제를 아름답게 정리했습니다. 그들은 다음을 구분합니다:

  • 세션(Sessions): 단일 대화를 위한 임시 메모장
  • 메모리(Memory): 세션을 넘어 지속되는 영구적인 지식

대부분의 챗봇은 세션만 처리합니다. 우리는 둘 다 다루고 싶었고—여기에 세 번째 차원을 추가했습니다: 여러 에이전트 간에 메모리를 공유하는 것.


아키텍처: 이것은 실제로 어떻게 작동하나요?

자, 이제 흥미로운 부분으로 들어가봅시다. AI 에이전트를 위한 공유 두뇌를 어떻게 구축할까요?

전체 그림

┌─────────────────────────────────────────────────────────────┐
│                    사용자가 보는 것 (프론트엔드)                   │
│         크기 조절 가능한 패널이 있는 Next.js 인터페이스               │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                      API 게이트웨이                            │
│               Flask REST API (15개 엔드포인트)                  │
└──────────────────────────┬──────────────────────────────────┘
                           │
        ┌──────────────────┼──────────────────┐
        ▼                  ▼                  ▼
┌──────────────┐   ┌──────────────┐   ┌──────────────┐
│    세션      │   │    메모리     │   │  A2A 브릿지      │
│   매니저     │   │    매니저     │   │   프로토콜        │
└──────────────┘   └──────────────┘   └──────────────┘
        │                  │                  │
        └──────────────────┼──────────────────┘
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                       공유 두뇌                               │
│       벡터 스토어 + 메모리 유형 + 에이전트 간 동기화                  │
└─────────────────────────────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                   Ollama + phi4-mini                        │
│                 128k 컨텍스트 윈도우 LLM                        │
└─────────────────────────────────────────────────────────────┘

왜 이런 컴포넌트들인가요?

세션 매니저 — "방금 무슨 이야기를 했지?"

단기적인 것들을 처리합니다. 에이전트와 채팅할 때, 세션 매니저는 대화를 추적하고, 토큰을 세고(128k 컨텍스트 윈도우도 무한하지 않으니까요), 내용이 너무 길어지면 무엇을 유지할지 결정합니다.

메모리 매니저 — "영원히 기억해야 할 것은 무엇인가?"

대화의 모든 것이 기억할 가치가 있는 것은 아닙니다. 메모리 매니저는 중요한 부분—고객 선호도, 해결된 문제, 학습된 절차—을 추출하고 장기간 저장합니다.

A2A 브릿지 — "다른 에이전트들아, 너희도 이거 알아야 해."

이것이 마법의 소스입니다. 한 에이전트가 가치 있는 것을 배우면, A2A 브릿지는 그것이 필요할 수 있는 다른 에이전트들에게 전파합니다. 팀의 단체 채팅과 같지만, 지식을 위한 것입니다.


메모리 유형: 모든 기억이 동등하게 만들어지지는 않습니다

인지 과학에서 배운 것이 있습니다: 인간에게는 다양한 유형의 기억이 있고, 우리 에이전트도 그래야 합니다.

선언적 메모리: "내가 아는 것은 무엇인가?"

이것들은 사실입니다. 정적인 지식. 맥락과 관계없이 참인 것들.

하위 유형저장하는 것예시
의미적(Semantic)일반 지식"프리미엄 고객은 48시간 환불을 받습니다"
개체(Entity)특정 대상에 대한 사실"고객 #12345는 이메일을 선호합니다"
일화적(Episodic)특정 사건"1월 5일에 John의 배송 문제를 해결했습니다"

절차적 메모리: "어떻게 하는 거지?"

이것들은 기술과 워크플로우입니다. 작업을 수행하는 방법에 대한 단계별 지식.

하위 유형저장하는 것예시
기술(Skill)도구 사용법"반품 처리에는 refund_api를 사용합니다"
워크플로우(Workflow)다단계 프로세스"1) 주문 확인 → 2) 자격 확인 → 3) 환불 처리"

왜 이 구분이 중요한가요?

메모리를 검색하고 사용하는 방식에 영향을 미치기 때문입니다. 고객이 "환불 정책이 뭐예요?"라고 물으면, 선언적 메모리를 검색합니다. "환불 받고 싶어요"라고 말하면, 워크플로우를 위한 절차적 메모리도 가져옵니다.

다른 질문에는 다른 유형의 지식이 필요합니다. 우리 시스템은 그 차이를 알고 있습니다.


세션 문제: 대화가 길어지면 어떻게 되나요?

우리가 해결해야 했던 실질적인 문제가 있습니다: LLM은 제한된 컨텍스트 윈도우를 가지고 있습니다.

phi4-mini의 인상적인 128k 토큰 컨텍스트도 결국 가득 찹니다. 단일 고객 상호작용이 수천 개의 토큰을 생성할 수 있습니다. 여러 에이전트가 협업하면, 메모리 관리 문제가 생깁니다.

우리의 해결책: 압축 전략

세 가지 접근 방식을 구현했습니다 (그리고 서로 교체할 수 있습니다):

1. 슬라이딩 윈도우 마지막 N개의 메시지를 유지하고, 이전 것은 모두 삭제합니다.

  • 장점: 간단하고, 예측 가능
  • 단점: 중요한 초기 맥락을 잃을 수 있음

2. 토큰 기반 잘라내기 토큰 수를 모니터링하고, 한계에 가까워지면 가장 오래된 내용을 제거합니다.

  • 장점: 컨텍스트 사용을 최대화
  • 단점: 정확한 토큰 계산이 필요

3. 요약 오래된 대화 청크를 AI가 생성한 요약으로 대체합니다.

  • 장점: 토큰을 줄이면서 의미를 보존
  • 단점: 요약이 뉘앙스를 놓칠 수 있음
# 예시: 압축을 트리거하는 방법
if session.token_count > (self.max_tokens * 0.8):  # 80% 임계값
    self.trigger_compaction(session_id)

핵심 통찰? 공간이 다 찰 때까지 기다리지 마세요. 80% 용량에서 압축을 시작하여 시스템에 여유 공간을 줍니다.


A2A 프로토콜: 에이전트들이 실제로 어떻게 대화하나요?

여기서 정말 흥미로워집니다. 독립적인 AI 에이전트들이 어떻게 협력하게 할까요?

메시지 흐름

고객이 지원팀에 연락할 때 어떤 일이 일어나는지 살펴봅시다:

1. 고객: "주문이 도착하지 않았어요. 프리미엄 회원이에요."
                    │
                    ▼
2. Alex (코디네이터)가 메시지를 받음
   └── 공유 메모리 조회: "프리미엄 고객에 대해 뭘 알고 있지?"
   └── 발견: "프리미엄 회원은 48시간 빠른 환불을 받습니다"
   └── 결정: "Sam(주문)과 Jordan(기술 확인)이 필요해"
                    │
                    ▼
3. Alex가 전문가들에게 A2A 메시지 전송:
   ├── Sam에게: "이 고객의 주문 상태 확인해줘"
   └── Jordan에게: "배송 시스템이 작동하는지 확인해줘"
                    │
                    ▼
4. 전문가들이 병렬로 작업:
   ├── Sam: 주문 이력 접근, 배송 지연 발견
   └── Jordan: 시스템 정상 작동 확인, 광범위한 문제 없음
                    │
                    ▼
5. 결과가 Alex에게 돌아옴
   └── Alex 종합: "주문이 지연됨 (시스템 전체 문제 아님), 
                   고객은 빠른 환불 자격이 있음"
                    │
                    ▼
6. Alex가 전체 맥락을 가지고 고객에게 응답

메모리 동기화 메커니즘

Sam이 이 고객에게 이전에도 배송 문제가 있었다는 것을 발견하면, 그 정보는 공유 메모리에 들어갑니다:

# 이것은 작업 처리 중 자동으로 발생합니다
new_memory = Memory(
    content="고객 #12345 배송 지연 경험 - 3번째 발생",
    type=MemoryType.EPISODIC,
    scope=MemoryScope.SHARED,  # <-- 모든 에이전트에게 보이게 함
    confidence=0.95
)
memory_manager.add_memory(new_memory)

이제 어떤 에이전트가 이 고객을 상대하든, 이력을 알게 됩니다.


실제로 동작하는 것을 봅시다: 실제 데모

이론은 충분합니다. 이것을 실제로 어떻게 사용하는지 봅시다.

설정하기

필수 조건:

# Ollama 설치하고 phi4-mini 가져오기
curl -fsSL https://ollama.ai/install.sh | sh
ollama pull phi4-mini
ollama serve

백엔드 시작:

cd backend
source venv/bin/activate
python3 api.py  # http://localhost:5001에서 실행

프론트엔드 시작:

cd frontend
npm run dev  # http://localhost:3000에서 실행

고객 서비스 시나리오

1단계: 공유 메모리 로드

curl -X POST http://localhost:5001/api/memory/demo/prepare

이것은 다음을 미리 로드합니다:

  • 고객 프로필 (누가 무엇을 선호하는지)
  • 환불 정책 (프리미엄 회원이 받는 것)
  • 해결 워크플로우 (일반적인 문제 처리 방법)

2단계: Alex에게 복잡한 요청 보내기

http://localhost:3000/agents로 이동하여 Alex를 선택하고 다음을 보냅니다:

"고객이 최근 주문이 도착하지 않았다고 보고하고 환불을 요청하고 있습니다. 이전에 이메일 통신을 선호한다고 언급했고 프리미엄 회원입니다."

3단계: 협업이 펼쳐지는 것을 관찰

로그 페이지(http://localhost:3000/logs)를 열고 관찰하세요:

  • Alex가 작업을 분석하고 누구를 참여시킬지 결정
  • 에이전트 간 A2A 메시지가 오가는 것
  • 공유 메모리가 접근되고 업데이트되는 것
  • 최종 조율된 응답이 함께 만들어지는 것

볼 수 있는 것

Alex의 조율:

"우리 팀을 조율하여 이 프리미엄 고객 문제를 처리하겠습니다. 공유 메모리에 따르면, 이 고객은 이메일을 선호하고 빠른 서비스 자격이 있습니다..."

Sam의 주문 분석:

"주문 관리 관점에서, 주문이 발송되었지만 추적에서 지연이 보입니다. 고객은 48시간 프리미엄 환불 자격이 있습니다..."

Jordan의 기술 확인:

"배송 알림 시스템이 정상 작동하는 것을 확인했습니다. 지연은 우리 쪽이 아닌 배송사 관련 문제로 보입니다..."

종합된 응답:

"연락해 주셔서 감사합니다. 저희 팀이 귀하의 케이스를 검토했습니다: [구체적인 주문 세부 사항], [무엇이 잘못되었는지], [우리가 어떻게 처리하고 있는지]. 빠른 환불이 48시간 내에 처리되며, 이메일 확인을 받으실 것입니다..."

코드: 핵심 구현 패턴

우리가 개발한 흥미로운 코드 패턴들을 살펴봅시다.

메모리 추출: 기억할 가치가 있는 것 찾기

class MemoryExtractor:
    # 명시적 메모리 명령 패턴
    EXPLICIT_PATTERNS = [
        r"remember (?:that )?(.+)",
        r"don't forget (?:that )?(.+)",
        r"note (?:that )?(.+)",
    ]
    
    # 암시적 사실 패턴 (명령하지 않아도 기억할 가치가 있는 것들)
    FACT_PATTERNS = [
        r"(?:my|our|the) (\w+) is (.+)",
        r"i (?:prefer|like|want|need) (.+)",
        r"we (?:always|usually|typically) (.+)",
    ]
    
    def extract_from_text(self, text: str) -> List[Memory]:
        memories = []
        
        # 명시적 메모리는 높은 신뢰도를 받음
        for pattern in self.EXPLICIT_PATTERNS:
            if match := re.search(pattern, text.lower()):
                memories.append(Memory(
                    content=match.group(1),
                    mechanism=CreationMechanism.EXPLICIT,
                    confidence=0.95  # 직접 명령은 높은 신뢰도
                ))
        
        # 암시적 메모리는 낮은 신뢰도를 받음
        for pattern in self.FACT_PATTERNS:
            if match := re.search(pattern, text.lower()):
                memories.append(Memory(
                    content=match.group(0),
                    mechanism=CreationMechanism.IMPLICIT,
                    confidence=0.70  # 추론된 사실은 낮은 신뢰도
                ))
        
        return memories

왜 신뢰도 점수인가요? 모든 정보가 동등하게 신뢰할 수 있는 것은 아니기 때문입니다. 직접적인 "이것을 기억해" 명령은 맥락에서 우리가 추론한 것보다 더 신뢰할 수 있습니다.

의미 검색: 관련 메모리 찾기

class SemanticMemoryStore:
    def search(self, query: str, top_k: int = 5) -> List[Tuple[Memory, float]]:
        # 쿼리를 벡터로 변환
        query_embedding = self.embedder.embed(query)
        
        # 유사한 메모리 찾기
        results = self.vector_store.similarity_search(
            query_embedding,
            top_k=top_k,
            filter={"confidence": {"$gte": 0.7}}  # 높은 신뢰도 메모리만
        )
        
        return [(self.memories[id], score) for id, score in results]

왜 키워드 매칭 대신 벡터 검색인가요? "고객이 이메일을 선호함"이 "통신 선호도"에 대한 쿼리와 일치해야 하기 때문입니다. 비록 그 단어들이 정확히 겹치지 않더라도요.

A2A 브릿지: 에이전트 간 통신

class A2AMemoryBridge:
    def sync_memory_to_agents(self, memory: Memory, target_agents: List[str]):
        # 메모리 페이로드로 A2A 메시지 생성
        message = A2AMessage(
            source_agent=self.agent_id,
            content_type="application/vnd.a2a.memory-sync+json",
            payload={
                "memories": [memory.to_dict()],
                "source_confidence": memory.confidence,
                "sync_timestamp": datetime.utcnow().isoformat()
            }
        )
        
        for target in target_agents:
            # 각 대상에게 전송
            self.send_message(target, message)
            
            # 수신하는 에이전트는 신뢰도 할인 적용
            # (간접 정보는 약간 덜 신뢰할 수 있음)
            received_memory = memory.copy()
            received_memory.confidence *= 0.9
            target_bridge = self.get_bridge(target)
            target_bridge.memory_manager.add_memory(received_memory)

왜 0.9 신뢰도 승수인가요? 전화 게임처럼 생각하세요. 여러 사람을 거쳐 전달된 정보는 직접 얻은 지식보다 약간 덜 신뢰할 수 있습니다. 우리는 그 불확실성을 시스템에 직접 인코딩합니다.


성능: 이것이 실제로 확장되나요?

숫자에 대해 솔직해집시다.

컨텍스트 윈도우 사용

phi4-mini의 128k 토큰 한계로:

  • 평균 대화: 2,000-8,000 토큰
  • 세션당 저장된 메모리: 50-100개
  • 동시에 협업하는 에이전트: 3-5개
  • 압축 전 연속 세션 시간: 4-6시간

지연 시간

목표 (Google 백서에서): 메모리 조회 200ms 미만

우리의 벤치마크:

  • 벡터 유사성 검색: 1000개 메모리에 ~50ms
  • 전체 컨텍스트 조립: ~150ms
  • A2A 메시지 라우팅: ~30ms

예산 내에 있지만, 최적화할 여지는 항상 있습니다.

프로덕션을 위해 무엇을 바꿀 것인가요?

이것은 참조 구현입니다. 실제 프로덕션 사용을 위해:

컴포넌트현재프로덕션 대안
벡터 스토어인메모리 PythonPinecone, Weaviate, 또는 Qdrant
임베딩해시 기반 의사 임베딩OpenAI, Cohere, 또는 Sentence Transformers
세션 저장소인메모리 dictRedis 또는 PostgreSQL
메시지 큐동기 호출RabbitMQ 또는 Kafka

배운 교훈: 우리를 놀라게 한 것들

이것을 구축하면서 예상치 못한 몇 가지를 배웠습니다.

1. 메모리 추출은 보기보다 어렵습니다

초기 접근 방식은 너무 공격적이었습니다—모든 것을 저장하고 있었습니다. 결과는요? 검색 결과를 덜 유용하게 만드는 노이즈가 많은 메모리. 핵심은 선택적이 되는 것이었습니다: 명시적 명령과 명확한 선호도 진술만.

2. 신뢰도 점수는 필수입니다

모든 메모리가 동등하게 신뢰할 수 있는 것은 아닙니다. 고객이 한 대화에서 "이메일을 선호해요"라고 말하는 것은 여러 세션에 걸쳐 세 번 말하는 것보다 덜 확실합니다. 신뢰도 점수를 핵심 데이터 모델에 구축해야 했습니다.

3. 압축 전략은 생각보다 중요합니다

처음에는 단순 잘라내기를 기본값으로 했습니다. 나쁜 아이디어였습니다. 대화 초반의 중요한 맥락이 삭제되고 있었습니다. 요약 기반 압축은 추가적인 복잡성을 감수할 가치가 있었습니다.

4. A2A 프로토콜은 신중한 설계가 필요합니다

에이전트 간 통신은 루프와 불일치의 가능성을 만듭니다. 몇 가지 안전장치를 추가했습니다:

  • 메시지 중복 제거
  • 전달된 정보에 대한 신뢰도 감소
  • 디버깅을 위한 명확한 소스 추적

다음은 무엇인가요?

우리는 끝나지 않았습니다. 탐색 중인 것들:

단기:

  • 의미 검색을 위한 더 나은 임베딩 모델
  • 영구 저장소 백엔드 (현재 인메모리)
  • 메모리 업데이트를 위한 더 정교한 충돌 해결

장기:

  • 메모리 통합 (인간의 수면처럼, 기억이 재구성되는 것)
  • 에이전트 네트워크 간 연합 학습
  • 프라이버시 보존 메모리 공유

직접 해보세요

모든 것이 오픈 소스입니다. 클론하고, 실행하고, 부수고, 개선하세요.

git clone https://github.com/your-org/a2a-memory-layer
cd a2a-memory-layer

# 백엔드
cd backend && pip install -r requirements.txt && python api.py

# 프론트엔드 (새 터미널)
cd frontend && npm install && npm run dev

# http://localhost:3000/agents 열기

데모 시나리오가 미리 구성되어 있습니다. 클릭하면서 에이전트들이 협업하는 것을 지켜보세요.


감사의 말

이 프로젝트는 거인들의 어깨 위에 서 있습니다:

  • Google의 Context Engineering 팀 - 기초가 된 백서
  • Ollama - 로컬 LLM 배포를 접근 가능하게 만들어줌
  • 오픈소스 커뮤니티 - Flask, Next.js, 그리고 수많은 다른 도구들

기여하고 싶으신가요?

다음에 대한 도움을 환영합니다:

  • 새로운 메모리 압축 알고리즘
  • 새로운 에이전트 전문화
  • 성능 최적화
  • 더 나은 문서 (네, 이 README 포함)

이슈를 열거나 PR을 제출하세요. 함께 흥미로운 것을 만들어봅시다.


Read more

[시리즈 2편] 실무로 배우는 메시지 큐 - RabbitMQ

[시리즈 2편] 실무로 배우는 메시지 큐 - RabbitMQ

들어가며 [시리즈1]에서는 프로세스 내부 메시지 큐를 다뤘습니다. 이번엔 네트워크 메시지 큐인 RabbitMQ를 다룹니다. RabbitMQ 공식 문서나 기술 블로그는 많지만, 실무에서 어떻게 사용하는지에 대한 글은 의외로 적습니다. "Producer가 뭐고 Consumer가 뭔지는 알겠는데, 그래서 실제로는 어떻게 쓰는데?" 이번 글에서는 우리 MES 시스템에서 RabbitMQ를 어떻게 활용하고 있는지 실제 코드와 함께 공유합니다. 우리

By Jeonggil
[시리즈 1편] 실무로 배우는 메시지 큐 - Windows Message Loop

[시리즈 1편] 실무로 배우는 메시지 큐 - Windows Message Loop

들어가며 이 글은 "실무로 배우는 메시지 큐" 시리즈의 첫 번째 글입니다. 실무에서 발견한 문제를 해결하는 과정에서, IME 입력 문제와 해결 과정을 공유합니다. 메시지 큐는 RabbitMQ, Kafka 같은 네트워크 레벨만 있는 게 아닙니다. 우리가 매일 쓰는 Windows 애플리케이션도 메시지 큐 기반으로 동작합니다. * 시리즈1 (이 글): 프로세스 내부의 메시지 큐 - Windows

By Jeonggil
[시리즈 2편] 그림으로 풀어낸 SaaS 알림 시스템

[시리즈 2편] 그림으로 풀어낸 SaaS 알림 시스템

이 글은 1편 - 그림으로 풀어낸 SaaS 알림 시스템의 후속편입니다. 들어가며 1편에서는 설비 연속 OFF 알림 기능의 핵심 로직과 어떤식으로 해결했는지 그림으로 알아봤습니다. 이번 글에서는 실무에서 마주한 진짜 고민들을 공유합니다: * 왜 3개의 새로운 테이블이 필요했나? * 어떻게 확장 가능한 구조를 만들었나? * SMS 14원짜리 알림이 왜 무서운가? * 운영 레벨로 나가기까지 무엇을 준비했나?

By Jeonggil
[시리즈 1편] 그림으로 풀어낸 SaaS 알림 시스템

[시리즈 1편] 그림으로 풀어낸 SaaS 알림 시스템

들어가며 제조업 IoT 플랫폼에서 N대 이상의 설비를 실시간으로 모니터링하고, 설비가 연속으로 꺼졌을 때 담당자에게 즉시 알림을 보내는 기능을 개발하게 되었습니다. 데이터는 실시간으로 쌓이지만, 설비이상을 체크하는 스케줄러 주기는 1분으로 설정하였습니다. 시스템 아키텍처 기존 인프라와 Push 기능은 이미 구축되어 있었습니다. 저는 중간에 들어가는 Alert Scheduler만 구현하면 되는 상황이었습니다. ┌──────────────────────────────────────────────────────────┐ │ 설비 IoT 센서 (실시간)

By Jeonggil