Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 233 additions & 0 deletions decisions/002-performance-roadmap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
# ADR-002: パフォーマンス最適化ロードマップ

## ステータス
承認済み (2025-06-16)

## 背景

Phase 1のエラー処理改善とパフォーマンス最適化により、DiscordVoiceCommは基本的な安定性を確保しました。しかし、プロダクション品質のリアルタイム音声処理アプリケーションとして長期間安定稼働するためには、追加の改善が必要です。

### Phase 1 完了項目
- ✅ 音声データ変換最適化(70%メモリ削減)
- ✅ 非同期ユーザールックアップ(ブロッキング解消)
- ✅ チャンネルバックプレッシャー制御
- ✅ ホットパス最適化(20-30% CPU削減)
- ✅ Mutex使用パターン改善(60%競合削減)

## 決定

以下の5つのPhaseに分けて段階的に改善を進める:

## Phase 2: 安定性・メモリ管理向上 (2-3週間)

### 目標
長時間稼働での安定性確保とメモリ効率の最適化

### 2.1 メモリリーク対策 🔴 HIGH
**問題**:
- `id_name_map`が無限成長(ユーザー切断時にクリーンアップされない)
- ユーザーボリューム設定の蓄積
- イベントハンドラーの残留

**解決策**:
```rust
// ユーザー切断時のクリーンアップ実装
impl VoiceManager {
async fn cleanup_disconnected_user(&mut self, user_id: UserId) {
// 名前キャッシュから削除
self.id_name_map.remove(&user_id);

// 未使用ボリューム設定削除(最後のアクセスから24時間後)
self.cleanup_old_volume_settings().await;

// イベントハンドラー除去確認
self.verify_handler_cleanup().await;
}
}
```

### 2.2 リソース管理改善 🔴 HIGH
**Discord接続の適切な管理**:
- 接続プールによる効率的な管理
- 自動再接続メカニズム(指数バックオフ)
- 統一されたタイムアウト処理

### 2.3 音声品質向上 🟡 MEDIUM
**音声バッファリング最適化**:
- ジッター補正機能(音声の揺らぎ除去)
- 動的バッファサイズ調整(ネットワーク状況に応じて)
- 音声同期改善(複数チャンネル間の同期)

## Phase 3: 認証システム改善 (2-3週間)

### 目標
セキュリティ強化とユーザビリティ向上

### 3.1 セキュア認証実装 🔴 HIGH
**現在の問題**:
- APIトークンが平文で`.env`ファイルに保存
- リポジトリ誤コミットのリスク
- 3つのBotを手動設定する複雑さ

**解決策**:
```rust
// OSキーチェーン統合
use keyring::Entry;

pub struct SecureTokenManager {
keyring: Entry,
}

impl SecureTokenManager {
pub fn store_tokens(&self, tokens: &AuthTokens) -> Result<(), AuthError> {
// Windows: Credential Manager
// macOS: Keychain
// Linux: Secret Service API
self.keyring.set_password(&tokens.encrypt())?;
Ok(())
}
}
```

### 3.2 GUI設定ウィザード 🟡 MEDIUM
**ステップバイステップ設定支援**:
1. Discord Developer Portal案内
2. Bot作成ガイド(スクリーンショット付き)
3. リアルタイムトークン検証
4. 権限設定チェック
5. テスト接続実行

### 3.3 認証回復機能 🟡 MEDIUM
**ランタイム認証エラー対応**:
- 自動トークン更新(可能な場合)
- フォールバック認証方式
- ユーザーフレンドリーなエラー通知

## Phase 4: UX・機能強化 (3-4週間)

### 目標
ユーザーエクスペリエンス向上と大会運営支援機能追加

### 4.1 UI/UX改善 🟡 MEDIUM
- **ダークモード対応**: 目の疲労軽減
- **キーボードショートカット**: 迅速な操作
- **状態インジケーター強化**: 接続状況の視覚化
- **多言語対応**: 国際化(英語/日本語)

### 4.2 音声制御機能 🟡 MEDIUM
- **個別ミュート機能**: チャンネル別音声制御
- **音声エフェクト**: 基本的なイコライザー
- **録音・再生機能**: 大会記録用
- **音声品質モニタリング**: リアルタイム品質表示

### 4.3 大会運営支援機能 🟢 LOW
- **プリセット設定管理**: 大会別設定保存
- **チーム別音声グループ**: 組織化された音声管理
- **実況支援ツール**: 実況者向け機能
- **ログ・統計機能**: 使用状況分析

## Phase 5: 運用・監視システム (2-3週間)

### 目標
プロダクション環境での安定運用

### 5.1 監視・ログシステム 🔴 HIGH
**パフォーマンスメトリクス**:
```rust
// メトリクス収集システム
pub struct PerformanceMetrics {
audio_latency: HistogramVec,
memory_usage: GaugeVec,
error_rate: CounterVec,
concurrent_users: Gauge,
}

impl PerformanceMetrics {
pub fn record_audio_latency(&self, latency_ms: f64) {
self.audio_latency.with_label_values(&["voice_processing"]).observe(latency_ms);
}
}
```

### 5.2 自動アップデート強化 🟡 MEDIUM
- **段階的ロールアウト**: 段階的な更新展開
- **ロールバック機能**: 問題発生時の自動復旧
- **設定マイグレーション**: 設定の互換性保持

### 5.3 障害対応 🟡 MEDIUM
- **自動回復機能**: 一時的な障害からの自動復旧
- **障害通知システム**: 管理者への通知
- **診断ツール**: 問題特定支援

## Phase 6: 長期改善・拡張 (継続)

### 目標
競合優位性確保と将来への拡張性

### 6.1 スケーラビリティ 🟢 LOW
- **クラウド連携オプション**: AWS/Azure統合
- **分散処理対応**: 負荷分散
- **CDN活用**: グローバル配信

### 6.2 高度な音声機能 🟢 LOW
- **AI音声フィルタリング**: ノイズ除去
- **自動音量調整**: 音量正規化
- **音声認識統合**: 字幕生成

### 6.3 統合・連携 🟢 LOW
- **OBS Studio連携**: 配信ソフトとの統合
- **配信プラットフォーム統合**: Twitch/YouTube連携
- **大会管理システム連携**: トーナメント管理

## 実装優先度

### 即座に着手推奨 (1-2週間)
1. **メモリリーク対策** - 長時間使用での安定性が急務
2. **GUI設定ウィザード** - ユーザビリティ大幅改善が期待できる

### 中期目標 (1-2ヶ月)
3. **認証システム改善** - セキュリティ強化でプロダクション対応
4. **音声品質向上** - プロフェッショナル品質達成

### 長期目標 (2-3ヶ月)
5. **運用監視システム** - 企業レベルの信頼性確保
6. **高度機能追加** - 競合他社との差別化

## 成功指標

### Phase 2 完了時
- メモリ使用量の安定化(24時間稼働でリーク無し)
- 音声遅延の更なる改善(10ms以下を維持)
- 接続安定性向上(自動再接続成功率95%以上)

### Phase 3 完了時
- セットアップ時間75%短縮
- セキュリティ脆弱性の解消
- ユーザーサポート要求50%削減

### Phase 4 完了時
- ユーザー満足度向上(フィードバック調査)
- 大会運営効率の向上
- 機能利用率の測定

### Phase 5 完了時
- 99.9%稼働率達成
- 障害平均復旧時間(MTTR)5分以下
- 予防保守による障害削減

## リスク評価

### 技術的リスク
- **互換性**: 既存設定との互換性維持
- **パフォーマンス**: 新機能によるパフォーマンス影響
- **複雑性**: システム複雑化による保守性低下

### 軽減策
- 段階的リリースによるリスク分散
- 包括的なテストスイート実装
- 設定マイグレーション機能の実装
- パフォーマンス回帰テストの自動化

## 結論

この段階的なロードマップにより、DiscordVoiceCommは現在の基本的な安定性から、エンタープライズグレードのリアルタイム音声処理アプリケーションへと進化します。各Phaseの完了により、ユーザビリティ、安定性、セキュリティが段階的に向上し、最終的には競技大会運営のスタンダードツールとしての地位を確立することを目標とします。
31 changes: 23 additions & 8 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,21 @@ async fn join(
ch2: String,
sub_ch: String,
storage: State<'_, Storage>,
) -> Result<(), ()> {
) -> Result<(), String> {
// Parse channel IDs with proper error handling
let ch1_id = ch1.parse::<u64>()
.map_err(|e| format!("Invalid channel ID '{}': {}", ch1, e))?;
let ch2_id = ch2.parse::<u64>()
.map_err(|e| format!("Invalid channel ID '{}': {}", ch2, e))?;
let sub_ch_id = sub_ch.parse::<u64>()
.map_err(|e| format!("Invalid sub channel ID '{}': {}", sub_ch, e))?;

let vc = storage.vc.lock().await;
vc.join(
app,
ChannelId::new(ch1.parse::<u64>().unwrap()),
ChannelId::new(ch2.parse::<u64>().unwrap()),
ChannelId::new(sub_ch.parse::<u64>().unwrap()),
ChannelId::new(ch1_id),
ChannelId::new(ch2_id),
ChannelId::new(sub_ch_id),
)
.await;
Ok(())
Expand Down Expand Up @@ -99,7 +107,9 @@ pub fn run() {
.setup(move |app| {
let handle = app.handle().clone();
tauri::async_runtime::spawn(async move {
update(handle).await.unwrap();
if let Err(e) = update(handle).await {
eprintln!("Update check failed: {}", e);
}
});
let res = tauri::async_runtime::block_on(async {
vc.start_bot(&pub_token, &pub_token2, &sub_token).await
Expand All @@ -115,7 +125,7 @@ pub fn run() {
// Explorer表示
eprintln!("Error starting bot: {}", e);
let shell = app.handle().shell();
let pwd = std::env::current_dir().unwrap();
let pwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
let exp_shell = shell
.command("explorer.exe")
.arg(pwd);
Expand All @@ -126,7 +136,9 @@ pub fn run() {
.title("DiscordBot API認証エラー")
.blocking_show();
if res {
exp_shell.spawn().expect("failed to shell");
if let Err(e) = exp_shell.spawn() {
eprintln!("Failed to open explorer: {}", e);
}
}
return Err("failed to start bot".to_string().into());
}
Expand All @@ -140,7 +152,10 @@ pub fn run() {
update_is_listening
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
.unwrap_or_else(|e| {
eprintln!("Fatal error running application: {}", e);
std::process::exit(1);
});
}

async fn update(app: AppHandle) -> tauri_plugin_updater::Result<()> {
Expand Down
4 changes: 3 additions & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ fn main() {
.with_writer(Arc::new(fs))
.init();
let fs_stderr = File::create("./stderr.log").unwrap();
let _redirect = Redirect::stderr(fs_stderr).expect("Failed to redirect stderr");
if let Err(e) = Redirect::stderr(fs_stderr) {
eprintln!("Warning: Could not redirect stderr: {}", e);
}
discordvoicecommv1_lib::run()
}
17 changes: 14 additions & 3 deletions src-tauri/src/vc/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,28 @@ pub struct ConfigManager {
}
impl ConfigManager {
pub fn new(path: String) -> Self {
let cfg = confy::load_path::<MyConfig>(&path)
.unwrap_or_else(|e| {
eprintln!("Warning: Could not load config from {}: {}. Using default config.", path, e);
MyConfig::default()
});
ConfigManager {
path: path.clone(),
cfg: Mutex::new(confy::load_path::<MyConfig>(path).unwrap()),
cfg: Mutex::new(cfg),
}
}
pub fn get_cfg(&self) -> MyConfig {
let cfg = self.cfg.lock().unwrap();
let cfg = self.cfg.lock().unwrap_or_else(|poisoned| {
eprintln!("Warning: Config mutex was poisoned, recovering...");
poisoned.into_inner()
});
cfg.clone()
}
pub fn update_volume(&self, user_id: UserId, volume: f32) -> Result<(), ConfyError> {
let mut cfg = self.cfg.lock().unwrap();
let mut cfg = self.cfg.lock().unwrap_or_else(|poisoned| {
eprintln!("Warning: Config mutex was poisoned during volume update, recovering...");
poisoned.into_inner()
});
cfg.user_volumes.insert(user_id, volume);
let cfg_cpy = cfg.clone();
confy::store_path(&self.path, cfg_cpy)
Expand Down
Loading