메시지 및 메모리에 Time-to-Live 사용
메시지 및 메모리를 만든 대화가 종료된 후에도 메시지 및 메모리를 계속 검색할 수 있습니다. Time-to-live를 사용하면 규정 준수 요구 사항을 충족하거나, 오래된 정보를 제거하거나, 스토리지 공간을 절약하는 등 정의된 기간이 지나면 애플리케이션이 자동으로 레코드를 만료할 수 있습니다.
이 설명서에서는 Oracle DB 지원 메시지 및 메모리에 대한 레코드 만료를 구성하는 방법을 보여줍니다. 또한 비우기 작업 및 레코드별 TTL 재정의에 대해서도 설명합니다.
Oracle Agent Memory는 다음 두 계층에서 TTL(Time-to-Live)을 적용합니다.
- 스키마 레벨 보존 기본값은
MemoryRetentionConfig에서 가져옵니다. 새 쓰기에서 생략된ttl_days값은MemoryRetentionConfig.default_ttl_days를 사용합니다. - 레코드별
ttl_days및ttl_anchor값을 사용하면 하나의 메시지 또는 메모리가 스키마 기본값과 다른 앵커 시간에서 더 일찍 또는 나중에 만료될 수 있습니다.
레코드별 앵커를 선택하는 경우 TimeToLiveAnchor.CREATED_AT를 사용하여 Oracle이 행을 저장하는 시점부터 계산하거나 TimeToLiveAnchor.TIMESTAMP를 사용하여 대신 레코드의 이벤트 타임스탬프에서 계산합니다.
참고: 정의된 기간 동안 레코드를 계속 사용할 수 있어야 하는 경우 TTL(Time-to-Live)을 사용한 다음 자동으로 만료됩니다. 예를 들어, 응용 프로그램은 30일 동안의 지원 세부 정보 또는 1주일 동안의 임시 작업 정보를 유지할 수 있습니다.
다음 보존 규칙이 적용됩니다.
- 새 쓰기에서
ttl_days를 생략하면 스키마 기본값이 사용됩니다. ttl_days=None를 전달하면 최대값이 구성된 경우max_ttl_days가 사용되고, 최대값이 구성되지 않은 경우 만료되지 않는 행이 저장됩니다.max_ttl_days이상의 값은 경고와 함께 해당 최대값으로 고정됩니다.- 업데이트 API는
ttl_days또는ttl_anchor가 제공되지 않는 한 현재 만료를 유지합니다.
힌트: 패키지 설정은 에이전트 메모리 시작하기를 참조하십시오. 이 예에 대한 로컬 Oracle 데이터베이스가 필요한 경우 Run Oracle AI Database Locally를 따르십시오.
스키마 레벨 보존 기본값 구성
SDK가 관리되는 Oracle 스키마를 생성 또는 업그레이드하고 보존 정책을 함께 유지하도록 하려면 schema_policy=SchemaPolicy.CREATE_IF_NECESSARY를 사용하여 OracleAgentMemory 클라이언트를 생성합니다.
import oracledb
from oracleagentmemory.apis import TimeToLiveAnchor
from oracleagentmemory.core import (
MemoryExtractionConfig,
MemoryRetentionConfig,
OracleAgentMemory,
SchemaPolicy,
)
from oracleagentmemory.core.embedders.embedder import Embedder
embedder = Embedder(model="YOUR_EMBEDDING_MODEL")
db_pool = oracledb.SessionPool(
user="YOUR DB USER",
password="YOUR DB PASSWORD",
dsn="YOUR DB CONNECT STRING",
)
memory = OracleAgentMemory(
connection=db_pool,
embedder=embedder,
schema_policy=SchemaPolicy.CREATE_IF_NECESSARY,
memory_extraction_config=MemoryExtractionConfig(extract_memories=False),
memory_retention_config=MemoryRetentionConfig(
default_ttl_days=30,
max_ttl_days=90,
),
)
이 구성에서 다음을 수행합니다.
ttl_days를 생략하는 새 쓰기는 기본적으로30일로 설정됩니다.90일 이상의 명시적ttl_days값은 경고와 함께 고정됩니다.- 명시적
ttl_days=None는max_ttl_days가 설정되었기 때문에90일로 분석됩니다.
API 참조: OracleAgentMemory
TTL(Time-to-Live)을 사용하여 메시지 및 메모 추가
스레드 쓰기를 사용하여 일부 레코드에 대해 스키마 기본 TTL을 사용하고 다른 레코드에 대해 다른 TTL을 설정합니다.
from datetime import datetime, timezone
ttl_thread = memory.create_thread(
thread_id="ttl_demo_thread",
user_id="user_123",
)
message_ids = ttl_thread.add_messages(
[
{
"id": "msg-ttl-1",
"role": "user",
"content": (
"I opened ticket 1042 yesterday because the laptop battery failed."
),
"timestamp": "2026-04-01T09:00:00Z",
},
{
"id": "msg-ttl-2",
"role": "assistant",
"content": (
"I will keep ticket 1042 active and send a replacement checklist."
),
"timestamp": "2026-04-01T09:01:00Z",
},
]
)
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
#for instance, 2026-04-01T09:00:00Z
backfilled_memory_id = ttl_thread.add_memory(
"Ticket 1042 battery-failure report was filed on 2026-04-01.",
memory_id="mem-ttl-backfill",
timestamp=timestamp,
ttl_days=90,
ttl_anchor=TimeToLiveAnchor.TIMESTAMP,
)
short_lived_memory_id = ttl_thread.add_memory(
"Replacement checklist should be revisited within one week.",
memory_id="mem-ttl-short",
ttl_days=7,
)
print(message_ids)
print(backfilled_memory_id)
print(short_lived_memory_id)
#['msg-ttl-1', 'msg-ttl-2']
#mem-ttl-backfill
#mem-ttl-short
이 예에서는 다음과 같습니다.
add_messages()는ttl_days를 생략하므로 두 메시지 모두30일의 스키마 기본값을 사용합니다.mem-ttl-backfill는TimeToLiveAnchor.TIMESTAMP를 사용하기 때문에 저장된 이벤트 시간 기록 이후90일 후에 만료됩니다.mem-ttl-short는 기본값을 대체하고7일 후 만료됩니다.
API 참조: OracleThread
기존 레코드의 보관 시간 새로고침
저장된 콘텐츠를 바꾸지 않고 기존 만료를 새로 고치려면 update_message() 또는 update_memory()를 사용합니다.
ttl_thread.update_message("msg-ttl-1", ttl_days=14)
ttl_thread.update_memory(
"mem-ttl-backfill",
ttl_days=60,
ttl_anchor=TimeToLiveAnchor.TIMESTAMP,
)
이러한 호출은 만료 창을 재계산하는 동안 저장된 컨텐트 및 메타 데이터를 제자리에 유지합니다. 업데이트는 ttl_days 또는 ttl_anchor를 통과하지 않는 한 현재 만료를 보존합니다.
API 참조: OracleThread
관리되는 비우기 작업이 존재하는지 확인하십시오.
Oracle Agent Memory는 관리되는 Oracle 스키마를 생성하거나 업그레이드할 때 만료된 메시지 및 메모리 행과 해당 검색 청크를 삭제하는 일별 DBMS_SCHEDULER 작업도 생성합니다. 이 작업은 하나의 큰 삭제를 실행하고 일괄 처리 간에 커밋하는 대신 일괄 처리로 행을 비우고, 단일 실행이 하나의 일관된 만료 차단을 사용하도록 하나의 참조 시간기록을 미리 캡처합니다.
또한 스케줄러 정의는 schedule_limit를 하루로 설정하므로 너무 늦게 시작하는 실행을 임의로 늦게 실행하는 대신 건너뛸 수 있습니다. 작업 본문은 별도로 실행 시작 시간부터 하루 후 새 일괄 처리 수행을 정지합니다. 해당 런타임 제한은 뱃치 간에 확인되므로 이미 진행 중인 뱃치가 완료 및 커밋이 허용됩니다. 실행 종료 시 작업은 런타임 제한에 도달했는지 여부와 범주별로 삭제된 행 수를 포함하는 짧은 DBMS_OUTPUT 요약을 작성합니다.
SchemaPolicy.CREATE_IF_NECESSARY에서 해당 작업을 생성해야 하지만 스키마 사용자에게 CREATE JOB이 없으면 설정이 완료되고 경고가 표시됩니다. 만료된 레코드는 읽기 및 검색에서 숨겨지지만 권한이 있는 사용자가 작업을 생성할 때까지 물리적으로 지워지지 않습니다.
table_name_prefix를 사용하는 경우 아래의 작업 및 테이블 이름에 동일한 접두어를 적용합니다. 기본 관리 작업 이름은 PURGE_EXPIRED_RECORDS_J입니다.
참고: 스키마 소유자에게 CREATE JOB가 없는 경우 다음 DBA 옵션을 사용하십시오.
스케줄러 작업 권한 부여
GRANT CREATE JOB TO app_schema;
-- Run OracleAgentMemory schema setup as app_schema.
REVOKE CREATE JOB FROM app_schema;
수동으로 비우기 작업 생성
테이블 이름 접두어가 구성된 경우 APP_SCHEMA를 관리 스키마 소유자로 바꾸고 객체 이름을 조정합니다. 아래 PL/SQL 블록은 의도적으로 압축되므로 접두어가 붙은 객체 이름이 긴 경우에도 Oracle Scheduler의 job_action 길이 한도 내에서 유지됩니다.
BEGIN
DBMS_SCHEDULER.CREATE_JOB(
job_name => 'APP_SCHEMA.PURGE_EXPIRED_RECORDS_J',
job_type => 'PLSQL_BLOCK',
job_action => q'[
DECLARE
bs CONSTANT PLS_INTEGER := 1000;
n PLS_INTEGER;
mc PLS_INTEGER := 0;
xc PLS_INTEGER := 0;
mr PLS_INTEGER := 0;
xr PLS_INTEGER := 0;
tl PLS_INTEGER := 0;
rt TIMESTAMP(6) WITH TIME ZONE := SYSTIMESTAMP;
dl TIMESTAMP(6) WITH TIME ZONE := rt + INTERVAL '1 00:00:00' DAY TO SECOND;
BEGIN
LOOP
IF SYSTIMESTAMP >= dl THEN
tl := 1;
EXIT;
END IF;
DELETE FROM APP_SCHEMA.RECORD_CHUNKS c
WHERE c.rowid IN (
SELECT rid
FROM (
SELECT c.rowid AS rid
FROM APP_SCHEMA.RECORD_CHUNKS c
WHERE c.source_record_type = 'message'
AND EXISTS (
SELECT 1
FROM APP_SCHEMA.MESSAGE m
WHERE m.record_id = c.source_id
AND m.expires_at IS NOT NULL
AND m.expires_at <= rt
)
FETCH FIRST bs ROWS ONLY
)
);
n := SQL%ROWCOUNT;
mc := mc + n;
EXIT WHEN n = 0;
COMMIT;
END LOOP;
LOOP
IF SYSTIMESTAMP >= dl THEN
tl := 1;
EXIT;
END IF;
DELETE FROM APP_SCHEMA.RECORD_CHUNKS c
WHERE c.rowid IN (
SELECT rid
FROM (
SELECT c.rowid AS rid
FROM APP_SCHEMA.RECORD_CHUNKS c
WHERE c.source_record_type IN ('fact', 'guideline', 'memory', 'preference')
AND EXISTS (
SELECT 1
FROM APP_SCHEMA.MEMORY m
WHERE m.record_id = c.source_id
AND m.memory_type = c.source_record_type
AND m.expires_at IS NOT NULL
AND m.expires_at <= rt
)
FETCH FIRST bs ROWS ONLY
)
);
n := SQL%ROWCOUNT;
xc := xc + n;
EXIT WHEN n = 0;
COMMIT;
END LOOP;
LOOP
IF SYSTIMESTAMP >= dl THEN
tl := 1;
EXIT;
END IF;
DELETE FROM APP_SCHEMA.MESSAGE
WHERE rowid IN (
SELECT rid
FROM (
SELECT rowid AS rid
FROM APP_SCHEMA.MESSAGE
WHERE expires_at IS NOT NULL
AND expires_at <= rt
FETCH FIRST bs ROWS ONLY
)
);
n := SQL%ROWCOUNT;
mr := mr + n;
EXIT WHEN n = 0;
COMMIT;
END LOOP;
LOOP
IF SYSTIMESTAMP >= dl THEN
tl := 1;
EXIT;
END IF;
DELETE FROM APP_SCHEMA.MEMORY
WHERE rowid IN (
SELECT rid
FROM (
SELECT rowid AS rid
FROM APP_SCHEMA.MEMORY
WHERE expires_at IS NOT NULL
AND expires_at <= rt
FETCH FIRST bs ROWS ONLY
)
);
n := SQL%ROWCOUNT;
xr := xr + n;
EXIT WHEN n = 0;
COMMIT;
END LOOP;
DBMS_OUTPUT.PUT_LINE(
'OracleAgentMemory purge reference_time='
|| TO_CHAR(rt, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZH:TZM')
);
DBMS_OUTPUT.PUT_LINE(
'OracleAgentMemory purge run_deadline='
|| TO_CHAR(dl, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZH:TZM')
);
DBMS_OUTPUT.PUT_LINE(
'OracleAgentMemory purge runtime_limit_reached='
|| CASE WHEN tl = 1 THEN 'true' ELSE 'false' END
);
DBMS_OUTPUT.PUT_LINE(
'OracleAgentMemory purge deleted message chunk rows='
|| TO_CHAR(mc)
);
DBMS_OUTPUT.PUT_LINE(
'OracleAgentMemory purge deleted fact/guideline/memory/preference chunk rows='
|| TO_CHAR(xc)
);
DBMS_OUTPUT.PUT_LINE(
'OracleAgentMemory purge deleted message rows='
|| TO_CHAR(mr)
);
DBMS_OUTPUT.PUT_LINE(
'OracleAgentMemory purge deleted fact/guideline/memory/preference rows='
|| TO_CHAR(xr)
);
END;
]',
start_date => SYSTIMESTAMP,
repeat_interval => 'FREQ=DAILY;INTERVAL=1',
enabled => FALSE,
auto_drop => FALSE,
comments => 'OracleAgentMemory expired-record purge'
);
DBMS_SCHEDULER.SET_ATTRIBUTE(
name => 'APP_SCHEMA.PURGE_EXPIRED_RECORDS_J',
attribute => 'logging_level',
value => DBMS_SCHEDULER.LOGGING_RUNS
);
DBMS_SCHEDULER.SET_ATTRIBUTE(
name => 'APP_SCHEMA.PURGE_EXPIRED_RECORDS_J',
attribute => 'store_output',
value => TRUE
);
DBMS_SCHEDULER.SET_ATTRIBUTE(
name => 'APP_SCHEMA.PURGE_EXPIRED_RECORDS_J',
attribute => 'schedule_limit',
value => INTERVAL '1 00:00:00' DAY TO SECOND
);
DBMS_SCHEDULER.ENABLE('APP_SCHEMA.PURGE_EXPIRED_RECORDS_J');
END;
/
스케줄러 정의를 확인하고 최근 실행 기록을 검사하는 데 유용합니다. 필요한 경우 APP_SCHEMA를 바꾸고 배포에서 작업 이름을 사용할 때 구성된 테이블 이름 접두어를 작업 이름에 적용합니다.
관리 비우기 작업이 존재하고 예상 검사 설정을 유지했는지 확인합니다.
SELECT owner,
job_name,
enabled,
state,
repeat_interval,
schedule_limit,
logging_level,
store_output,
start_date,
last_start_date,
next_run_date
FROM all_scheduler_jobs
WHERE owner = 'APP_SCHEMA'
AND job_name = 'PURGE_EXPIRED_RECORDS_J';
지우기 실행이 발생하고 성공적으로 완료되는지 확인해야 하는 경우 최근 실행을 확인합니다.
SELECT log_date,
status,
run_duration,
output,
additional_info
FROM all_scheduler_job_run_details
WHERE owner = 'APP_SCHEMA'
AND job_name = 'PURGE_EXPIRED_RECORDS_J'
ORDER BY log_date DESC;
설치를 즉시 검증하려면 작업을 수동으로 한 번 실행합니다.
BEGIN
DBMS_SCHEDULER.RUN_JOB(
job_name => 'APP_SCHEMA.PURGE_EXPIRED_RECORDS_J',
use_current_session => TRUE
);
END;
/
결론
이 설명서에서는 스키마 레벨 보존 기본값, 레코드별 ttl_days 값 및 TimeToLiveAnchor가 함께 작동하는 방법, 관리되는 비우기 작업이 만료된 행을 제거하는 방법 및 Python에서 기존 만료를 새로 고치는 방법에 대해 배웠습니다.
메타데이터별로 레코드를 필터링하는 방법에 대한 자세한 내용은 메타데이터 및 메타데이터 필터링 사용을 참조하십시오.
전체 코드
다음 코드는 이 설명서의 예제를 결합합니다.
#Copyright © 2026 Oracle and/or its affiliates.
#This software is under the Apache License 2.0
#(LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or Universal Permissive License
#(UPL) 1.0 (LICENSE-UPL or https://oss.oracle.com/licenses/upl), at your option.
#Oracle Agent Memory Code Example - Use Time-to-Live for Messages and Memories
#-----------------------------------------------------------------------------
##Create a retention aware client
from datetime import datetime, timezone
import oracledb
from oracleagentmemory.apis import TimeToLiveAnchor
from oracleagentmemory.core import (
MemoryExtractionConfig,
MemoryRetentionConfig,
OracleAgentMemory,
SchemaPolicy,
)
from oracleagentmemory.core.embedders.embedder import Embedder
embedder = Embedder(model="YOUR_EMBEDDING_MODEL")
db_pool = oracledb.SessionPool(
user="YOUR DB USER",
password="YOUR DB PASSWORD",
dsn="YOUR DB CONNECT STRING",
)
memory = OracleAgentMemory(
connection=db_pool,
embedder=embedder,
schema_policy=SchemaPolicy.CREATE_IF_NECESSARY,
memory_extraction_config=MemoryExtractionConfig(extract_memories=False),
memory_retention_config=MemoryRetentionConfig(
default_ttl_days=30,
max_ttl_days=90,
),
)
##Add messages and memories with time to live
ttl_thread = memory.create_thread(
thread_id="ttl_demo_thread",
user_id="user_123",
)
message_ids = ttl_thread.add_messages(
[
{
"id": "msg-ttl-1",
"role": "user",
"content": (
"I opened ticket 1042 yesterday because the laptop battery failed."
),
"timestamp": "2026-04-01T09:00:00Z",
},
{
"id": "msg-ttl-2",
"role": "assistant",
"content": (
"I will keep ticket 1042 active and send a replacement checklist."
),
"timestamp": "2026-04-01T09:01:00Z",
},
]
)
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
#for instance, 2026-04-01T09:00:00Z
backfilled_memory_id = ttl_thread.add_memory(
"Ticket 1042 battery-failure report was filed on 2026-04-01.",
memory_id="mem-ttl-backfill",
timestamp=timestamp,
ttl_days=90,
ttl_anchor=TimeToLiveAnchor.TIMESTAMP,
)
short_lived_memory_id = ttl_thread.add_memory(
"Replacement checklist should be revisited within one week.",
memory_id="mem-ttl-short",
ttl_days=7,
)
print(message_ids)
print(backfilled_memory_id)
print(short_lived_memory_id)
#['msg-ttl-1', 'msg-ttl-2']
#mem-ttl-backfill
#mem-ttl-short
##Refresh time to live on existing records
ttl_thread.update_message("msg-ttl-1", ttl_days=14)
ttl_thread.update_memory(
"mem-ttl-backfill",
ttl_days=60,
ttl_anchor=TimeToLiveAnchor.TIMESTAMP,
)