デプロイされたアーカイブでのアプリケーション状態の管理
R2026bこの例では、MATLAB® Production Server™ にデプロイされたアプリケーション アーカイブ内の永続データを管理する方法を示します。MATLAB Production Server MATLAB 関数実行のための RESTful API と JSON を使用して、MATLAB アプリの 1 つ以上のインスタンスをサーバーにデプロイされたアーカイブに接続します。
MATLAB Production Server ワーカーはステートレスです。永続性により、サーバーにデプロイされた MATLAB コードへの複数回の呼び出し間でデータをキャッシュし、状態を維持するためのメカニズムが提供されます。複数のワーカーが、キャッシュされたデータにアクセスできます。
メモ
サーバーへの呼び出し間では MATLAB ワークスペース変数は共有されませんが、リクエストの処理中に行われた特定の環境レベルの変更は、そのリクエストを処理したワーカー プロセス内に保持されることがあります。たとえば、デプロイされた関数が javaaddpath (MATLAB) を使用して Java 動的クラスパスを変更した場合、同じワーカーにルーティングされた後続のリクエストには、変更されたクラスパスが引き継がれます。リクエストからワーカーへのルーティングは保証されておらず、ワーカーは定期的に再起動されるため、この動作には依存しないでください。デプロイされた関数で Java 動的クラスパスや同様のプロセスレベルの状態を変更する場合は、クリーンな状態であることを前提とせず、各呼び出しの開始時に現在の環境を確認するように関数を設計してください。
この例では、2 つのワークフローについて説明します。
アプリケーションをサーバーにデプロイする前に、MATLAB デスクトップ環境でその機能をテストするためのテスト ワークフロー。
アクティブな MATLAB Production Server インスタンスを使用してアーカイブをデプロイするデプロイ ワークフロー。
永続性の使用方法を示すため、この例では、都市間の最短経路を求める巡回セールスマン問題を使用します。この実装では、永続的な MATLAB graph (MATLAB) オブジェクトをデータ キャッシュに格納します。都市がグラフのノードを形成し、都市間の距離がグラフのエッジに関連付けられた重みとなります。この例では、グラフは完全グラフです。テスト ワークフローでは、経路探索関数のローカル バージョンを使用します。デプロイ ワークフローでは、アーカイブにパッケージ化されてサーバーにデプロイされた経路探索関数を使用します。MATLAB アプリは、経路探索関数を呼び出します。これらの関数は、キャッシュからグラフ データを読み取り、キャッシュにグラフ データを書き込みます。
この例のコードは にあります。ここで、$MPS_INSTALL/client/matlab/examples/persistence/TravelingSalesman$MPS_INSTALL は MATLAB Production Server がインストールされている場所です。
[Production Server コンパイラ] アプリで作成したデプロイ可能なアーカイブをホストするには、アーカイブの作成に使用した MATLAB のバージョンと互換性のあるバージョンの MATLAB Runtime がインストールされている必要があります。詳細については、MATLAB Production Server でサポートされる MATLAB Runtime バージョンを参照してください。
手順 1: 永続性関数を使用する MATLAB コードの作成
永続データを初期化する関数を作成します。
都市と距離のグラフがデータ キャッシュに存在するかどうかを確認する関数を作成します。グラフが存在しない場合は、距離データを含む Excel® スプレッドシートからグラフを作成し、キャッシュに書き込みます。この書き込み操作を一度に実行できる MATLAB Production Server ワーカーは 1 つだけであるため、同期ロックを使用して、データの初期化が 1 回だけ実行されるようにします。
mps.cache.connectを使用して、距離データを格納するキャッシュに接続するか、キャッシュが存在しない場合は作成します。書き込み操作の間、mps.sync.mutexを使用してミューテックスのロックを取得します。データがキャッシュに書き込まれたら、ロックを解放します。loadDistanceData関数を使用して距離データを初期化します。function tf = loadDistanceData(connectionName, cacheName) c = mps.cache.connect(cacheName,'Connection',connectionName); tries = 0; while isKey(c,'Distances') == false && tries < 6 lk = mps.sync.mutex('DistanceData','Connection',connectionName); if acquire(lk,10) if isKey(c,'Distances') == false g = initDistanceData('Distances.xlsx'); c.Distances = g; end release(lk); end tries = tries + 1; end tf = isKey(c,'Distances'); end
永続データを読み取る関数を作成します。
データ キャッシュから距離データのグラフを読み取る関数を作成します。キャッシュからのデータ読み取りは冪等な操作であるため、同期ロックを使用する必要はありません。
mps.cache.connectを使用してキャッシュに接続し、その後グラフを取得します。キャッシュからグラフを読み取り、
listDestinations関数を使用して cell 配列に変換します。findRoute関数を使用して、可能な最短経路を計算します。最近傍アルゴリズムを使用し、指定された都市から開始して、すべての都市を訪問するまで、その時点で最も近い次の都市を繰り返し訪問します。function destinations = listDestinations() c = mps.cache.connect('TravelingSalesman','Connection','ScratchPad'); if loadDistanceData('ScratchPad','TravelingSalesman') == false error('Failed to load distance data. Cannot continue.'); end g = c.Distances; destinations = table2array(g.Nodes); end
function [route,distance] = findRoute(start,destinations) c = mps.cache.connect('TravelingSalesman','Connection','ScratchPad'); if loadDistanceData('ScratchPad','TravelingSalesman') == false error('Failed to load distance data. Cannot continue.'); end g = c.Distances; route = {start}; distance = 0; current = start; while ~isempty(destinations) minDistance = Inf; nextSegment = {}; for n = 1:numel(destinations) [p,d] = shortestpath(g,current,destinations{n}); if d < minDistance nextSegment = p(2:end); minDistance = d; end end current = nextSegment{end}; distance = distance + minDistance; destinations = setdiff(destinations,current); route = [ route nextSegment ]; end end
永続データを変更する関数を作成します。
新しい都市を追加する関数を作成します。都市を追加すると、データ キャッシュに保存されているグラフが変更されます。この操作ではキャッシュへの書き込みが必要になるため、ロックにはステップ 1 で説明した
mps.sync.mutex関数を使用します。都市を追加した後、すべての都市の組み合わせについて都市間の距離が既知であることを確認し、グラフが引き続き完全グラフであることを確認します。addDestination関数を使用して都市を追加します。都市を追加すると、新しいグラフ ノードnameとともに、このノードをグラフ内の既存のすべてのノードに接続する新しいエッジが追加されます。新しく追加されたエッジの重みは、ベクトルdistancesで指定されます。destinationsは、グラフ内の他の都市の名前を含む文字ベクトルの cell 配列です。function count = addDestination(name, destinations, distances) count = 0; c = mps.cache.connect('TravelingSalesman','Connection','ScratchPad'); if loadDistanceData('ScratchPad','TravelingSalesman') == false error('Failed to load distance data. Cannot continue.'); end lk = mps.sync.mutex('DistanceData','Connection','ScratchPad'); if acquire(lk,10) g = c.Distances; newDestinations = setdiff(g.Nodes.Name, destinations); if ~isempty(newDestinations) error('MPS:Example:TSP:MissingDestinations', ... 'Add distances for missing destinations: %s', ... strjoin(newDestinations,', ')); end src = repmat({name},1,numel(destinations)); g = addedge(g, src, destinations, distances); c.Distances = g; release(lk); count = numnodes(g); end end
経路探索関数を呼び出す MATLAB アプリを作成します。
ステップ 2 とステップ 3 で説明した関数を、それぞれ対応するプロキシ関数でラップする MATLAB アプリを作成します。このアプリでは、ホストとポートを指定できます。テストでは、ホストが空白で、ポートの値が 0 の場合に、経路探索関数のローカル バージョンを呼び出します。デプロイ ワークフローでは、指定したホストとポートで稼働しているサーバー上のデプロイ済み関数を呼び出します。
webwrite(MATLAB) 関数を使用して、HTTP POST リクエストをサーバーに送信します。アプリの作成方法の詳細については、App Designer を使用した簡単なアプリの作成と実行 (MATLAB)を参照してください。
findRoute、addDestination、listDestination関数に対して、それぞれプロキシ関数findRouteProxy、addDestinationProxy、listDestinationProxyを作成します。function destinations = listDestinationsProxy(app) if isempty(app.HostEditField.Value) && ... app.PortEditField.Value <= 0 destinations = listDestinations(); return; end listDestinations_OPTIONS = weboptions('MediaType','application/json','Timeout',60,'ContentType','raw'); listDestinations_HOST = app.HostEditField.Value; listDestinations_PORT = app.PortEditField.Value; noInputJSON = '{ "rhs": [], "nargout": 1 }'; destinations_JSON = webwrite(sprintf('http://%s:%d/TravelingSalesman/listDestinations', ... listDestinations_HOST,listDestinations_PORT), noInputJSON, listDestinations_OPTIONS); if iscolumn(destinations_JSON), destinations_JSON = destinations_JSON'; end destinations_RESPONSE = mps.json.decoderesponse(destinations_JSON); if isstruct(destinations_RESPONSE) error(destinations_RESPONSE.id,destinations_RESPONSE.message); else if nargout > 0, destinations = destinations_RESPONSE{1}; end end end
function [route,distance] = findRouteProxy(app,start,destinations) if isempty(app.HostEditField.Value) && ... app.PortEditField.Value <= 0 [route,distance] = findRoute(start,destinations); return; end findRoute_OPTIONS = weboptions('MediaType','application/json','Timeout',60,'ContentType','raw'); findRoute_HOST = app.HostEditField.Value; findRoute_PORT = app.PortEditField.Value; start_destinations_DATA = {}; if nargin > 0, start_destinations_DATA = [ start_destinations_DATA { start } ]; end if nargin > 1, start_destinations_DATA = [ start_destinations_DATA { destinations } ]; end route_distance_JSON = webwrite(sprintf('http://%s:%d/TravelingSalesman/findRoute', ... findRoute_HOST,findRoute_PORT), ... mps.json.encoderequest(start_destinations_DATA,'nargout',nargout), findRoute_OPTIONS); if iscolumn(route_distance_JSON), route_distance_JSON = route_distance_JSON'; end route_distance_RESPONSE = mps.json.decoderesponse(route_distance_JSON); if isstruct(route_distance_RESPONSE) error(route_distance_RESPONSE.id,route_distance_RESPONSE.message); else if nargout > 0, route = route_distance_RESPONSE{1}; end if nargout > 1, distance = route_distance_RESPONSE{2}; end end end
function count = addDestinationProxy(app, name, destinations,distances) if isempty(app.HostEditField.Value) && ... app.PortEditField.Value <= 0 count = addDestination(name, destinations,distances); return; end addDestination_OPTIONS = weboptions('MediaType','application/json','Timeout',60,'ContentType','raw'); addDestination_HOST = app.HostEditField.Value; addDestination_PORT = app.PortEditField.Value; name_destinations_distances_DATA = {}; if nargin > 0, name_destinations_distances_DATA = [ name_destinations_distances_DATA { name } ]; end if nargin > 1, name_destinations_distances_DATA = [ name_destinations_distances_DATA { destinations } ]; end if nargin > 2, name_destinations_distances_DATA = [ name_destinations_distances_DATA { distances } ]; end count_JSON = webwrite(sprintf('http://%s:%d/TravelingSalesman/addDestination', ... addDestination_HOST,addDestination_PORT), ... mps.json.encoderequest(name_destinations_distances_DATA,'nargout',nargout), addDestination_OPTIONS); if iscolumn(count_JSON), count_JSON = count_JSON'; end count_RESPONSE = mps.json.decoderesponse(count_JSON); if isstruct(count_RESPONSE) error(count_RESPONSE.id,count_RESPONSE.message); else if nargout > 0, count = count_RESPONSE{1}; end end end
手順 2: テスト ワークフローでの例の実行
MATLAB デスクトップ環境でサンプル コードをテストします。そのためには、 にあるすべてのファイルを、$MPS_INSTALL/client/matlab/examples/persistence/TravelingSalesman/tmp/persistence_example など、システム上の書き込み可能なフォルダーにコピーします。MATLAB デスクトップを起動し、cd (MATLAB) コマンドを使用して現在の作業ディレクトリを /tmp/persistence_example に設定します。
テスト目的では、mps.cache.control 関数を使用して、MATLAB デスクトップから永続性サービスを制御します。この関数は、ローカル永続性サービスのライフサイクルを管理する mps.cache.Controller オブジェクトを返します。
Redis™ 永続性プロバイダーを使用するローカル永続性サービス用の
mps.cache.Controllerオブジェクトを作成します。>> ctrl = mps.cache.control('ScratchPad', 'Redis', 'Port', 8675);
このコントローラーがアクティブになると、
ScratchPadという名前の接続が有効になります。接続名は、キャッシュを永続性サービス内の保存場所に関連付けます。mps.cache.connect関数でデータ キャッシュを作成するには、接続名が必要です。MATLAB Production Server 管理者は、キャッシュ構成ファイルmps_cache_configで接続名を設定します。詳細については、Redis を使用するためのサーバーの構成を参照してください。MATLAB デスクトップ セッションで同じ接続名を使用することで、コードを変更せずに開発からテストを経て本番環境へ移行できます。startを使用して永続性サービスを開始します。>> start(ctrl);
永続性サービスを使用する
TravelingSalesman経路探索アプリを開始します。>> TravelingSalesman
アプリは、[ホスト] と [ポート] に既定値が設定された状態で開始します。
[Load Cities] をクリックして、都市のリストを読み込みます。[開始] メニューを使用して出発地点を設定し、[>>] および [<<] ボタンを使用して、訪問する都市を選択または選択解除します。[Compute Path] をクリックして、すべての都市を訪問する経路を表示します。

アプリを閉じるときは、
stopを使用して永続性サービスを停止します。永続性サービスを停止すると、そのサービスによって保存されているデータが削除されます。>> stop(ctrl);
手順 3: デプロイ ワークフローでの例の実行
デプロイ ワークフローでこの例を実行するには、 にあるすべてのファイルを、$MPS_INSTALL/client/matlab/examples/persistence/TravelingSalesman/tmp/persistence_example など、システム上の書き込み可能なフォルダーにコピーします。MATLAB デスクトップを起動し、MATLAB cd (MATLAB) コマンドを使用して現在の作業ディレクトリを /tmp/persistence_example に設定します。
デプロイ ワークフローでは、MATLAB デスクトップ環境の外部で永続性サービスのライフサイクルを管理し、サーバーにデプロイされたアーカイブにパッケージ化されている経路探索関数を呼び出します。
MATLAB Production Server インスタンスを作成します。
mps-newを使用して、システム コマンド ラインからサーバーを作成します。詳細については、コマンド ラインを使用したサーバー インスタンスの作成を参照してください。サーバー環境をまだセットアップしていない場合、詳細については、mps-setupを参照してください。フォルダー
tmpに新しいサーバーserver_1を作成します。mps-new /tmp/server_1
または、MATLAB Production Server ダッシュボードを使用してサーバーを作成します。詳細については、MATLAB Production Server Admin Portal のセットアップとログインを参照してください。
永続性サービス接続を作成します。
デプロイ可能なアーカイブには、
ScratchPadという名前の永続性サービス接続が必要です。ダッシュボードを使用してScratchPad接続を作成するか、サンプル ディレクトリからファイルmps_cache_configをサーバー インスタンスのconfigディレクトリにコピーします。configディレクトリにmps_cache_configファイルが既に存在する場合は、そのファイルを編集して、サンプルのmps_cache_configで指定されているScratchPad接続を追加します。Production Server コンパイラ アプリを使用して、デプロイ可能なアーカイブを作成し、サーバーにデプロイします。
[Production Server アーカイブ コンパイラ] を開きます。
MATLAB ツールストリップ: [アプリ] タブの [アプリケーションのデプロイ] で、[Production Server アーカイブ コンパイラ] をクリックします。
MATLAB コマンド プロンプト: 「
productionServerArchiveCompiler」と入力します。
[エクスポートする関数] フィールドに、
findRoute.m、listDestinations.m、addDestination.mを追加します。アーカイブの名前を
TravelingSalesmanに変更します。[カスタム要件] で、
Distances.xlsxを追加します。[パッケージ化] をクリックします。
デプロイ可能なアーカイブ
TravelingSalesman.ctfがプロジェクトの出力フォルダーに生成されます。ホストするために、TravelingSalesman.ctfファイルをサーバーのauto_deployフォルダーにコピーします。この例では/tmp/server_1/auto_deployです。
サーバー インスタンスを開始します。
mps-startを使用して、システム コマンド ラインからサーバーを開始します。または、ダッシュボードを使用してサーバーを開始します。mps-start -C /tmp/server_1
永続性サービスを開始します。
mps-cacheを使用して、システム コマンド ラインから永続性サービスを開始します。または、ダッシュボードを使用して永続性サービスを開始し、アタッチします。mps-cache start -C /tmp/server_1 --connection ScratchPad
アプリをテストします。
永続性サービスを使用する
TravelingSalesman経路探索アプリを開始します。>> TravelingSalesman
アプリは、[ホスト] と [ポート] に空の値が設定された状態で開始します。
server_name/configにあるサーバー構成ファイルmain_configを参照して、MATLAB Production Server インスタンスのホスト値とポート値を取得します。この例では、構成ファイルは/tmp/server_1/configにあります。アプリにホストとポートの値を入力します。[Load Cities] をクリックして、都市のリストを読み込みます。[開始] メニューを使用して出発地点を設定し、[>>] および [<<] ボタンを使用して、訪問する都市を選択または選択解除します。[Compute Path] をクリックして、すべての都市を訪問する経路を表示します。

テスト環境のワークフローとデプロイ環境のワークフローの結果は同じです。
参考
mps.cache.Controller (MATLAB Compiler SDK) | mps.cache.DataCache (MATLAB Compiler SDK) | mps.sync.TimedMATFileMutex (MATLAB Compiler SDK) | mps.sync.TimedRedisMutex (MATLAB Compiler SDK) | mps.cache.control (MATLAB Compiler SDK) | mps.cache.connect (MATLAB Compiler SDK) | mps.sync.mutex (MATLAB Compiler SDK)