메시지 및 메모리에 Time-to-Live 사용

메시지 및 메모리를 만든 대화가 종료된 후에도 메시지 및 메모리를 계속 검색할 수 있습니다. Time-to-live를 사용하면 규정 준수 요구 사항을 충족하거나, 오래된 정보를 제거하거나, 스토리지 공간을 절약하는 등 정의된 기간이 지나면 애플리케이션이 자동으로 레코드를 만료할 수 있습니다.

이 설명서에서는 Oracle DB 지원 메시지 및 메모리에 대한 레코드 만료를 구성하는 방법을 보여줍니다. 또한 비우기 작업 및 레코드별 TTL 재정의에 대해서도 설명합니다.

Oracle Agent Memory는 다음 두 계층에서 TTL(Time-to-Live)을 적용합니다.

레코드별 앵커를 선택하는 경우 TimeToLiveAnchor.CREATED_AT를 사용하여 Oracle이 행을 저장하는 시점부터 계산하거나 TimeToLiveAnchor.TIMESTAMP를 사용하여 대신 레코드의 이벤트 타임스탬프에서 계산합니다.

참고: 정의된 기간 동안 레코드를 계속 사용할 수 있어야 하는 경우 TTL(Time-to-Live)을 사용한 다음 자동으로 만료됩니다. 예를 들어, 응용 프로그램은 30일 동안의 지원 세부 정보 또는 1주일 동안의 임시 작업 정보를 유지할 수 있습니다.

다음 보존 규칙이 적용됩니다.

힌트: 패키지 설정은 에이전트 메모리 시작하기를 참조하십시오. 이 예에 대한 로컬 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,
    ),
)

이 구성에서 다음을 수행합니다.

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

이 예에서는 다음과 같습니다.

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,
)