Facebook Twitter
お問い合わせ
TOPICS
更新情報
ホーム > ブログ > Oracle Cloud > 【Oracle Cloud】FunctionsでBaseDBのパーティションを削除し、結果をメール通知する

【Oracle Cloud】FunctionsでBaseDBのパーティションを削除し、結果をメール通知する

ブログ
Oracle Cloud
2025.11.25

皆様、こんにちは。y.kobayashiです。

今回はOCI Functionsを利用してBaseDBのパーティション化されたテーブル内の最も古いパーティションを削除して結果をメールで通知する方法をご紹介していきます。

 


構成イメージ

概要

OCI FunctionsからBaseDBに接続し、パーティション化されたテーブル内の最も古いパーティションを削除します。

削除処理の結果(成功・失敗)はメールで通知されるように構成します。

この仕組みにより、定期的なデータ削除作業を効率化し、運用負荷を軽減することができます。

※本記事では、CloudShellからファンクションをデプロイ・実行します。
 VCN・BaseDBの構築手順については割愛しています。

事前準備

認証トークンの作成

以下のドキュメントを参照ください。
認証トークンの作成

 

動的グループ/ポリシーの作成

ファンクションが通知サービスを利用できるようにするために動的グループとポリシーを作成します。

動的グループの設定は以下の通りです。

ルール説明
ALL {resource.type = 'fnfunc', resource.compartment.id = '<コンパートメントOCID>'}ファンクションが通知サービスを利用するために必要な動的グループ

ポリシーの設定は以下の通りです。

ポリシー説明
Allow dynamic-group <動的グループ名> to use ons-topics in compartment <コンパートメント名>ファンクションが通知サービスを利用するために必要なポリシー

 

セキュリティリストの設定

ファンクションがBaseDBに接続できるようにするため、ポート1521を解放しておきます。

 

Oracle Instant Clientのダウンロード

ファンクションからBaseDBに接続する際にOracle Instant Clientを使用します。

以下からダウンロードしCloudShell上の任意のディレクトリにアップロードし展開します。
Oracle Instant Client Downloads | Oracle 日本

通知の設定

トピックの作成

はじめに、通知設定のためのトピックを作成します。

OCIコンソールのメニューから「開発者サービス」→「通知」を選択します。

トピックの作成」を選択し、以下の項目を入力してトピックを作成します。

  • 名前:任意(base-db-partition-notify)
  • 説明:任意(BaseDBのパーティション削除通知用)
  • コンパートメント:任意

作成したトピックの詳細画面に表示されるOCIDをテキストエディタなどに控えておきます。

このOCIDは、後ほどファンクションのPythonコード内で使用します。

サブスクリプションの作成

次に、サブスクリプションを作成します。

OCIコンソールのメニューから「開発者サービス」→「通知」→「サブスクリプション」を選択します。

サブスクリプションの作成」を選択し、以下の項目を入力してサブスクリプションを作成します。

  • サブスクリプション・トピック:先ほど作成したトピックを選択
  • プロトコル:電子メール
  • 電子メール:任意(通知を受け取りたいメールアドレスを指定)

サブスクリプション確認のためのメールが届きますのでメール内の「Confirm Subscription」を選択して承認します。

ファンクションの作成

アプリケーションの作成

次にアプリケーションを作成します。

OCIコンソールのメニューから「開発者サービス」→「アプリケーション」を選択します。

アプリケーションの作成」を選択し、以下の項目を入力してアプリケーションを作成します。

  • 名前:任意(fn-drop-partition)
  • VCNコンパートメント:任意
  • VCN:任意
  • サブネットコンパートメント:任意
  • サブネット:任意
  • シェイプ:GENERIC_X86

次に、ファンクションをデプロイします。

以下のファイルをすべてCloudShell上の同じディレクトリに配置します。

例)ディレクトリ構成
/home/user01/drop-partition/
├── Dockerfile
├── func.py
├── func.yaml
├── ng_template.txt
├── ok_template.txt
├── oracledb19c.env
├── requirements.txt
└── instantclient←事前準備でダウンロードしたOracle Instant Client

■Dockerfile

FROM oraclelinux:9-slim
WORKDIR /function
RUN groupadd --gid 1000 fn && adduser --uid 1000 --gid fn fn
RUN microdnf -y install python3 python3-pip libnsl libaio && \    microdnf clean all
ADD . /function
RUN pip3 install --upgrade pip
RUN pip3 install --no-cache --no-cache-dir -r requirements.txt
RUN rm -fr /function/.pip_cache ~/.cache/pip requirements.txt func.yaml Dockerfile
ENV LD_LIBRARY_PATH=<Oralce Instant Clientを配置するパス>:$LD_LIBRARY_PATHENV PYTHONPATH=/python
ENTRYPOINT ["/usr/local/bin/fdk", "/function/func.py", "handler"]

■func.py

import io
import os
import oracledb
import re
import oci
import string
import traceback
from fdk import response
from datetime import datetime
from os.path import join, dirname
from dotenv import load_dotenv

"""
日付をキーとしたレンジ・パーティションについて、古い日付のパーティションを削除して、
結果を通知するスクリプトです。
"""
def handler(ctx, data: io.BytesIO = None):
    try:
        partition_row = None
        error_msg = None
        trace_msg = None
        #DB接続情報を初期化
        dbpool = init_db()
        #削除対象のパーティションを取得
        partition_row = get_partition(dbpool)
        #削除対象のパーティションを削除
        drop_partition(partition_row['PARTITION_NAME'], dbpool)
    except Exception as ex:
        print("ERROR: Failed", flush=True)
        error_msg = str(ex)
        trace_msg = str(traceback.format_exc())
    finally:
        #メール送信
        send_mail(partition_row, error_msg, trace_msg)

"""
DB接続情報を初期化し、接続プールを作成する関数

Returns:
     dbpool(oracledb.ConnectionPool):Oracle Database 接続プールオブジェクト
"""     
def init_db():
    try:
        env_path = join(dirname(__file__), "oracledb19c.env")
        load_dotenv(env_path)
        instantclient_path = os.environ.get("INSTANTCLIENT")
        db_user = os.environ.get("DB_USER")
        db_password = os.environ.get("DB_PASSWORD")
        dsn = os.environ.get("DSN")
        os.environ["LD_LIBRARY_PATH"] = instantclient_path + ":" + os.environ.get("LD_LIBRARY_PATH", "")
        oracledb.init_oracle_client(lib_dir=instantclient_path)
        dbpool = oracledb.create_pool(user=db_user, password=db_password, dsn=dsn, min=1, max=10)
        return dbpool
    except Exception as ex:
        print("ERROR: Failed to initialize DB", flush=True)
        raise

"""
パーティションを日付の昇順にソートする関数

Args:
     partition_rows(dict):削除対象パーティションのリスト

Returns:
     sorted_rows(dict):削除対象パーティションのリスト(ソート後)
"""
def sort_partitions_by_date(partition_rows):
    try:
        datevalue = re.search(r"\d{4}-\d{2}-\d{2}", partition_rows['HIGH_VALUE'])
        return datetime.strptime(datevalue.group(), "%Y-%m-%d")
    except Exception as ex:
        print("ERROR: Failed to sort", flush=True)
        raise

"""
削除対象のパーティションを取得する関数

Args:
     dbpool(oracledb.ConnectionPool):Oracle Database 接続プールオブジェクト

Returns:
     sorted_rows[0](dict):削除対象パーティションの情報
"""
def get_partition(dbpool):
    sql_select = "select table_name, partition_name, high_value, num_rows from dba_tab_partitions where table_name = 'SALES'"
    try:
        with dbpool.acquire() as dbconnection:
            with dbconnection.cursor() as dbcursor:
                dbcursor.execute(sql_select)
                dbcursor.rowfactory = lambda *args: dict(zip([d[0] for d in dbcursor.description], args))
                results = dbcursor.fetchall()
                sorted_rows = sorted(results, key=sort_partitions_by_date)
                return sorted_rows[0]
    except Exception as ex:
        print("ERROR: Failed to get partition", ex , flush=True)
        raise

"""
指定したパーティションを削除して統計情報を取得する関数

Args:
     partition_name(str):削除対象のパーティション
     dbpool(oracledb.ConnectionPool):Oracle Database接続プールオブジェクト

Returns:
     None
"""
def drop_partition(partition_name, dbpool):
    sql_drop = f"alter table user01.sales drop partition {partition_name}"
    sql_stats = sql_stats = """
    BEGIN
      DBMS_STATS.GATHER_TABLE_STATS(
        ownname          => 'USER01',
        tabname          => 'SALES',
        estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
        method_opt       => 'FOR ALL COLUMNS SIZE AUTO',
        cascade          => TRUE
      );
    END;
    """
    try:
        with dbpool.acquire() as dbconnection:
            with dbconnection.cursor() as dbcursor:
                dbcursor.execute(sql_drop)
                dbcursor.execute(sql_stats)
    except Exception as ex:
        print("ERROR: Failed to delete partition", ex , flush=True)
        raise

"""
メールを送信する関数

Args:
     partition_row(dict):削除対象パーティションの情報
     error_msg(str):エラー発生時のエラーメッセージ
     trace_msg(str):エラー発生時のトレース情報

Returns:
     None
"""
def send_mail(partition_row, error_msg, trace_msg):
    signer = oci.auth.signers.get_resource_principals_signer()
    ons_client = oci.ons.NotificationDataPlaneClient(config={}, signer=signer)
    topic_id = "<トピックのOCID>"
    if error_msg is None and trace_msg is None:
        date_str = re.search(r"\d{4}-\d{2}", partition_row['HIGH_VALUE'])
        year_month =date_str.group().split("-")
        year = int(year_month[0])
        month = int(year_month[1])
        if month == 1:
            year -= 1
            month =12
        else:
            month -=1
        with open("/function/ok_template.txt") as ok_template_file:
            ok_template_msg = string.Template(ok_template_file.read())
        status = "OK"
        title = f"【{status}】{year}年{month}月分パーティション削除"
        body = ok_template_msg.substitute(table_name=partition_row['TABLE_NAME'], partition_name=partition_row['PARTITION_NAME'], row_count=partition_row['NUM_ROWS'])
        message_details = oci.ons.models.MessageDetails(
            title=title,
            body=body
        )
        ons_client.publish_message(
            topic_id=topic_id,
            message_details=message_details
        )
    else:
        with open("/function/ng_template.txt") as ng_template_file:
            ng_template_msg = string.Template(ng_template_file.read())
        status = "NG"
        title = f"【{status}】パーティション削除失敗"
        body = ng_template_msg.substitute(error_msg=error_msg, trace_msg=trace_msg)
        message_details = oci.ons.models.MessageDetails(
            title=title,
            body=body
        )
        ons_client.publish_message(
            topic_id=topic_id,
            message_details=message_details
        )

■func.yaml

schema_version: 20180708
name: <ファンクション名>
version: 0.0.23
runtime: podman
entrypoint: /python/bin/fdk /function/func.py handler
memory: 256

■ng_template.txt

エラーが発生しました。
エラー内容:$error_msg
Traceback:$trace_msg

■ok_template.txt

■対象テーブル
 テーブル名:$table_name
 パーティション名:$partition_name
 件数:$row_count

■oracledb19c.env

INSTANTCLIENT=<Oracle Instant Clinetを配置するパス>
DB_USER=<DBユーザ名>
DB_PASSWORD=<DBパスワード>
DSN=<ホスト>:1521/<サービス名>?connect_timeout=15

■requirements.txt

fdk
oracledb
oci
python-dotenv

以下のコマンドをCloud Shellで実行しファンクションをデプロイします。

1.リポジトリ名の一意の接頭辞を指定
 fn update context registry <region-key>/<tenancy-namespace>/<接頭辞>
2.コンテナ・レジストリにログイン
 docker login <region-key>.ocir.io
3.プロンプトが表示されたら、事前に確認・生成しておいた認証情報を入力
 Username: <tenancy-namespace>/<username>
 Password: <認証トークン>
4.Dockerfile等を格納しているディレクトリに移動しファンクションをデプロイ
 fn -v deploy --app <アプリケーション名>

以下は実際にコマンドを実行した結果の一部となります。

動作確認

成功時の動作確認

次にファンクションを実行し成功時の動作確認を行います。

SALESテーブルから直近3か月分のデータを保持し、それ以外の最も古い2025年8月分のデータを削除します。

※HIGH_VALUE列には各パーティションの境界値(上限値)が格納されています。
たとえば、HIGH_VALUEが「TO_DATE(' 2025-09-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORI」の場合は2025年8月分のデータが格納されていることを意味します。

以下のコマンドでファンクションを実行します。

1.ファンクション実行
 fn invoke <アプリケーション名> <ファンクション名>

ファンクション実行後、対象テーブル名、削除したパーティション名、および削除件数がメールで通知されます。

メールの通知内容は以下のように表示されます。

SALESテーブルでは直近3か月分のデータは保持され、最も古い2025年8月分のデータのみ削除されています。

失敗時の動作確認

次に、失敗時の動作確認をします。

データベースを停止します。

先ほどと同じコマンド使用してファンクションを実行します。

ファンクション実行後、エラー内容がメールで通知されます。

メールの通知内容は以下のように表示されます。

今回のエラー内容からデータベースが停止しているため接続できなかったことがわかります。

さいごに

本記事では、OCI Functionsを使用してBaseDBのパーティション化されたテーブル内の最も古いパーティションを削除して結果をメールで通知する方法についてご紹介しましたがいかがでしたでしょうか。

今回作成したファンクションをリソース・スケジューラで月に一度実行するよう設定すれば、直近3か月分のデータを保持しつつ、古いデータを自動で削除することも可能です。

この記事を通じ、皆様の問題解決の一助となれば幸いです。

最後まで読んで頂き、ありがとうございました。

参考

以下のドキュメントもあわせてご参照ください。

ドキュメント
Functionsの概要
【初心者向け】Oracle Functionsをつかってサーバレスを始めてみました! | Oracle Cloud のことなら Cloudii(クラウディ)
OCI Functionsサービスでイベント通知メールを成型してみる(3) | LAC WATCH


この記事が気に入ったら
「いいね!」

この記事を書いた人


関連コンテンツ

CONTACT お問い合わせ

Cloudiiのサービスやプロダクトについて
興味をお持ちの方は、
お気軽にお問い合わせください。