分散デプロイメント

複数ノードから1つのドキュメントを提供する

複数のノードが同じドキュメントを提供する場合、ドキュメントをレンダリングするノードが必ずしもブラウザーのページリクエストに応答するノードとは限りません。このリリースではその作業を 書き込み側 と 読み取り側 の二つの明確な半分に分割します。書き込み側はドキュメントのアーティファクトを共有ストレージに公開し、読み取り側は任意のノードからそれらを提供します。

現在のアプリケーションが UseDoconutWebFarm(...) を呼び出している場合、このページがその代替になります。UseDoconutWebFarm も WebFarmOptions もこのリリースには存在しません。

トポロジーは変わりません。 今日使用している UNC パスを共有ルートとして指定すれば、すべてのノードは現在と同じように動作し続けます。変わるのは、公開がレンダリングの副作用ではなく、呼び出す操作になることです。

web-farm ミドルウェアの代替は何か

以前は 1 つのミドルウェアがすべてを行っていました。共有フォルダーから事前にレンダリングされた画像を提供し、別のノードがまだ書き込み中である間に ポーリング していました。待機は盲目的な予算であり、"ページが表示されるまで最大 3 分ブロックする" というものでした。

現在は同じ作業が 2 つの呼び出しに分かれ、見えていて理由付けが可能です。

text
node A            OpenDocument request
                      -> PublishAsync(...)   writes pages, document info,
                                             search index and metadata
                                             into  RootPath/{token}/
                      -> returns the token

node B            ?token=...&page=7
                      -> UseDoconutCloud<FileShareCloudHandler>
                      -> reads RootPath/{token}/ and serves the page

両側が必要とする共有設定

パブリッシャーは FileShareConfig で書き込み、読み取りハンドラは同じ設定で提供します。RootPath または UsePerTokenSubfolders が両者で異なると、もう一方のノードは 404 を返します。 設定は正確に 1 カ所で構築し、両側から呼び出すようにします。参照アプリケーションはこの理由で単一の SharedStore.Config(...) ヘルパーを保持しています。

csharp
internal static class SharedStore
{
    public static FileShareConfig Config(string rootPath, bool asyncPublish) => new()
    {
        Location              = CloudLocation.FileShare,
        RootPath              = rootPath,               // e.g. \\fileserver\doconut
        UsePerTokenSubfolders = true,
        ReadReadyPollMs       = asyncPublish ? 250 : 0,
    };
}

FileShareConfig は Doconut.Clouds にあり、AzureConfig、AmazonConfig、GoogleCloudConfig、DropBoxConfig、RedisConfig、CDNConfig、FTPConfig と共存します。以前のライブラリではこれらは Doconut.Configs.Cloud にありましたが、クラス名は変更されていません。

プロパティ用途
LocationUNC/ローカル共有用の CloudLocation.FileShare
RootPathすべてのノードがアクセスできる共有フォルダー
UsePerTokenSubfolders各ドキュメントのアーティファクトを {token} フォルダーに分けて保持する
ReadReadyPollMsまだ書き込み中のページを待つ時間。0 にするとポーリングを無効化する

書き込み側 — ドキュメントを公開する

DistributedDocumentPublisher を注入し、ローカルセッションを開く代わりに公開します。1 回の呼び出しでページ、ドキュメント情報、検索インデックス、メタデータという完全なアーティファクトセットがレンダリングされ書き込まれます。

csharp
var shareConfig = SharedStore.Config(rootPath, asyncPublish: false);

var documentOptions = new DocOptions { TimeOut = 60 };
var config          = new PdfConfig { AllowSearch = true };

var publishOptions = new DistributedDocumentPublishOptions
{
    CleanUpStagingDirectory = true,
    FirstPagePriority       = true,
};

DistributedDocumentPublishResult result = await _publisher.PublishAsync(
    pathToFile, shareConfig, config, documentOptions, publishOptions, ct);

return Content(result.Token);

DistributedDocumentPublishResult には Token、ArtifactPath、TotalPages、Format が含まれます。トークンは通常のビューアートークンと同様にブラウザーへそのまま渡されます。

この単一呼び出しが、以前の 2 段階(ローカルセッションでドキュメントを開き、ExportToPng(sharedPath) で画像を共有にダンプする)を置き換えます。ExportToPng と ExportToCloud に相当するものはここにはありません。

読み取りサイド — 共有から提供

クライアントがすでに呼び出している同じブランチに FileShare ハンドラをマウントします:

csharp
app.UseDoconutResources();

app.MapWhen(
    ctx => ctx.Request.Path.Value?.EndsWith("DocImage.axd", StringComparison.OrdinalIgnoreCase) == true,
    branch => branch.UseDoconutCloud<FileShareCloudHandler>(options =>
    {
        options.Location          = CloudLocation.FileShare;
        options.CloudUploadConfig = SharedStore.Config(rootPath, asyncPublish: false);
    }, "/"));

UseDoconutCloud はこのリリースでジェネリック化されており、ミドルウェア内部で Location 値を切り替えるのではなく、プロバイダーが型引数として指定されます。ハンドラは <Provider>CloudHandler という名前で、FileShareCloudHandler、AzureCloudHandler、AmazonS3CloudHandler、GoogleCloudHandler、DropBoxCloudHandler、RedisCloudHandler、FTPCloudHandler、CDNCloudHandler が用意されています。

CDN ハンドラは 読み取り専用 です — 既に CDN に存在するものを提供するだけで、アップロードは行いません。

ファーストページ優先度 と HTTP 202

FirstPagePriority = true を設定すると、ページ 1 がすぐに配信可能になります。以降のページは書き込みが完了するまで HTTP 202 Accepted を返し、リクエストをブロックしません。

これにより待機がクライアント側に移り、待機時間が制限されて可視化されます。ウィジェットでリトライを有効にします:

javascript
const objViewer = $('#div_ctlDoc').docViewer({
    BasePath:   '/',
    ResPath:    'doconut-res/images',
    retryOn409: true      // 202(および従来の 409)準備完了レスポンスをリトライ
});

リトライしない クライアントは、まだレンダリング中のページに対して破損したタイルを表示します。リトライ予算とそのデフォルト値(retryInitialDelayMs、retryBackoffFactor、retryMaxDelayMs、retryMaxAttempts、retryMaxTotalMs)は ViewerConfig に記載されています。

この組み合わせ — FirstPagePriority と読み取り側ポーリング — が、従来の PageWaitTimeSeconds と StartWaitFromPage 設定に取って代わります。これらの設定はもう存在しません。

オプション: バックグラウンドでの公開

デフォルトでは PublishAsync は完了するまで待機してから戻ります。トークンが発行されたらすぐに戻し、ホストされたワーカーに同じフォルダーへレンダリングさせるには、オプトインキューを登録します:

csharp
builder.Services.AddDoconut(options => { /* … */ });
builder.Services.AddDoconutDistributedAsyncPublish();

その後、インラインで公開する代わりにキューにエンキューし、公開オプションで Async = true を設定します。キューはサイズが制限されており、飽和すると EnqueueAsync は DistributedPublishQueueFullException をスローします。これを捕捉して 503 と Retry-After ヘッダーを返すことで、クライアントにバックオフさせ、スタックさせません:

csharp
try
{
    var token = await _publishQueue.EnqueueAsync(
        pathToFile, shareConfig, config, documentOptions, publishOptions, ct);
    return Content(token);
}
catch (DistributedPublishQueueFullException)
{
    Response.Headers.RetryAfter = "5";
    Response.StatusCode = 503;
    return Content("The publish queue is full. Please retry.");
}

この登録がない場合、何も登録されず公開は同期的に実行されます — デフォルトの動作はバイト単位で完全に同一です。

バックグラウンド公開が有効なときは、読み取り側に ReadReadyPollMs を 0 以外に設定し、ワーカーがまだ書き込んでいないページを短時間待機させます。

ノード間のセキュリティ

セッションに紐付くトークンは、配信ノードが最初のリクエストを受け取ったことを前提としています。ファーム構成ではノードがリクエストを受け取っていないことがあるため、セッション単位のチェックが正当なリクエストを拒否してしまいます。そこで分散リファレンスアプリケーションは UnsafeMode = true を設定しています。

UnsafeMode = true はセキュリティモデルではなく、もはや適用できないモデルを除去したものです。 代わりに署名付きアクセスチケットを使用し、各ノードが個別に検証できるようにします:

csharp
builder.Services.AddDoconutDistributedDocumentSecurity(options => { /* … */ });

UnsafeMode = true をシングルノード環境に持ち込んだり、単一ノードでのセッションチェック失敗の回避策として使用したりしないでください。そのようなケースでは正しい対処は AddSession() / UseSession() です。詳細は セッションとセキュリティ を参照してください。

公開されたドキュメントを閉じる

CloseDocument(token) は、注入された Viewer 上でローカルセッションを破棄します。共有からドキュメントの公開アーティファクトを削除するために、パブリッシャーは次のメソッドを公開しています:

text
bool CloseDistributedDocument(FileShareConfig fileShareConfig, string token)

ドキュメントが公開された際に使用したのと同じ FileShareConfig を渡してください。

設定ごとのマッピング

以前現在
UseDoconutWebFarm(new WebFarmOptions { … })PublishAsync(...) を書き込み側で、UseDoconutCloud<FileShareCloudHandler>(...) を読み取り側で使用
WebFarmOptions.PathFileShareConfig.RootPath
PageWaitTimeSeconds / StartWaitFromPageDistributedDocumentPublishOptions.FirstPagePriority と FileShareConfig.ReadReadyPollMs
viewer.OpenDocument(...) then viewer.ExportToPng(share)1つの PublishAsync(...) 呼び出し
viewer.ExportToCloud(cloudConfig)プロバイダーの設定で PublishAsync(...)
UseDoconutCloud(new CloudOptions { Location = … })UseDoconutCloud<THandler>(options => …, pathPrefix)
DocOptions.IsWebfarm / WebfarmPathまだ存在し、IsWebFarm / WebFarmPath と綴られます — 公開パスでは不要です
Doconut.Configs.CloudDoconut.Clouds

チェックリスト

  1. FileShareConfig をヘルパーで作成し、両方の側から呼び出します。
  2. RootPath を既に使用している共有に設定します。
  3. PublishAsync(...) で公開し、ExportToPng / ExportToCloud の呼び出しはすべて削除します。
  4. クライアントが呼び出すブランチで UseDoconutCloud<FileShareCloudHandler>(...) を使用して提供します。
  5. FirstPagePriority と retryOn409 を同時に有効にするか、両方とも無効のままにします。
  6. セッションごとのチェックを署名付きアクセスチケットに置き換えます — UnsafeMode = true だけを出荷しないでください。
  7. あるノードでドキュメントを開き、別のノードから遅延ページを要求して検証します。

このページは役に立ちましたか?