블로그
아키텍처

게시일 수정일

Arag 및 Anshu

AI agent 애플리케이션을 위한 database 설계 pattern

Markdown으로 읽기

agent-ready database schema는 task, operation, audit event를 business row와 분리해 기록합니다. constraint로 tenant scope와 idempotency를 적용하고, concurrent worker를 조율하며, model transcript를 증거로 취급하지 않고도 timeout 후 복구할 수 있을 만큼의 history를 보관합니다.

database가 prompt를 이해할 필요는 없습니다. 안전하지 않은 state transition과 중복 effect가 일어나기 어렵게 만들면 됩니다.

task와 operation부터 시작하세요

task는 사용자 수준 workflow입니다. operation은 해당 task가 시도하는 하나의 외부 또는 내부 effect입니다. task 하나가 operation을 여러 번 재시도하거나 operation 사이에 기다릴 수 있으므로 둘을 분리하세요.

CREATE TABLE agent_tasks (
  id uuid PRIMARY KEY,
  tenant_id uuid NOT NULL,
  workflow_version text NOT NULL,
  state text NOT NULL,
  state_version bigint NOT NULL DEFAULT 0,
  current_step text,
  wake_at timestamptz,
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now(),
  UNIQUE (tenant_id, id)
);

CREATE TABLE agent_operations (
  id uuid PRIMARY KEY,
  tenant_id uuid NOT NULL,
  task_id uuid NOT NULL,
  idempotency_key text NOT NULL,
  action text NOT NULL,
  input_hash text NOT NULL,
  state text NOT NULL,
  provider_reference text,
  result jsonb,
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now(),
  UNIQUE (tenant_id, idempotency_key),
  FOREIGN KEY (tenant_id, task_id) REFERENCES agent_tasks (tenant_id, id)
);

composite foreign key는 operation이 다른 tenant의 task를 가리키지 못하게 합니다. unique idempotency key는 두 worker가 같은 logical operation을 두 번 만들지 못하게 합니다. 기존 result를 반환하기 전에 저장된 input hash를 비교해 같은 key로 다른 parameter를 조용히 승인하지 못하게 하세요.

model과 협상할 수 없는 규칙에는 constraint를 사용하세요

uniqueness, foreign key, non-null ownership, 허용된 enum value, version check를 database에서 적용하세요. 애플리케이션 validation은 error를 개선하지만 최종 constraint는 migration script나 새로 생성된 도구를 포함한 모든 writer를 보호합니다.

tenant isolation을 위해 tenant가 소유한 모든 child table을 tenant-scoped key로 join할 수 있게 하세요. multi-tenant agent 인프라 가이드는 row, schema, database isolation 선택지를 다룹니다.

현재 state와 append-only event를 기록하세요

current-state table은 read와 scheduling을 단순하게 만듭니다. append-only event table은 state가 어떻게 바뀌었는지 설명합니다.

state update와 event를 한 transaction에서 작성하세요.

UPDATE agent_tasks
SET state = $1, state_version = state_version + 1, updated_at = now()
WHERE tenant_id = $2 AND id = $3 AND state_version = $4;

INSERT INTO agent_events
  (tenant_id, task_id, event_type, actor_id, policy_version, payload)
VALUES ($1, $2, $3, $4, $5, $6);

commit 전에 update가 row 하나에 적용되었는지 확인하세요. 적용된 row가 0개라면 다른 worker가 이미 task를 진행했다는 뜻이므로 stale worker는 덮어쓰지 말고 다시 읽어야 합니다.

secret, 전체 prompt, 크기가 제한되지 않은 provider response를 event payload에 넣지 마세요. 별도의 retention policy와 함께 redaction된 fact나 reference를 저장하세요.

안정적인 다운스트림 전달을 위해 outbox를 추가하세요

database update 후 event를 발행하거나 작업을 queue에 넣어야 한다면 같은 transaction에서 outbox row를 작성하세요. relay가 pending row를 전송하고 delivered로 표시합니다. relay가 acknowledgement를 받지 못한 뒤 두 번 발행할 수 있으므로 consumer는 여전히 idempotent해야 합니다.

이 방식은 database commit 후 process가 queue에 알리기 전에 crash하는 틈을 막습니다. log line에 의존하지 않고 query할 수 있는 delivery state도 operation에 제공합니다.

lease 또는 version check로 worker를 조율하세요

짧은 state transition에는 optimistic version을 사용하세요. worker가 task를 임시로 소유해야 한다면 lease를 사용합니다. lease에는 owner, acquired time, expiry가 들어갑니다. 작업 중 갱신하고 완료 시 owner 또는 더 새로운 유효 lease를 요구하세요.

model이 생각하거나 provider가 응답하는 동안 database transaction을 열어 두지 마세요. operation을 예약하고 commit한 뒤 call을 수행한 다음 결과를 조정하고 update하세요. persistent state 가이드는 이 방식이 새 worker의 재개를 어떻게 지원하는지 설명합니다.

JSON은 유연하게 사용하되 query하는 fact는 명시하세요

JSONB는 model별 metadata, provider payload fragment, workflow마다 다른 step-local value에 적합합니다. column은 tenant ownership, state, operation identity, scheduling, retention, policy나 index에서 사용하는 field에 적합합니다.

모든 authorization query나 scheduler에 나타나는 JSON path는 typed column으로 승격하세요. database introspection은 coding agent에게 유용하지만 schema가 단순하다고 해서 권한이 생기는 것은 아닙니다. 생성된 SQL에 administrator connection을 넘기지 말고 도구를 승인된 statement나 application API로 제한하세요.

database를 telemetry 저장소로 만들지 말고 trace를 연결하세요

task와 operation에 trace 및 span identifier를 저장하세요. 자세한 model 및 tool telemetry는 observability system에 보관합니다. 그러면 대용량 trace를 Postgres에 복제하지 않고도 문제가 있는 business row에서 이를 만든 실행으로 이동할 수 있습니다.

OpenTelemetry의 GenAI semantic convention은 공통 agent 및 tool attribute를 정의합니다. 이 schema는 변화하고 있으므로 instrumentation이 내보내는 version을 고정하세요.

retry와 concurrency에서 schema를 증명하세요

같은 task에 대해 worker 두 개를 실행하세요. 같은 idempotency key를 일치하는 parameter와 충돌하는 parameter로 재사용합니다. business transaction commit 후 queue acknowledgement 전에 crash를 일으키세요. test environment에서 tenant를 삭제하고 소유한 task, operation, artifact, credential이 의도한 policy를 따르는지 확인하세요.

아래 프롬프트는 이런 보장을 현재 schema에 mapping합니다. “we handle that in the agent”는 database invariant가 아니므로 query path와 constraint를 요구합니다.