CosyVoice の音声合成は、ストリーミングリアルタイム通信をサポートするため WebSocket プロトコルを使用します。ただし、高い同時実行性のシナリオでは、各リクエストごとに WebSocket 接続を作成・破棄すると、ネットワークおよびシステムリソースへの負荷が大きく、接続遅延も顕著になります。パフォーマンスを最適化し安定性を確保するため、DashScope SDK には接続プールやオブジェクトプールなど、効率的なリソース再利用メカニズムが組み込まれています。本ドキュメントでは、DashScope Python SDK および Java SDK を使用して、高い同時実行性条件下で CosyVoice を効率的に呼び出す方法について説明します。
前提条件
要件を満たす DashScope SDK のバージョンをインストールします。最新バージョンのインストールを推奨します:最新バージョンのインストール
Python SDK:バージョン 1.25.2 以降
Java SDK:バージョン 2.16.6 以降
Python SDK:オブジェクトプールによる最適化
Python SDK では、SpeechSynthesizerObjectPool を使用してオブジェクトプールの最適化を実装します。これにより、SpeechSynthesizer オブジェクトの管理および再利用が可能になります。
初期化時に、オブジェクトプールは指定された数の SpeechSynthesizer インスタンスを作成し、WebSocket 接続を事前に確立します。プールからオブジェクトを取得すると、新規接続の確立にかかる時間が発生しないため、即座にリクエストを送信できます。これにより、最初のパッケージ遅延(first-package latency)が低減されます。タスク終了後にオブジェクトをプールへ返却すると、その WebSocket 接続はオープン状態のまま保持され、再利用可能になります。
実装手順
依存関係のインストール:DashScope パッケージをインストールします(
pip install -U dashscope)。オブジェクトプールの作成および構成。
SpeechSynthesizerObjectPoolを使用してプールサイズを設定します。推奨値は、ピーク同時実行数の 1.5 倍~2 倍です。プールサイズは、ご利用のアカウントのクエリ/秒(QPS)制限を超えないように設定してください。以下のコードを使用して、グローバルなシングルトン固定サイズオブジェクトプールを作成します。このプールは初期化時に指定された数の
SpeechSynthesizerオブジェクトを作成し、WebSocket 接続を確立します。この処理には時間がかかります。from dashscope.audio.tts_v2 import SpeechSynthesizerObjectPool synthesizer_object_pool = SpeechSynthesizerObjectPool(max_size=20)プールから
SpeechSynthesizerオブジェクトを取得。現在借用中のオブジェクト数がプールの最大サイズを超える場合、システムは新しい
SpeechSynthesizerオブジェクトを作成します。この新しく作成されたオブジェクトは、初期化および WebSocket 接続の確立を行う必要があります。既存のプール内の接続を再利用できないため、プールによる恩恵を受けません。
speech_synthesizer = connectionPool.borrow_synthesizer( model='cosyvoice-v3-flash', voice='longanyang', seed=12382, callback=synthesizer_callback )音声合成の実行。
SpeechSynthesizerオブジェクトの call または streaming_call メソッドを呼び出して、音声を合成します。SpeechSynthesizerオブジェクトの返却。音声合成タスクが終了したら、後続のタスクで再利用できるよう、
SpeechSynthesizerオブジェクトを返却します。未完了または失敗したタスクからオブジェクトを返却しないでください。
connectionPool.return_synthesizer(speech_synthesizer)
完全なコード例
# !/usr/bin/env python3
# Copyright (C) Alibaba Group. All Rights Reserved.
# MIT License (https://opensource.org/licenses/MIT)
import os
import time
import threading
import dashscope
from dashscope.audio.tts_v2 import *
USE_CONNECTION_POOL = True
text_to_synthesize = [
'最初の文:Alibaba Cloud の音声合成へようこそ。',
'2 番目の文:Alibaba Cloud の音声合成へようこそ。',
'3 番目の文:Alibaba Cloud の音声合成へようこそ。',
]
connectionPool = None
if USE_CONNECTION_POOL:
print('接続プールを作成中')
start_time = time.time() * 1000
connectionPool = SpeechSynthesizerObjectPool(max_size=3)
end_time = time.time() * 1000
print('接続プールの作成完了。所要時間:{} ms'.format(end_time - start_time))
def init_dashscope_api_key():
'''
DashScope の API キーを設定します。詳細については、以下をご参照ください:
https://github.com/aliyun/alibabacloud-bailian-speech-demo/blob/master/PREREQUISITES.md
'''
# API キーはシンガポールリージョンと北京リージョンで異なります。API キーの取得方法:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
if 'DASHSCOPE_API_KEY' in os.environ:
dashscope.api_key = os.environ[
'DASHSCOPE_API_KEY'] # 環境変数 DASHSCOPE_API_KEY から API キーを読み込みます
else:
dashscope.api_key = '<your-dashscope-api-key>' # 手動で API キーを設定します
def synthesis_text_to_speech_and_play_by_streaming_mode(text, task_id):
global USE_CONNECTION_POOL, connectionPool
'''
ストリーミングモードで指定されたテキストを音声合成し、非同期で呼び出し、合成された音声をリアルタイムで再生します。
詳細については、https://www.alibabacloud.com/help/document_detail/2712523.html をご参照ください。
'''
complete_event = threading.Event()
# 結果を処理するコールバックを定義
class Callback(ResultCallback):
def on_open(self):
# オブジェクトプールを使用する場合、on_open はタスク開始後に呼び出されます
self.file = open(f'result_{task_id}.mp3', 'wb')
print(f'[task_{task_id}] 開始')
def on_complete(self):
print(f'[task_{task_id}] 音声合成タスクが正常に完了しました。')
complete_event.set()
def on_error(self, message: str):
print(f'[task_{task_id}] 音声合成タスクが失敗しました。{message}')
def on_close(self):
# オブジェクトプールを使用する場合、on_open はタスク終了後に呼び出されます
print(f'[task_{task_id}] 終了')
def on_event(self, message):
# print(f'recv speech synthsis message {message}')
pass
def on_data(self, data: bytes) -> None:
# プレーヤーへ送信
# 音声をファイルに保存
self.file.write(data)
# 音声合成コールバックを呼び出します
synthesizer_callback = Callback()
# 音声合成器を初期化します
# voice、フォーマット、サンプルレートなどの合成パラメーターをカスタマイズできます
if USE_CONNECTION_POOL:
speech_synthesizer = connectionPool.borrow_synthesizer(
model='cosyvoice-v3-flash',
voice='longanyang',
seed=12382,
callback=synthesizer_callback
)
else:
speech_synthesizer = SpeechSynthesizer(model='cosyvoice-v3-flash',
voice='longanyang',
seed=12382,
callback=synthesizer_callback)
try:
speech_synthesizer.call(text)
except Exception as e:
print(f'[task_{task_id}] 音声合成タスクが失敗しました。{e}')
if USE_CONNECTION_POOL:
# オブジェクトプールを使用中にタスクが失敗した場合は、合成器の接続を手動で閉じます。
speech_synthesizer.close()
return
print('[task_{}] 合成されたテキスト:{}'.format(task_id, text))
complete_event.wait()
print('[task_{}][メトリック] requestId:{}、最初のパッケージ遅延(ms):{}'.format(
task_id,
speech_synthesizer.get_last_request_id(),
speech_synthesizer.get_first_package_delay()))
if USE_CONNECTION_POOL:
connectionPool.return_synthesizer(speech_synthesizer)
# メイン関数
if __name__ == '__main__':
# 以下の URL はシンガポールリージョン向けです。北京リージョンのモデルを使用する場合は、代わりに wss://dashscope.aliyuncs.com/api-ws/v1/inference を使用してください。
dashscope.base_websocket_api_url='wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference'
init_dashscope_api_key()
task_thread_list = []
for task_id in range(3):
thread = threading.Thread(
target=synthesis_text_to_speech_and_play_by_streaming_mode,
args=(text_to_synthesize[task_id], task_id))
task_thread_list.append(thread)
for task_thread in task_thread_list:
task_thread.start()
for task_thread in task_thread_list:
task_thread.join()
if USE_CONNECTION_POOL:
connectionPool.shutdown()リソース管理およびエラー処理
タスク成功時:音声合成タスクが正常に完了した場合、
connectionPool.return_synthesizer(speech_synthesizer)を呼び出して、SpeechSynthesizerオブジェクトをプールへ返却し、再利用可能にします。重要未完了または失敗したタスクから
SpeechSynthesizerオブジェクトを返却しないでください。タスク失敗時:SDK またはビジネスロジックからの例外によってタスクが中断された場合、基盤となる WebSocket 接続を手動で閉じる必要があります:
speech_synthesizer.close()。すべての音声合成タスクが終了したら、
connectionPool.shutdown()を使用してオブジェクトプールをシャットダウンします。サービスが TaskFailed エラーを返した場合、追加のアクションは不要です。
Java SDK:接続プールおよびオブジェクトプールによる最適化
Java SDK では、内部接続プールとカスタムオブジェクトプールを組み合わせることで、最適なパフォーマンスを実現します。
接続プール:SDK は OkHttp3 の組み込み接続プールを使用して、基盤となる WebSocket 接続の管理および再利用を行います。これによりハンドシェイクのオーバーヘッドが削減され、デフォルトで有効になっています。
オブジェクトプール:
commons-pool2を基盤として構築されており、事前に接続が確立されたSpeechSynthesizerオブジェクトのグループを維持します。プールからオブジェクトを取得することで、接続確立の遅延が解消され、最初のパッケージ遅延(first-package latency)が大幅に低減されます。
実装手順
依存関係の追加。
ビルドツールに応じて、プロジェクトの依存関係設定ファイルに dashscope-sdk-java および commons-pool2 を追加します。
Maven および Gradle の例:
Maven
Maven プロジェクトの
pom.xmlファイルを開きます。<dependencies>タグ内に、以下の依存関係を追加します。
<dependency> <groupId>com.alibaba</groupId> <artifactId>dashscope-sdk-java</artifactId> <!-- 'the-latest-version' をバージョン 2.16.9 以降に置き換えます。利用可能なバージョンは、https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java をご確認ください。--> <version>the-latest-version</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <!-- 'the-latest-version' を最新バージョンに置き換えます。利用可能なバージョンは、https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 をご確認ください。--> <version>the-latest-version</version> </dependency>pom.xmlファイルを保存します。プロジェクトの依存関係を更新するために、
mvn clean installまたはmvn compileなどの Maven コマンドを実行します。
Gradle
Gradle プロジェクトの
build.gradleファイルを開きます。dependenciesブロック内に、以下の依存関係を追加します。dependencies { // 'the-latest-version' をバージョン 2.16.6 以降に置き換えます。利用可能なバージョンは、https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java をご確認ください。 implementation group: 'com.alibaba', name: 'dashscope-sdk-java', version: 'the-latest-version' // 'the-latest-version' を最新バージョンに置き換えます。利用可能なバージョンは、https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 をご確認ください。 implementation group: 'org.apache.commons', name: 'commons-pool2', version: 'the-latest-version' }build.gradleファイルを保存します。ターミナルでプロジェクトのルートディレクトリに移動し、依存関係を更新するための以下の Gradle コマンドを実行します。
./gradlew build --refresh-dependenciesWindows を使用している場合は、以下のコマンドを実行します。
gradlew build --refresh-dependencies
接続プールの構成。
環境変数を使用して、主要な接続プールパラメーターを設定します。
環境変数
説明
DASHSCOPE_CONNECTION_POOL_SIZE
接続プールのサイズ。
推奨値:ピーク同時実行数の 2 倍以上。
デフォルト:32。
DASHSCOPE_MAXIMUM_ASYNC_REQUESTS
非同期リクエストの最大数。
推奨値:
DASHSCOPE_CONNECTION_POOL_SIZEと一致させる。デフォルト:32。
DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST
ホストごとの非同期リクエストの最大数。
推奨値:
DASHSCOPE_CONNECTION_POOL_SIZEと一致させる。デフォルト:32。
オブジェクトプールの構成。
環境変数を使用してオブジェクトプールのサイズを設定します。
環境変数
説明
COSYVOICE_OBJECTPOOL_SIZE
オブジェクトプールのサイズ。
推奨値:ピーク同時実行数の 1.5 倍~2 倍。
デフォルト:500。
重要オブジェクトプールのサイズ(
COSYVOICE_OBJECTPOOL_SIZE)は、接続プールのサイズ(DASHSCOPE_CONNECTION_POOL_SIZE)以下である必要があります。そうでない場合、接続プールが満杯の状態でオブジェクトプールがオブジェクトを要求すると、呼び出しスレッドが利用可能な接続が解放されるまでブロックされます。オブジェクトプールのサイズは、ご利用のアカウントの QPS(クエリ/秒)制限を超えてはいけません。
以下のコードを使用してオブジェクトプールを作成します。
class CosyvoiceObjectPool { // ・・・他のコードは省略。完全な例は以下を参照してください。 public static GenericObjectPool<SpeechSynthesizer> getInstance() { lock.lock(); if (synthesizerPool == null) { // オブジェクトプールのサイズをここで設定できます。または COSYVOICE_OBJECTPOOL_SIZE 環境変数で設定することも可能です。 // 推奨値:サーバーの最大同時接続数の 1.5 倍~2 倍。 int objectPoolSize = getObjectivePoolSize(); SpeechSynthesizerObjectFactory speechSynthesizerObjectFactory = new SpeechSynthesizerObjectFactory(); GenericObjectPoolConfig<SpeechSynthesizer> config = new GenericObjectPoolConfig<>(); config.setMaxTotal(objectPoolSize); config.setMaxIdle(objectPoolSize); config.setMinIdle(objectPoolSize); synthesizerPool = new GenericObjectPool<>(speechSynthesizerObjectFactory, config); } lock.unlock(); return synthesizerPool; } }プールから
SpeechSynthesizerオブジェクトを取得。現在借用中のオブジェクト数がプールの最大サイズを超える場合、システムは新しい
SpeechSynthesizerオブジェクトを作成します。この新しく作成されたオブジェクトは、初期化および WebSocket 接続の確立を行う必要があります。既存のプール内の接続を再利用できないため、プールによる恩恵を受けません。
synthesizer = CosyvoiceObjectPool.getInstance().borrowObject();音声合成の実行。
音声合成を実行するには、
SpeechSynthesizerオブジェクトの call または streamingCall メソッドを呼び出すことができます。SpeechSynthesizerオブジェクトの返却。音声合成タスクが終了したら、後続のタスクで再利用できるよう、
SpeechSynthesizerオブジェクトを返却します。未完了または失敗したタスクからオブジェクトを返却しないでください。
CosyvoiceObjectPool.getInstance().returnObject(synthesizer);
完全なコード例
import com.alibaba.dashscope.audio.tts.SpeechSynthesisResult;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisAudioFormat;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer;
import com.alibaba.dashscope.common.ResultCallback;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.utils.Constants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
/**
* プロジェクトには、org.apache.commons.pool2 および DashScope パッケージを含める必要があります。
*
* DashScope SDK バージョン 2.16.6 以降は、高い同時実行性のシナリオ向けに最適化されています。
* DashScope SDK バージョン 2.16.6 より前のバージョンは、高い同時実行性での使用は推奨されません。
*
*
* TTS サービスに対する高い同時実行性の呼び出しを行う前に、
* 次の環境変数を使用して接続プールのパラメーターを構成してください:
*
* DASHSCOPE_MAXIMUM_ASYNC_REQUESTS
* DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST
* DASHSCOPE_CONNECTION_POOL_SIZE
*
*/
class SpeechSynthesizerObjectFactory
extends BasePooledObjectFactory<SpeechSynthesizer> {
public SpeechSynthesizerObjectFactory() {
super();
}
@Override
public SpeechSynthesizer create() throws Exception {
return new SpeechSynthesizer();
}
@Override
public PooledObject<SpeechSynthesizer> wrap(SpeechSynthesizer obj) {
return new DefaultPooledObject<>(obj);
}
}
class CosyvoiceObjectPool {
public static GenericObjectPool<SpeechSynthesizer> synthesizerPool;
public static String COSYVOICE_OBJECTPOOL_SIZE_ENV = "COSYVOICE_OBJECTPOOL_SIZE";
public static int DEFAULT_OBJECT_POOL_SIZE = 500;
private static Lock lock = new java.util.concurrent.locks.ReentrantLock();
public static int getObjectivePoolSize() {
try {
Integer n = Integer.parseInt(System.getenv(COSYVOICE_OBJECTPOOL_SIZE_ENV));
System.out.println("環境変数からオブジェクトプールサイズを取得: "+ n);
return n;
} catch (NumberFormatException e) {
System.out.println("デフォルトのオブジェクトプールサイズを使用: "+ DEFAULT_OBJECT_POOL_SIZE);
return DEFAULT_OBJECT_POOL_SIZE;
}
}
public static GenericObjectPool<SpeechSynthesizer> getInstance() {
lock.lock();
if (synthesizerPool == null) {
// オブジェクトプールサイズをここで設定できます。または COSYVOICE_OBJECTPOOL_SIZE 環境変数で設定することも可能です。
// 推奨値:サーバーの最大同時接続数の 1.5 倍~2 倍。
int objectPoolSize = getObjectivePoolSize();
SpeechSynthesizerObjectFactory speechSynthesizerObjectFactory =
new SpeechSynthesizerObjectFactory();
GenericObjectPoolConfig<SpeechSynthesizer> config =
new GenericObjectPoolConfig<>();
config.setMaxTotal(objectPoolSize);
config.setMaxIdle(objectPoolSize);
config.setMinIdle(objectPoolSize);
synthesizerPool =
new GenericObjectPool<>(speechSynthesizerObjectFactory, config);
}
lock.unlock();
return synthesizerPool;
}
}
class SynthesizeTaskWithCallback implements Runnable {
String[] textArray;
String requestId;
long timeCost;
public SynthesizeTaskWithCallback(String[] textArray) {
this.textArray = textArray;
}
@Override
public void run() {
SpeechSynthesizer synthesizer = null;
long startTime = System.currentTimeMillis();
// if recv onError
final boolean[] hasError = {false};
try {
class ReactCallback extends ResultCallback<SpeechSynthesisResult> {
ReactCallback() {}
@Override
public void onEvent(SpeechSynthesisResult message) {
if (message.getAudioFrame() != null) {
try {
byte[] bytesArray = message.getAudioFrame().array();
System.out.println("受信した音声。音声ストリーム長: " + bytesArray.length);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@Override
public void onComplete() {}
@Override
public void onError(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
hasError[0] = true;
}
}
// your-dashscope-api-key をご自身の API キーに置き換えます
String dashScopeApiKey = "your-dashscope-api-key";
SpeechSynthesisParam param =
SpeechSynthesisParam.builder()
.model("cosyvoice-v3-flash")
.voice("longanyang")
// API キーはシンガポールリージョンと北京リージョンで異なります。API キーの取得方法:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// 環境変数を設定していない場合は、以下の行を .apiKey("sk-xxx") に置き換えてください。
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.format(SpeechSynthesisAudioFormat
.MP3_22050HZ_MONO_256KBPS) // ストリーミング合成には PCM または MP3 を使用します
.apiKey(dashScopeApiKey)
.build();
try {
synthesizer = CosyvoiceObjectPool.getInstance().borrowObject();
synthesizer.updateParamAndCallback(param, new ReactCallback());
for (String text : textArray) {
synthesizer.streamingCall(text);
}
Thread.sleep(20);
synthesizer.streamingComplete(60000);
requestId = synthesizer.getLastRequestId();
} catch (Exception e) {
System.out.println("例外 e: " + e.toString());
hasError[0] = true;
}
} catch (Exception e) {
hasError[0] = true;
throw new RuntimeException(e);
}
if (synthesizer != null) {
try {
if (hasError[0] == true) {
// エラーが発生した場合、接続を閉じ、プール内のオブジェクトを無効化します。
synthesizer.getDuplexApi().close(1000, "bye");
CosyvoiceObjectPool.getInstance().invalidateObject(synthesizer);
} else {
// タスクが正常に完了した場合、オブジェクトを返却します。
CosyvoiceObjectPool.getInstance().returnObject(synthesizer);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
long endTime = System.currentTimeMillis();
timeCost = endTime - startTime;
System.out.println("[スレッド " + Thread.currentThread() + "] 音声合成タスクが完了しました。所要時間: " + timeCost + " ms、RequestId: " + requestId);
}
}
}
@Slf4j
public class SynthesizeTextToSpeechWithCallbackConcurrently {
public static void checkoutEnv(String envName, int defaultSize) {
if (System.getenv(envName) != null) {
System.out.println("[ENV CHECK]: " + envName + " "
+ System.getenv(envName));
} else {
System.out.println("[ENV CHECK]: " + envName
+ " デフォルト値(" + defaultSize + ")を使用します");
}
}
public static void main(String[] args)
throws InterruptedException, NoApiKeyException {
// 以下の URL はシンガポールリージョン向けです。北京リージョンのモデルを使用する場合は、代わりに wss://dashscope.aliyuncs.com/api-ws/v1/inference を使用してください。
Constants.baseWebsocketApiUrl = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference";
// 接続プールの環境変数を確認
checkoutEnv("DASHSCOPE_CONNECTION_POOL_SIZE", 32);
checkoutEnv("DASHSCOPE_MAXIMUM_ASYNC_REQUESTS", 32);
checkoutEnv("DASHSCOPE_MAXIMUM_ASYNC_REQUESTS_PER_HOST", 32);
checkoutEnv(CosyvoiceObjectPool.COSYVOICE_OBJECTPOOL_SIZE_ENV, CosyvoiceObjectPool.DEFAULT_OBJECT_POOL_SIZE);
int runTimes = 3;
// SpeechSynthesis オブジェクトのプールを作成
ExecutorService executorService = Executors.newFixedThreadPool(runTimes);
for (int i = 0; i < runTimes; i++) {
// タスク送信時刻を記録
LocalDateTime submissionTime = LocalDateTime.now();
executorService.submit(new SynthesizeTaskWithCallback(new String[] {
"私の床の前で、月光が輝いている。", "まるで地上に霜が降りているようだ。", "私は目を上げて明るい月を眺める。", "それから頭を下げ、故郷を思い出す。"}));
}
// ExecutorService をシャットダウンし、すべてのタスクが完了するのを待ちます
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.MINUTES);
System.exit(0);
}
}推奨構成
以下の構成は、指定された仕様の Alibaba Cloud ECS インスタンス上で CosyVoice 音声合成サービスのみを実行した際のテスト結果に基づいています。過剰な同時リクエストはタスク遅延を引き起こす可能性があります。
単一マシン同時実行数とは、1 台のマシン上で同時に実行される CosyVoice 音声合成タスクの数を指します。これはワーカースレッドの数と等価です。
マシン構成(Alibaba Cloud) | 最大単一マシン同時実行数 | オブジェクトプールサイズ | 接続プールサイズ |
4 vCPU、8 GiB メモリ | 100 | 500 | 2000 |
8 vCPU、16 GiB メモリ | 150 | 500 | 2000 |
16 vCPU、32 GiB メモリ | 200 | 500 | 2000 |
リソース管理およびエラー処理
タスク成功時:音声合成タスクが正常に完了した場合、
SpeechSynthesizerオブジェクトのGenericObjectPoolのreturnObjectメソッドを呼び出して、プールへ返却し、再利用可能にします。現在のコードでは、これに対応する呼び出しは
CosyvoiceObjectPool.getInstance().returnObject(synthesizer)です。重要未完了または失敗したタスクから
SpeechSynthesizerオブジェクトを返却しないでください。タスク失敗時:SDK またはビジネスロジックからの例外によってタスクが中断された場合、以下の両方の操作を実行してください。
基盤となる WebSocket 接続を手動で閉じます。
オブジェクトプール内のオブジェクトを無効化し、再利用を防止します。
// 現在のコードでは、以下の対応となります: // 接続を閉じる synthesizer.getDuplexApi().close(1000, "bye"); // プール内の不具合のある合成器を無効化する CosyvoiceObjectPool.getInstance().invalidateObject(synthesizer);サービスが TaskFailed エラーを返した場合、追加のアクションは不要です。
ウォームアップおよびタイミングメトリック
DashScope Java SDK に対する同時呼び出しのレイテンシーなどのパフォーマンスメトリックを評価する際は、正式なテストの前にシステムをウォームアップしてください。ウォームアップにより、測定結果が安定状態におけるサービスの実際のパフォーマンスを正確に反映し、初期接続確立時間によるデータスキューを回避できます。
接続再利用メカニズム
DashScope Java SDK は、WebSocket 接続の管理および再利用を効率的に行うためのグローバルシングルトン接続プールを使用します。これにより、頻繁な接続確立および切断のオーバーヘッドが削減され、高い同時実行性のシナリオにおけるスループットが向上します。
このメカニズムの主な動作は以下のとおりです。
オンデマンド作成:SDK は起動時に WebSocket 接続を事前に作成しません。最初の呼び出し時にのみ接続を作成します。
期間限定再利用:リクエストが完了した後、接続は最大 60 秒間プール内で再利用可能として保持されます。
60 秒以内に新しいリクエストが到着した場合、既存の接続が再利用されます。これによりハンドシェイクのオーバーヘッドが回避されます。
接続が 60 秒以上アイドル状態になると、リソース解放のために自動的に閉じられます。
ウォームアップの重要性
以下のケースでは、接続プールに再利用可能なアクティブな接続が含まれていない可能性があります。そのため、リクエストは新しい接続を作成する必要があります。
アプリケーションが起動直後であり、まだ呼び出しを行っていない場合。
サービスが 60 秒以上アイドル状態であったため、プール内のすべての接続がタイムアウトして閉じられた場合。
このような場合、最初または初期のリクエストは、TCP ハンドシェイク、TLS ネゴシエーション、プロトコルアップグレードを含む完全な WebSocket 接続確立プロセスをトリガーします。これらのリクエストのエンドツーエンドレイテンシーは、接続を再利用する後続のリクエストよりも大幅に高くなります。この余分な時間は、ネットワーク接続の初期化に由来するものであり、サービス処理によるものではありません。ウォームアップを行わないと、初期接続時間が含まれるため、パフォーマンステストの結果が歪んでしまいます。
ベストプラクティス
信頼性の高いパフォーマンスデータを収集するため、正式なストレステストまたはレイテンシー測定の前に、以下のウォームアップ手順を実行してください。
正式なテストと同じ同時実行レベルをシミュレートします。接続プールを十分に埋めるために、十分な数のウォームアップ呼び出し(例:1~2 分間)を送信します。
正式なパフォーマンスデータ収集を開始する前に、接続プールが十分な数のアクティブな接続を確立および維持していることを確認します。
適切なウォームアップにより、SDK の接続プールは安定した再利用状態に達します。これにより、より代表的なレイテンシーメトリックが得られ、実際のオンライン運用時のパフォーマンスを反映できます。