フェデレーションインスタンスキャッシュ無効化機能#
概要#
Misskey のフェデレーションシステムは、フェデレーテッドインスタンスのメタデータを管理するための高度なキャッシュ無効化機能を実装しています。この機能には、管理者による明示的なキャッシュリセット操作、インスタンスホスト名の正規化の改善、冗長な更新を回避するための早期無効化メカニズムが含まれています。
キャッシュアーキテクチャ#
2 層キャッシング戦略#
フェデレーションインスタンスキャッシュは2 層キャッシングアーキテクチャを使用します:
- Redis キャッシュ(プライマリ): 30 分のライフタイム
- インメモリキャッシュ(セカンダリ): 3 分のライフタイム
キャッシュエントリは、Redisに kvcache:federatedInstance:{host} というキーパターンで保存されます。
キャッシュ実装#
キャッシュは、FederatedInstanceService内でRedisKVCacheクラスを使用して実装されており、3 分ごとに自動的にメモリキャッシュのガベージコレクションが実行されます。
管理者による明示的なキャッシュ無効化#
管理 API エンドポイント#
管理者はadmin/federation/update-instance API エンドポイントを介して、インスタンスキャッシュを明示的にリセットできます。
エンドポイント要件:
- Permission: モデレーターロールが必要
- API Kind:
write:admin:federation - Parameters:
host(必須)、isSuspended(オプション)、moderationNote(オプション)
キャッシュのみの無効化#
hostパラメータのみで呼び出された場合(更新なし)、エンドポイントは明示的にキャッシュを無効化します:
if (ps.isSuspended == null && ps.moderationNote == null) {
await this.federatedInstanceService.invalidate(normalizedHost);
return;
}
無効化の実装#
invalidate()メソッドは、ホスト名を正規化し、Redis とメモリキャッシュの両方からエントリを削除します:
public async invalidate(host: string): Promise<void> {
const normalizedHost = this.utilityService.normalizeHost(host);
await this.federatedInstanceCache.delete(normalizedHost);
}
ホスト名正規化の改善#
正規化メソッド#
システムはUtilityService.normalizeHost()を使用して、ホスト名を標準的な ASCII 形式に変換します:
public normalizeHost(host: string): string {
return punycode.toASCII(host.toLowerCase());
}
これにより以下が保証されます:
- 国際化ドメイン名(IDN)が punycode を使用して ASCII に変換される
- ホスト名が小文字化され、大文字小文字を区別しないマッチングが可能になる
- 一貫したキャッシュキーとデータベースルックアップが実現される
更新時の正規化#
FederatedInstanceService.fetchOrRegister()メソッドは、データベース操作の前にホスト名を正規化します:
public async fetchOrRegister(host: string): Promise<MiInstance> {
host = this.utilityService.normalizeHost(host);
const cached = await this.federatedInstanceCache.get(host);
if (cached) return cached;
const index = await this.instancesRepository.findOneBy({ host });
// ... インスタンスの作成または更新
}
利点:
- すべてのデータベースルックアップが正規化された形式を使用する
- 新しいインスタンスレコードが標準的なホスト名で保存される
- キャッシュキーが操作全体で一貫性を保つ
- host カラムのユニークインデックスにより、異なる形式でも同等のホスト名による重複インスタンスが防止される
データベース変更時の自動キャッシュ更新#
update()メソッドは、データベース更新後に自動的にキャッシュをリフレッシュします:
public async update(id: MiInstance['id'], data: Partial<MiInstance>): Promise<void> {
const result = await this.instancesRepository.createQueryBuilder().update()
.set(data)
.where('id = :id', { id })
.returning('*')
.execute()
.then((response) => response.raw[0]);
this.federatedInstanceCache.set(result.host, result);
}
冗長な更新を回避するための早期無効化メカニズム#
1. キャッシュヒット時の早期リターン#
fetch()メソッドは、キャッシュされたデータがある場合は即座に返し、不要なデータベースクエリを防ぎます:
public async fetch(host: string): Promise<MiInstance | null> {
host = this.utilityService.normalizeHost(host);
const cached = await this.federatedInstanceCache.get(host);
if (cached !== undefined) return cached;
// キャッシュにない場合のみデータベースをクエリ
const index = await this.instancesRepository.findOneBy({ host });
// ...
}
2. 更新前の値比較#
管理用更新エンドポイントは、更新を実行する前に値を比較します:
const isSuspendedBefore = instance.suspensionState !== 'none';
const suspensionState = (ps.isSuspended != null && isSuspendedBefore !== ps.isSuspended)
? (ps.isSuspended ? 'manuallySuspended' : 'none')
: undefined;
// 値が実際に変更された場合のみログを記録
if (ps.isSuspended != null && isSuspendedBefore !== ps.isSuspended) {
// ... モデレーションログエントリを作成
}
これにより、値が実際に変更された場合のみモデレーションログが作成され、冗長な操作が削減されます。
3. メタデータ取得の時間ベーススロットリング#
FetchInstanceMetadataServiceは、過去 24 時間以内に更新された場合はメタデータの取得を防ぎます:
if (!force) {
const _instance = await this.federatedInstanceService.fetchOrRegister(host);
const now = Date.now();
if (_instance && _instance.infoUpdatedAt && (now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24)) {
return; // 冗長な取得をスキップ
}
}
4. 更新をバッチ処理する Collapsed Queue#
InboxProcessorServiceは、CollapsedQueueを使用して、5 分間のウィンドウ内の複数の更新リクエストをマージします:
public collapseUpdateInstanceJobs(oldJob: UpdateInstanceJob, newJob: UpdateInstanceJob) {
const latestRequestReceivedAt = oldJob.latestRequestReceivedAt < newJob.latestRequestReceivedAt
? newJob.latestRequestReceivedAt
: oldJob.latestRequestReceivedAt;
const shouldUnsuspend = oldJob.shouldUnsuspend || newJob.shouldUnsuspend;
return {
latestRequestReceivedAt,
shouldUnsuspend,
};
}
これにより、複数の更新を単一の操作にバッチ処理することで、冗長なデータベース書き込みが大幅に削減されます。
5. 状態ベースの条件付き更新#
DeliverProcessorServiceは、状態が実際に変更される場合のみ更新します:
if (i.isNotResponding) {
this.federatedInstanceService.update(i.id, {
isNotResponding: false,
notRespondingSince: null,
});
}
これにより、インスタンスが既に正しい状態にある場合の不要なデータベース書き込みが防止されます。
フェデレーション活動中の自動キャッシュ更新#
キャッシュは、さまざまなフェデレーション活動中に自動的に更新されます(単なる無効化ではありません):
配信処理#
DeliverProcessorServiceは、配信結果に基づいてインスタンスステータスを更新します:
isNotRespondingとnotRespondingSinceフィールドを更新- 1 週間以上応答がないインスタンスを自動サスペンド
- HTTP 410(Gone)ステータスを返すインスタンスを自動サスペンド
受信処理#
InboxProcessorServiceは、インスタンスのアクティビティを更新します:
latestRequestReceivedAtタイムスタンプを更新autoSuspendedForNotResponding状態だったインスタンスを自動的にサスペンド解除isNotRespondingを false に設定
キャッシュライフサイクル管理#
サービスには適切なクリーンアップメカニズムが含まれています:
dispose()- キャッシュリソースを手動でクリーンアップonApplicationShutdown()- アプリケーションシャットダウン時に自動クリーンアップ- メモリキャッシュには、期限切れエントリを削除するための3 分ごとのガベージコレクションが実装されています
キャッシュ無効化フローの図#
まとめ#
フェデレーションインスタンスキャッシュ無効化機能は、以下を提供します:
- 明示的な管理制御 - API エンドポイントを介したキャッシュリセット
- 改善されたホスト名正規化 - 一貫したキャッシュキーを保証し、重複レコードを防止
- 複数の早期無効化メカニズム - キャッシュヒット、値比較、時間ベーススロットリング、Collapsed Queue、条件付き更新を含む
- 自動キャッシュリフレッシュ - フェデレーション活動中にデータを最新に保つ
- 適切なライフサイクル管理 - 自動クリーンアップとガベージコレクション
これらのメカニズムにより、システムは高いパフォーマンスを維持しながら、データの一貫性と正確性を確保します。