Claude Code Plugins

Community-maintained marketplace

Feedback
0
0

Obsidian LiveSync の CouchDbClient の構造と使用方法を説明します。CouchDbRepository トレイトの実装方法、HTTP プロキシパターン(forward_request)、longpoll リクエストの処理、メトリクス収集、ヘルスチェックの実装を理解・拡張する際に使用します。CouchDB 関連の機能追加、トラブルシューティング、パフォーマンス改善を依頼されたときに使用してください。

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name couchdb-client
description Obsidian LiveSync の CouchDbClient の構造と使用方法を説明します。CouchDbRepository トレイトの実装方法、HTTP プロキシパターン(forward_request)、longpoll リクエストの処理、メトリクス収集、ヘルスチェックの実装を理解・拡張する際に使用します。CouchDB 関連の機能追加、トラブルシューティング、パフォーマンス改善を依頼されたときに使用してください。

CouchDB Client for Obsidian LiveSync

Overview

このスキルは、Obsidian LiveSync の CouchDbClient の構造と使用方法を説明します。CouchDbClient は CouchDB との通信を担当し、HTTP プロキシとして Obsidian LiveSync プラグインからのリクエストを中継します。

アーキテクチャ上の位置

domain/services.rs     → CouchDbRepository トレイト(抽象)
infrastructure/couchdb.rs → CouchDbClient(実装)

Instructions

1. CouchDbClient の構造

pub struct CouchDbClient {
    client: Client,        // reqwest HTTP クライアント
    base_url: String,      // CouchDB ベース URL
    username: String,      // 認証ユーザー名
    password: String,      // 認証パスワード
}

初期化:

let client = CouchDbClient::new(
    "http://couchdb:5984/",
    "admin",
    "password",
);

2. CouchDbRepository トレイトの実装

トレイト定義domain/services.rs):

#[async_trait]
pub trait CouchDbRepository {
    // ドキュメント操作
    async fn get_document(&self, db_name: &str, doc_id: &str)
        -> Result<CouchDbDocument, DomainError>;
    async fn save_document(&self, db_name: &str, doc: CouchDbDocument)
        -> Result<CouchDbDocument, DomainError>;
    async fn delete_document(&self, db_name: &str, doc_id: &str, rev: &str)
        -> Result<(), DomainError>;

    // ビュークエリ
    async fn query_view(&self, db_name: &str, design_doc: &str,
        view_name: &str, options: Value) -> Result<Vec<CouchDbDocument>, DomainError>;

    // データベース管理
    async fn ensure_database(&self, db_name: &str) -> Result<(), DomainError>;

    // レプリケーション
    async fn replicate(&self, source: &str, target: &str, options: Value)
        -> Result<Value, DomainError>;

    // HTTP プロキシ
    async fn forward_request(&self, method: &str, path: &str,
        query: Option<String>, headers: HeaderMap, body: Bytes)
        -> Result<Response<Body>, DomainError>;

    // アクセサ
    fn get_base_url(&self) -> String;
    fn get_auth_credentials(&self) -> Option<(String, String)>;
}

3. HTTP プロキシパターン

forward_request の処理フロー:

Obsidian Plugin → /db/* → livesync-proxy → forward_request → CouchDB

リクエストタイプ別の処理:

エンドポイント 特徴 タイムアウト
/_changes?feed=longpoll 長時間接続 120秒
/_changes 通常の変更取得 90秒
/_bulk_docs 大量データ 60秒
その他 通常リクエスト 60秒

longpoll リクエストの検出と処理:

let is_longpoll = path.contains("/_changes")
    && query.as_ref().is_some_and(|q| q.contains("feed=longpoll"));

let client = if is_longpoll {
    Client::builder()
        .timeout(Duration::from_secs(120))
        .tcp_keepalive(Some(Duration::from_secs(30)))
        .tcp_nodelay(true)
        .build()?
} else {
    self.client.clone()
};

4. エラーハンドリング

エラータイプ別の処理:

match e {
    // longpoll の中断(正常)
    err if is_longpoll && err.to_string().contains("aborted") => {
        // 204 No Content を返す
    }
    // タイムアウト
    err if err.is_timeout() => {
        // 504 Gateway Timeout を返す
    }
    // 接続エラー
    err if err.is_connect() => {
        // 502 Bad Gateway を返す
    }
}

5. メトリクス収集

MetricsState の使用interfaces/web/metrics.rs):

// リクエスト記録
state.metrics_state.record_request(&path, method, status_code).await;

// 処理時間記録
state.metrics_state.record_request_duration(&path, method, start);

収集されるメトリクス:

  • http_requests_total - 総リクエスト数
  • http_request_duration_seconds - レスポンス時間(ヒストグラム)
  • longpoll/bulk_docs リクエストの個別カウント

6. ヘルスチェック

HealthState の実装interfaces/web/health.rs):

pub struct HealthState {
    pub couchdb_status: RwLock<CouchDbStatus>,
    consecutive_failures: AtomicU32,  // バックオフ用
    // ...
}

バックオフ戦略:

  • 成功時: 通常間隔(30秒)に戻る
  • 失敗時: 2^n 秒(最大5分)まで間隔を延長
let backoff_secs = std::cmp::min(
    2u64.pow(failures),
    self.max_check_interval.as_secs(),
);

Examples

新しい CouchDB 操作の追加

// 1. トレイトにメソッド追加(domain/services.rs)
#[async_trait]
pub trait CouchDbRepository {
    // 既存メソッド...

    async fn compact_database(&self, db_name: &str) -> Result<(), DomainError>;
}

// 2. CouchDbClient に実装追加(infrastructure/couchdb.rs)
async fn compact_database(&self, db_name: &str) -> Result<(), DomainError> {
    let url = format!("{}{}/_compact", self.base_url, db_name);

    let response = self.client
        .post(&url)
        .basic_auth(&self.username, Some(&self.password))
        .header("Content-Type", "application/json")
        .send()
        .await
        .map_err(|e| DomainError::CouchDbError(e.to_string()))?;

    if !response.status().is_success() {
        return Err(DomainError::CouchDbError("Compact failed".into()));
    }
    Ok(())
}

テスト用モックの作成

use mockall::mock;

mock! {
    pub CouchDbMock {}

    #[async_trait]
    impl CouchDbRepository for CouchDbMock {
        async fn get_document(&self, db_name: &str, doc_id: &str)
            -> Result<CouchDbDocument, DomainError>;
        // 他のメソッド...
    }
}

#[tokio::test]
async fn test_with_mock() {
    let mut mock = MockCouchDbMock::new();
    mock.expect_get_document()
        .returning(|_, _| Ok(CouchDbDocument { ... }));
}

インメモリ実装(テスト用)

struct InMemoryCouchDb {
    databases: Mutex<HashMap<String, HashMap<String, CouchDbDocument>>>,
}

#[async_trait]
impl CouchDbRepository for InMemoryCouchDb {
    async fn get_document(&self, db_name: &str, doc_id: &str)
        -> Result<CouchDbDocument, DomainError> {
        let dbs = self.databases.lock().unwrap();
        dbs.get(db_name)
            .and_then(|db| db.get(doc_id))
            .cloned()
            .ok_or(DomainError::CouchDbError("Not found".into()))
    }
}

CouchDB Best Practices

1. ドキュメント ID 最適化

影響: 16バイト ID → 4バイト ID で、1000万ドキュメントが 21GB → 4GB に削減された事例あり。

推奨:

  • Base64url エンコーディングで効率的な ID 生成
  • 順序的/ソート済み ID はランダム ID より挿入が高速
  • ID にスラッシュ / は避け、コロン : を使用(プロキシ互換性)
// 推奨: 順序的 ID(タイムスタンプベース)
let doc_id = format!("{}:{}", timestamp_ms, uuid_short);

// 階層的 ID(親子関係)
let child_id = format!("{}:child:{}", parent_id, child_uuid);

2. ドキュメント設計

多数の小さなドキュメント > 少数の大きなドキュメント

// 推奨: 頻繁に変更されるデータを分離
// メインドキュメント
{ "_id": "note:abc", "title": "...", "created_at": "..." }
// メタデータドキュメント(頻繁に更新)
{ "_id": "note:abc:meta", "view_count": 100, "updated_at": "..." }

メタデータフィールド(推奨):

{
  "_id": "...",
  "type": "note",
  "created_at": "2025-01-15T10:30:00.000Z",
  "updated_at": "2025-01-15T10:30:00.000Z",
  "created_by": "user_id",
  "version": 1
}

日付は ISO 8601 形式(.toISOString())を使用。

3. ビュー最適化

ネイティブ関数で高速化(JavaScript → ネイティブで 60秒 → 4秒):

// 遅い: JavaScript reduce
{ "reduce": "function(keys, values) { return sum(values); }" }

// 速い: ネイティブ reduce
{ "reduce": "_sum" }
{ "reduce": "_count" }
{ "reduce": "_stats" }

4. 競合解決

CouchDB は競合を「first-class citizen」として扱う。

// 競合検出
async fn check_conflicts(&self, db: &str, doc_id: &str) -> Result<Vec<String>, DomainError> {
    let url = format!("{}{}/{}?conflicts=true", self.base_url, db, doc_id);
    let response = self.client.get(&url)
        .basic_auth(&self.username, Some(&self.password))
        .send().await?;
    // _conflicts フィールドをパース
}

解決戦略:

  • 最新のタイムスタンプを勝者とする
  • マージ可能なフィールドはマージ
  • 解決できない場合はユーザーに通知

5. レプリケーション設定

設定 推奨値 説明
batch_size 100-500 小さいほどチェックポイント頻度↑、RAM使用量↓
checkpoint_interval 5000ms 頻繁な更新には低い値
worker_processes 4 ネットワークスループット向上

継続的レプリケーション(リアルタイム同期):

{
  "source": "local_db",
  "target": "remote_db",
  "continuous": true
}

6. コンパクション

定期的なコンパクションでパフォーマンス維持:

# データベースコンパクション
curl -X POST http://localhost:5984/dbname/_compact

# ビューコンパクション
curl -X POST http://localhost:5984/dbname/_compact/design_doc

Reference

主要ファイル

  • livesync-proxy/src/domain/services.rs - CouchDbRepository トレイト
  • livesync-proxy/src/infrastructure/couchdb.rs - CouchDbClient 実装(674行)
  • livesync-proxy/src/interfaces/web/health.rs - ヘルスチェック
  • livesync-proxy/src/interfaces/web/metrics.rs - メトリクス

CouchDB API

  • /_up - ヘルスチェック
  • /{db} - データベース操作
  • /{db}/{docid} - ドキュメント操作
  • /{db}/_changes - 変更フィード
  • /{db}/_bulk_docs - バルク操作
  • /_replicate - レプリケーション
  • /{db}/_compact - コンパクション

参考リンク