잠금
잠금은 영속성과는 다른 질문에 답합니다.
| 관심사 | 질문 | 연결 지점 |
|---|---|---|
| 상태 | 무엇이 영속적인가요? | 스토어 + withPersistence |
| 잠금 | 지금 이 임계 영역을 실행할 수 있는 주체는 누구인가요? | LockStore + withLocks |
잠금은 @tanstack/ai에 미들웨어 기능으로 포함됩니다. @tanstack/ai-persistence에 포함되지 않으며, AIPersistence.stores의 키로도 사용되지 않습니다.
잠금이 필요한 경우
동일한 키에 대해 둘 이상의 프로세스나 격리가 동일한 임계 영역에 진입할 수 있다면 잠금을 사용합니다.
- 샌드박스 재개 또는 생성 (
withSandbox/ensure) — 동일한 스레드에 대한 두 동시 실행이 모두 제공자 샌드박스를 생성해서는 안 됩니다. 샌드박스 인스턴스 내구성을 참고하세요. - 사용자 지정 미들웨어 — 워커 간에 직렬화하려는 모든 다중 기록 작업(예: 사용자 지정 “스레드당 활성 작업 하나” 게이트)에 사용합니다.
다음에는 잠금이 필요하지 않습니다.
- 단일 프로세스 로컬 개발(선택 사항:
InMemoryLockStore를 사용해도 됩니다). - 일반적인 채팅 상태 영속성 — 이는 뮤텍스가 아니라 스토어의 역할입니다.
- 전체
chat()턴의 자동 잠금 —withLocks는 기능만 제공하며, 배제해야 할 때 소비자가withLock을 호출합니다.
연결 방법
import { chat } from '@tanstack/ai'
import { withLocks, InMemoryLockStore } from '@tanstack/ai/locks'
import { grokBuildText } from '@tanstack/ai-grok-build'
import type { ModelMessage } from '@tanstack/ai'
const messages: Array<ModelMessage> = [{ role: 'user', content: 'hi' }]
chat({
adapter: grokBuildText('grok-build'),
messages,
middleware: [
// Single process. Multi-instance: pass a distributed LockStore instead.
withLocks(new InMemoryLockStore()),
// later middleware can getLocks(ctx) / withSandbox will use the same token
],
})
기능의 식별은 객체 참조로 이루어집니다. withLocks는 코어의 공유 LocksCapability를 제공하므로, 해당 토큰을 읽는 이후의 모든 미들웨어(@tanstack/ai-sandbox 포함)가 동일한 스토어를 확인합니다.
샌드박스와 함께 구성할 때의 일반적인 순서는 다음과 같습니다.
import { withLocks, InMemoryLockStore } from '@tanstack/ai/locks'
import { withSandbox } from '@tanstack/ai-sandbox'
import type { SandboxDefinition } from '@tanstack/ai-sandbox'
declare const sandbox: SandboxDefinition
const middleware = [
withLocks(new InMemoryLockStore()),
withSandbox(sandbox), // after providers
]
계약
import type { LockStore } from '@tanstack/ai/locks'
declare const locks: LockStore
// Mutual exclusion for a key; lease-backed impls abort `signal` on loss.
await locks.withLock('thread:abc', async (signal) => {
// critical section — pass `signal` to cancellable work when using leases
void signal
})
| 구성 요소 | 역할 |
|---|---|
LockStore | 인터페이스: withLock(key, fn) |
withLocks(store) | LocksCapability를 제공하는 채팅 미들웨어 |
InMemoryLockStore | 프로세스 로컬 구현(키별 프로미스 체인) |
getLocks / provideLocks | 사용자 지정 미들웨어를 위한 저수준 기능 접근자 |
InMemoryLockStore는 하나의 프로세스 내에서만 올바르게 동작합니다. 동일한 키의 호출자를 직렬화하고, 임계 영역에서 예외가 발생해도 체인을 오염시키지 않으며, 프로세스 내에서는 소유권을 잃을 수 없으므로 시그널을 중단하지 않습니다.
잠금 구현
defineLock으로 자체 상호 배제 기본 요소를 감쌉니다. 계약에 맞춰 객체의 타입을 인라인으로 지정하며(자동 완성 지원, : LockStore 주석 불필요), 키를 획득하고 fn을 실행한 다음 fn이 해결되거나 예외를 던지는 등 어떤 방식으로 완료되든 해제합니다.
import { defineLock } from '@tanstack/ai/locks'
// Your distributed primitive. `acquire` waits until the key is free and returns
// a `release` (plus, for leases, a `signal` that fires when ownership is lost).
import { acquire } from './my-lock-backend'
export const locks = defineLock({
async withLock(key, fn) {
const { release, signal } = await acquire(key)
try {
return await fn(signal)
} finally {
release()
}
},
})
withLocks(locks)를 사용해 미들웨어로 연결합니다. 프로덕션 스토어가 충족해야 하는 요구 사항은 아래에 설명되어 있습니다.
분산 잠금과 리스
다중 인스턴스 배포에는 분산 구현(Durable Object, Redis 등)이 필요합니다. 우수한 스토어는 다음을 충족합니다.
key별 소유자를 직렬화합니다.- 리스(또는 이에 상응하는 방식)를 사용하여 충돌한 소유자가 영원히 차단하지 못하게 합니다.
AbortSignal을fn에 전달하고, 리스를 잃으면 중단하여 콜백이 외부에 표시되는 작업을 시작하지 않도록 중지하며 취소 가능한 종속성에 시그널을 전달합니다.
signal을 무시하는 콜백도 타입 검사를 통과하지만(() => Promise<T>를 할당할 수 있음), 임계 영역이 중단 후에도 계속 변경 작업을 수행하면 리스 기반 백엔드가 이를 보호할 수 없습니다.
채팅 스토어 테스트 키트에는 공유 잠금 적합성 테스트 모음이 없습니다. 백엔드에 대해 동시성, 예외 발생 시 해제, 리스 만료를 대상으로 한 테스트를 작성합니다. Cloudflare Durable Object 레시피는 ai-persistence/build-cloudflare-adapter 에이전트 스킬에 있습니다(앱 소유 파일이며 배포 패키지에는 포함되지 않음).
사용자 지정 미들웨어에서 사용
import { defineChatMiddleware } from '@tanstack/ai'
import { LocksCapability, getLocks } from '@tanstack/ai/locks'
const serializePerThread = defineChatMiddleware({
name: 'serialize-per-thread',
requires: [LocksCapability],
async onStart(ctx) {
const locks = getLocks(ctx)
await locks.withLock(`thread:${ctx.threadId}`, async (signal) => {
// critical section — honor `signal` under lease-backed locks
void signal
})
},
})
또는 이미 사용자 지정 미들웨어를 소유하고 있다면 자체 setup 훅에서 provideLocks를 호출하여 withLocks 없이 제공할 수 있습니다.
함께 보기
- 미들웨어 — 기능 버스와 수명 주기
- 샌드박스 — 샌드박스 미들웨어 개요
- 샌드박스 인스턴스 내구성 — 주요 제품 소비자(
withSandbox/ensure) - 영속성 제어 — 서로 다른 시스템의 상태 스토어 구성
- 자체 어댑터 빌드 — 채팅 스토어 계약