UPDATEステートメントのAPIコール数や更新行数をどう抑えるのか?

Invantive SQLは、技術的な仕組みを裏側に隠すことで、データの一括更新や同期を簡単に行えるようにします。update文および/またはsynchronize文を使えば、すべての業務プロセスが同じデータを使用するように、クラウドアプリケーション内のデータを簡単に一括更新できます。

一部のクラウドアプリケーションでは、技術的理由、フェアユース、あるいは商業的な理由からAPI呼び出しの回数に制限を設けています。この制限を超えると、「Too many requests」といったエラーメッセージやHTTPステータスコード429が返されることがよくあります。

このようなAPI呼び出し数の制限は、一般的に「レート制限(rate limit)」と呼ばれます。日単位、月単位、分単位、API単位、API操作単位(読み取り/書き込み)、あるいはAPI結果単位(エラーの有無を問わず)など、複数のレート制限が同時に適用されることもあります。

多くの場合、追加のAPI呼び出しを購入することでレート制限を引き上げることができ、前払いまたは後払いでの精算が選べます。プラットフォームによっては、API呼び出しのレート制限の引き上げが比較的高額で、複雑な呼び出しの場合には1件あたり数十ユーロに達することもあれば、逆に非常に安価なプラットフォームもあります。

月や日のように時間単位ごとに回数が制限されている場合は、利用可能なレート制限の枠を適切なタイミングで使うことが賢明です。優先度の低い変更は期間の終わりに実行することで、クラウドプラットフォームへの投資から最大限の効果を得られます。

API呼び出し回数を時間的に調整するのは、まずAPI呼び出し回数そのものを制限した後に行ってください。機能面でのヒントはHoe plan ik mijn Exact Online-omgeving op het beschikbare aantal API-calls? を、技術面でのヒントはたとえばSnel en met minder API-calls Exact Online uitlezen met de incrementele "sync API"-tabellen をご覧ください。

Invantive SQLは、利用可能なレート制限の枠をできるだけ効果的に使うための機能を備えています。ほとんどのプラットフォームでは、データディクショナリービューSystemPartitions@DataDictionaryの列にこの利用可能な枠が表示されます。ここには、日・時間・分単位のレート制限、および当日・当時間・当分内のリクエスト数を示す列が含まれています。

synchronize文では、最大でいくつの変更を行うかを指定できます。これは従来のupdate文、delete文、insert文でも同様に可能です。

update、delete、insertの最大変更件数は、select文でlimitが選択結果の行数を制限するのと同様に、limit 件数で設定できます。

Exact Online APIを用いた実際のケースを基に、これを説明します。

ケーススタディ:請求書添付ファイルの変更

Exact Onlineの会計システムでは、管理環境ごとに取引先単位で、請求書をどの形式で送付するかを設定できます。これは複数の形式を組み合わせることができ、いわゆる「ビット単位のAND(bitwise AND)」によって一つの数値にまとめられます。たとえば値「1」は売上請求書をPDF形式のみで送付することを意味し、値「2」はUBL形式、値「3」はPDF形式とUBL形式の両方を意味します。

それぞれ5万件の取引先を抱える管理環境を持つExact Onlineデータベースにおいて、すべての管理環境で請求書添付ファイルの形式をPDF+UBLからPDFのみに変更する必要があります。しかし、Exact OnlineにおけるAPI呼び出しの1日あたりの回数は、必要な5万回のAPI呼び出しのごく一部に過ぎず、しかもこの変換作業は日次処理よりも優先度が低いものです。

そのため、1日の終わりにこれらの変更をExact Onlineに反映させ、この追加リクエストによって残っているAPI呼び出し予算を消費させるようにします。

この変更の中で最も優先度が高いのは販売顧客に対するものであり、それ以外の取引先はまだ顧客ステータスを持っていません。

このジョブでは、Exact Onlineに対する変更件数を次のupdate文によって10件に制限できます。

use SELECTIE_ADMINISTRATIES@eol

update exactonlinerest..accounts@eol
set    InvoiceAttachmentType = 1
where  InvoiceAttachmentType = 3
and    status = 'C'
limit  100

ここでは、Exact OnlineのテーブルAccountsIncrementalは使用していません。このテーブルは読み取り専用であり、より複雑なsynchronize文が必要になります。一方、where句はInvantiveのサーバーサイドフィルタリングとExact Onlineのフィルター機能によって、そのままExact Onlineに渡すことができるため、更新対象の行を取得するのに必要なExact OnlineへのAPI呼び出しはわずか1回で済みます。

この文は、変更が必要な顧客がまだ残っている場合でも、およそ3秒ほどで完了します。

残っているAPI呼び出し予算があれば、顧客ではない取引先についても請求書添付ファイルの種類を設定するために使うことができます。

use SELECTIE_ADMINISTRATIES@eol

update exactonlinerest..accounts@eol
set    InvoiceAttachmentType = 1
where  InvoiceAttachmentType = 3
limit  100

改良:複数の管理環境への対応

この一括変更は、複数の管理環境にも簡単に拡張できます。そのためには、まず管理環境の選択を指定します。たとえばすべてのExact Online管理環境を対象にする場合は次のようになります。

use all@eol

update exactonlinerest..accounts@eol
set    InvoiceAttachmentType = 1
where  InvoiceAttachmentType = 3
and    status = 'C'
limit  1000

update exactonlinerest..accounts@eol
set    InvoiceAttachmentType = 1
where  InvoiceAttachmentType = 3
limit  1000

Exact Onlineでは現在、API呼び出しの制限が管理環境ごとに適用されているため、この制限をより高く設定することが可能です。この例では10件の管理環境を想定しており、並列処理によって1000件の更新がおおよそ10件の管理環境全体に分散されます。ただし、管理環境ごとに必要なAPI更新の量が大きく異なる場合はこの限りではありません。

改良:できるだけ短時間での空きAPI呼び出し枠の活用

さらに高度な改良としては、API呼び出しの回数をその管理環境で利用可能な分だけに制限しつつ、(並列化を行わずに)処理時間を最小限に抑える方法があります。

次のInvantive PSQLのブロックは、短い一連の更新を繰り返し実行するもので、Exact Onlineの1分あたり60回というAPI呼び出しの分間制限をできるだけ早く使い切るように長さが調整されています。

declare
  l_did_work  boolean;
  l_dummy_cnt number;
  g_max_call_per_minute pls_integer := 50 /* より頻繁に切り替えられるよう、60よりやや低い値に設定。 */;
begin
  use all@eol;
  --
  -- 残りのAPI呼び出し数が最新になるよう、
  -- すべての管理環境に一度アクセスする。
  --
  select /*+ http_disk_cache(false) http_memory_cache(false) */ count(*)
  into   l_dummy_cnt
  from   exactonlinerest..journals@eol
  ;
  l_did_work := true;
  while l_did_work
  loop
    dbms_output.put_line('1分間、各管理環境のAPI呼び出し分間制限に到達するよう試みる。');
    l_did_work := false;
    for r
    in 
    ( select 'update exactonlinerest..accounts@eol'
              || ' set InvoiceAttachmentType = 1'
              || ' where  InvoiceAttachmentType = 3'
              || ' limit  ' 
              || to_char
                 ( case
                   when DAILY_REMAINING > g_max_call_per_minute
                   then g_max_call_per_minute
                   else DAILY_REMAINING
                        --
                        -- 本日後ほど発生する可能性のある予期しない優先タスクのために、
                        -- Exact OnlineのAPI呼び出しを500回分残しておく。
                        --
                        - 500
                   end
                 )
              stmt
       ,      '最大' 
              || g_max_call_per_minute 
              || '行を管理環境 ' 
              || code
              || ' - ' 
              || name 
              || ' で更新する'
              txt
       from   SYSTEMPARTITIONS@DataDictionary
       where  IS_SELECTED = true
       and    PROVIDER_NAME = 'ExactOnlineAll'
       and    PROVIDER_DATA_CONTAINER_ALIAS = 'eol'
       --
       -- 本日、この管理環境に対してAPI呼び出しが
       -- 少なくとも500回残っている必要がある。
       --
       and    DAILY_REMAINING > 500
    )
    loop
      dbms_output.put_line(r.txt);
      execute immediate r.stmt;
      l_did_work := true;
    end loop;
  end loop;
end;