> ## Documentation Index
> Fetch the complete documentation index at: https://www.helius.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 압축 필터를 사용한 대규모 계정 필터링

> 압축된 쿠쿠 필터를 사용하여 단일 LaserStream gRPC 스트림에서 수십만 개의 Solana 계정에 가입하세요. 대략 8배 더 작은 구독 요청과 정확한 로컬 검증을 제공합니다.

## 개요

LaserStream은 **쿠쿠 필터를 통한 압축된 계정 필터링**을 지원합니다. 구독 요청에서 명시적인 pubkey 목록(계정당 32바이트)을 보내는 대신 대략 **계정당 3–4바이트**의 비용이 드는 compact 확률 필터를 보냅니다.

이를 통해 **단일 스트림에서 수십만 개의 계정에 가입하는 것이 실용적**입니다 — 연결 간 샤딩이 없고 지나치게 큰 구독 요청도 없습니다.

예를 들어, 500,000개의 계정을 추적하는 필터는 약 2.1MB로 직렬화되며, 이는 원시 pubkey 목록의 16MB에 비해 대략 **7.6배 작습니다**. 정확한 절감 효과는 필터의 용량에 따라 달라지며, 용량에 가까울수록 계정당 바이트 수가 적습니다.

### 가용성

| 클라이언트                                                          | 최소 버전  | 쿠쿠 지원        |
| -------------------------------------------------------------- | ------ | ------------ |
| LaserStream SDK — Rust (`helius-laserstream`)                  | 0.2.0  | ✅            |
| LaserStream SDK — JavaScript/TypeScript (`helius-laserstream`) | 0.4.0  | ✅            |
| LaserStream SDK — Go                                           | —      | ❌ 아직 지원되지 않음 |
| Yellowstone gRPC — Rust (`yellowstone-grpc-client`)            | 13.1.0 | ✅            |

## 쿠쿠 필터 사용 시기

| 추적 계정       | 추천 방법                                           |
| ----------- | ----------------------------------------------- |
| 약 10,000까지  | 명시적인 pubkey 목록 (`account: [...]`) — 간단하고 정확합니다. |
| 약 10,000 이상 | `CompressedAccountFilterSet`를 통한 쿠쿠 필터          |

일반적인 사용 사례: 토큰 소유자 모니터링, 대출 프로토콜의 모든 위치 추적, 거래 또는 분석 시스템을 위한 대규모 지갑 세트 감시.

## 작동 방식

1. **클라이언트 측에서 필터를 구축하세요.** 각 추적된 pubkey를 `CompressedAccountFilterSet`에 삽입합니다. 해시 시드는 필터마다 무작위로 설정되어 직렬화됩니다. 따라서 서버는 귀하의 클라이언트가 사용한 동일한 시드로 들어오는 계정을 해시합니다.
2. **구독 요청에 첨부하세요.** `insert_into_subscribe_request()`는 표준 `SubscribeRequest`의 계정 스트림에 직렬화된 필터를 배치합니다.
3. **서버가 확률적으로 일치합니다.** 필터가 확률적이기 때문에 서버는 추적하지 않은 계정에 대한 업데이트를 제공할 수 있습니다 — 오탐지가 **풀 로드 시 1% 미만**으로 제한됩니다. **오인식은 절대 없습니다**: 추적된 계정에 대한 모든 업데이트가 전달됩니다.
4. **각 업데이트를 로컬에서 다시 확인하세요 — 이 단계는 필수입니다.** 처리를 하기 전에 들어오는 계정마다 `set.contains(pubkey)`를 호출하세요. 이 확인은 정확합니다 (내부 해시 세트로 지원됨), 따라서 로컬 필터링 후 오탐지가 없습니다.

## 빠른 시작 (Rust)

프로젝트에 SDK를 추가하세요:

```toml Cargo.toml theme={"system"}
[dependencies]
helius-laserstream = "0.2"
tokio = { version = "1", features = ["full"] }
futures = "0.3"
```

필터를 구축하고 구독에 첨부한 다음 로컬에서 오탐지를 제거하세요:

```rust main.rs [expandable] theme={"system"}
use {
    futures::StreamExt,
    helius_laserstream::{
        cuckoo::{CompressedAccountFilterSet, Pubkey},
        grpc::{subscribe_update::UpdateOneof, SubscribeRequest},
        subscribe, LaserstreamConfig,
    },
    std::str::FromStr,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // The exact set of accounts you care about. In production this is
    // typically loaded from your database — hundreds of thousands of keys.
    let tracked: Vec<Pubkey> = [
        "So11111111111111111111111111111111111111112", // Wrapped SOL
        "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
        "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", // USDT
    ]
    .iter()
    .map(|s| Pubkey::from_str(s).unwrap())
    .collect();

    // Build the cuckoo filter. Size it for your peak tracked-set size.
    let mut set = CompressedAccountFilterSet::with_capacity(500_000)?;
    for pk in &tracked {
        set.insert(*pk)?;
    }
    println!(
        "Tracking {} accounts via cuckoo filter ({} bytes on the wire)",
        set.len(),
        set.to_proto().data.len()
    );

    // Attach the compressed filter to the accounts stream.
    let mut request = SubscribeRequest::default();
    set.insert_into_subscribe_request(&mut request, "tracked_accounts");

    let config = LaserstreamConfig::new(
        "https://laserstream-mainnet-ewr.helius-rpc.com".to_string(), // Choose your closest region
        "YOUR_API_KEY".to_string(), // Replace with your key from https://dashboard.helius.dev/
    );

    let (stream, _handle) = subscribe(config, request);
    tokio::pin!(stream);
    while let Some(message) = stream.next().await {
        match message {
            Ok(update) => {
                if let Some(UpdateOneof::Account(account_update)) = update.update_oneof {
                    if let Some(info) = account_update.account {
                        let pk = Pubkey::try_from(info.pubkey.as_slice()).ok();
                        // Re-check locally: drop server-side false positives.
                        match pk {
                            Some(pk) if set.contains(pk) => {
                                println!(
                                    "tracked account update: {pk} (slot {})",
                                    account_update.slot
                                );
                            }
                            Some(pk) => {
                                println!("(false positive, ignored): {pk}");
                            }
                            None => {}
                        }
                    }
                }
            }
            Err(e) => eprintln!("stream error: {e}"),
        }
    }

    Ok(())
}
```

완전 실행 가능한 버전은 SDK에 포함되어 있습니다: [`rust/examples/cuckoo_account_filter.rs`](https://github.com/helius-labs/laserstream-sdk/blob/main/rust/examples/cuckoo_account_filter.rs).

## 빠른 시작 (JavaScript/TypeScript)

SDK를 설치하세요 (쿠쿠 지원은 `helius-laserstream` 0.4.0+ 버전 필요):

```bash theme={"system"}
npm install helius-laserstream
```

필터를 구축하고 첨부한 후 각 업데이트를 로컬에서 다시 확인하세요:

```typescript [expandable] theme={"system"}
import {
  subscribe,
  CommitmentLevel,
  CompressedAccountFilterSet,
  SubscribeUpdate,
  LaserstreamConfig,
} from 'helius-laserstream';

async function main() {
  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY', // Replace with your key from https://dashboard.helius.dev/
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com', // Choose your closest region
  };

  // The accounts you want to track. In production this is typically loaded
  // from your database — hundreds of thousands of keys.
  const addresses = [
    'So11111111111111111111111111111111111111112', // Wrapped SOL
    'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
    'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', // USDT
  ];

  // Build a compact cuckoo filter instead of sending the full pubkey list.
  // Size capacity for your peak tracked-set size.
  const tracked = new CompressedAccountFilterSet(500_000);
  for (const address of addresses) {
    tracked.insert(address);
  }

  // Attach the filter to the request (no explicit account list needed).
  const request: any = { accounts: {}, commitment: CommitmentLevel.CONFIRMED };
  tracked.insertIntoSubscribeRequest(request, 'tracked-accounts');

  const stream = await subscribe(
    config,
    request,
    async (update: SubscribeUpdate) => {
      const pubkey = update.account?.account?.pubkey;
      if (!pubkey) return;
      // Re-check locally: drop server-side false positives. This is exact.
      if (tracked.contains(pubkey)) {
        console.log('tracked account update:', update.account);
      }
    },
    (error: Error) => {
      console.error('Stream error:', error);
    }
  );

  process.on('SIGINT', () => {
    stream.cancel();
    process.exit(0);
  });
}

main().catch(console.error);
```

완전 실행 가능한 버전은 SDK에 포함되어 있습니다: [`javascript/examples/cuckoo-account-sub.ts`](https://github.com/helius-labs/laserstream-sdk/blob/main/javascript/examples/cuckoo-account-sub.ts).

## API 참조

`CompressedAccountFilterSet`는 원시 쿠쿠 필터와 정확한 해시 세트를 함께 래핑하므로 변형 및 멤버십 확인이 항상 안전하고 정확합니다:

| 메서드                                                    | 동작                                                                                  |
| ------------------------------------------------------ | ----------------------------------------------------------------------------------- |
| `with_capacity(n)`                                     | `n` 추적된 계정에 맞춰 필터를 생성합니다. **최대** 추적 집합 크기에 맞게 크기를 조정하세요.                            |
| `insert(pubkey)`                                       | 새로운 경우 `Ok(true)`, 중복인 경우 `Ok(false)`, 필터가 용량에 도달한 경우 `Err(TableFullError)`를 반환합니다. |
| `remove(pubkey)`                                       | 계정을 제거합니다. 안전하고 정확합니다.                                                              |
| `contains(pubkey)`                                     | 정확한 멤버십 확인 — 서버 측 오탐지를 제거하는 데 사용하세요.                                                |
| `insert_into_subscribe_request(&mut request, "label")` | `SubscribeRequest`의 계정 스트림에 필터를 첨부합니다.                                              |
| `to_account_filter()` / `to_proto()`                   | 맞춤형 요청 조립을 위한 하위 수준 변환.                                                             |
| `is_dirty()` / `take_dirty()`                          | 마지막 요청 이후 집합이 변경되었는지 보고 — 재구독 사이클에 유용합니다.                                           |

위 메서드 이름은 Rust 관례를 사용합니다. JavaScript/TypeScript SDK는 같은 표면을 camelCase로 제공합니다 — `new CompressedAccountFilterSet(capacity)` 대신 `with_capacity`, `insertIntoSubscribeRequest`, `isDirty`, `takeDirty`, `toProto` 등이 포함됩니다. JavaScript에서 `insert`는 boolean을 반환하며 (새로 추가된 경우 `true`), 필터가 포화 상태일 때는 `TableFullError`를 던집니다. pubkey는 base58 문자열, raw 32바이트 또는 `toBytes()` 메서드가 있는 객체로 전달할 수 있습니다.

항상 `CuckooFilter`을 래핑하는 `CompressedAccountFilterSet`를 사용하세요. 원시 필터의 `remove()`는 잘못된 항목을 조용히 제거할 수 있습니다 — 쿠쿠 필터의 문서화된 위험성입니다. 래퍼는 필터와 정확한 해시 세트를 결합하므로 삽입, 제거 및 포함이 항상 정확합니다.

## 용량 크기 조정

* `with_capacity(n)`를 통해 추적할 것으로 예상되는 계정의 **최대** 수에 맞춰 필터의 크기를 조정하세요.
* 용량을 초과하는 삽입은 `TableFullError`로 우아하게 실패하며 필터가 손상되지 않습니다. 실제로 테이블은 튕겨나기 전에 약간의 초과 채움을 허용하지만, 그 여유 공간에 의존하지 마세요.
* 직렬화 크기는 얼마나 많은 계정을 삽입했는가가 아니라 용량에 의해 결정됩니다 — 따라서 과대 용량의 필터는 전송 바이트를 낭비합니다. 실제 최고치에 가까운 용량을 선택하세요.

## 추적 집합 업데이트

추적 집합이 변경될 때 (새 계정을 팔로우, 이전 계정을 삭제):

1. `CompressedAccountFilterSet`에서 `insert()` / `remove()`를 호출하세요.
2. 마지막으로 보낸 이후 필터가 변경되었는지 확인하려면 `is_dirty()`(또는 `take_dirty()`로 플래그를 소모)를 확인하세요.
3. 더러워진 경우 `insert_into_subscribe_request()`로 요청을 다시 작성하세요. JavaScript에서는 `stream.write(request)`로 동일한 스트림에서 다시 보낼 수 있고, Rust에서는 다시 작성된 요청으로 다시 구독할 수 있습니다.

## FAQ

<Accordion title="내 필터에 있는 계정의 업데이트를 놓칠 수 있나요?">
  아니요. 쿠쿠 필터는 false positives (추적되지 않은 계정에 대한 추가 업데이트)를 생성하지만 **false negatives는 절대 없습니다**. 추적된 계정에 대한 모든 업데이트가 전달됩니다.
</Accordion>

<Accordion title="얼마나 많은 추가 (오탐) 업데이트를 받을까요?">
  풀 로드 시 1% 미만이며, 필터가 용량보다 작을 때는 일반적으로 더 적습니다. 로컬 `contains()` 호출 한 번으로 이들을 정확하게 필터링합니다.
</Accordion>

<Accordion title="어떤 클라이언트가 쿠쿠 필터를 지원하나요?">
  Rust SDK (`helius-laserstream` 0.2.0+), JavaScript/TypeScript SDK (`helius-laserstream` 0.4.0+), 그리고 Yellowstone Rust 클라이언트 (`yellowstone-grpc-client` 13.1.0+). Go SDK는 아직 지원되지 않습니다. 위의 [가용성 표](#가용성)를 참조하세요.
</Accordion>

<Accordion title="명시적인 pubkey 목록을 여전히 사용할 수 있나요?">
  네. 표준 `account: [...]` 필터는 변경되지 않으며 작은 계정 집합(약 10,000개 계정까지)에는 여전히 적합한 선택입니다. [계정 구독 가이드](/docs/ko/laserstream/guides/account-subscription)를 참조하세요.
</Accordion>

## 관련 항목

<CardGroup cols={2}>
  <Card title="계정 구독" icon="user" href="/docs/ko/laserstream/guides/account-subscription">
    소유자, 데이터 크기 및 memcmp 필터를 사용한 표준 계정 필터링.
  </Card>

  <Card title="클라이언트 및 SDK" icon="code" href="/docs/ko/laserstream/clients">
    자동 재생 및 다시 연결 기능을 갖춘 TypeScript, Rust 및 Go SDK.
  </Card>
</CardGroup>
