本文へ移動
cccskills
無料GitHub で公開

clickhouse-nip33-addressable-dedup

Fix duplicate Nostr events in ClickHouse when using ReplacingMergeTree for NIP-33 addressable events (Kind 30000+). Use when: (1) Edited videos/events appear as duplicates, (2) Same d_tag shows multiple events with different IDs, (3) FINAL keyword doesn't deduplicate properly for parameterized replaceable events. The issue is that FINAL deduplicates by ORDER BY key (typically `id`), not by (pubkey, kind, d_tag).

インストール方法を見る

含まれるファイル(1)

  • SKILL.md4.3 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

ClickHouse NIP-33 Addressable Event Deduplication

Problem

When storing Nostr events in ClickHouse using ReplacingMergeTree, edited addressable events (Kind 30000-39999) appear as duplicates. Users edit their video/event, a new event ID is created with the same d_tag, but both versions are shown instead of just the latest.

Context / Trigger Conditions

  • Nostr relay storing events in ClickHouse with ReplacingMergeTree
  • Users report seeing duplicate videos/events after editing
  • Query returns multiple events with same (pubkey, kind, d_tag) but different id values
  • Using FINAL keyword but duplicates still appear
  • Kind 30000+ events (NIP-33 parameterized replaceable events like Kind 34236 videos)

Root Cause

The FINAL keyword in ClickHouse deduplicates based on the table's ORDER BY key. If your table is defined as:

ENGINE = ReplacingMergeTree(indexed_at)
ORDER BY (id)

Then FINAL deduplicates by id. Two events with different IDs are NOT considered duplicates, even if they represent the same addressable "slot" per NIP-33.

For NIP-33 addressable events, the replacement key should be (pubkey, kind, d_tag), not id.

Solution

Option 1: Fix at View Level (Recommended)

Change your videos view to use LIMIT 1 BY instead of FINAL:

CREATE VIEW videos AS
SELECT
    id,
    pubkey,
    created_at,
    kind,
    content,
    tags,
    d_tag,
    title,
    thumbnail,
    video_url
FROM events_local
WHERE kind IN (34235, 34236)
ORDER BY pubkey, kind, d_tag, created_at DESC
LIMIT 1 BY pubkey, kind, d_tag;

The LIMIT 1 BY clause keeps only the first row (latest by created_at) for each unique combination of (pubkey, kind, d_tag).

Option 2: Fix at Table Level (Breaking Change)

If you can recreate the table, use a composite ORDER BY:

CREATE TABLE events_addressable (
    ...
) ENGINE = ReplacingMergeTree(created_at)
ORDER BY (pubkey, kind, d_tag);

This makes FINAL work correctly for NIP-33 events but may not work for all event types.

Migration Example

-- Drop dependent views first
DROP VIEW IF EXISTS trending_videos;
DROP VIEW IF EXISTS video_stats;
DROP VIEW IF EXISTS videos;

-- Recreate with proper deduplication
CREATE VIEW videos AS
SELECT *
FROM events_local
WHERE kind IN (34235, 34236)
ORDER BY pubkey, kind, d_tag, created_at DESC
LIMIT 1 BY pubkey, kind, d_tag;

-- Recreate dependent views...

Verification

Query for a specific user's videos and confirm no duplicates:

SELECT id, d_tag, created_at
FROM videos
WHERE pubkey = 'user_pubkey_here'
ORDER BY created_at DESC;

Each d_tag should appear only once, with the highest created_at value.

Example

Before (broken):

| id       | d_tag    | created_at |
|----------|----------|------------|
| abc123   | video1   | 1769697188 | ← Newer edit
| def456   | video1   | 1769697150 | ← Original (should be hidden)

After (fixed):

| id       | d_tag    | created_at |
|----------|----------|------------|
| abc123   | video1   | 1769697188 | ← Only latest shown

Notes

  • This applies to all NIP-33 addressable events (Kind 30000-39999), not just videos
  • The LIMIT 1 BY approach is query-time deduplication, not storage deduplication
  • Old event versions remain in storage but won't appear in query results
  • Consider periodic cleanup of old event versions if storage is a concern
  • Don't forget to recreate dependent views in the correct order

References

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Fix ArgoCD ExternalSecret deployment failing with "namespace X is not permitted in project Y". Use when: (1) ExternalSecret shows OutOfSync in ArgoCD but won't sync, (2) ArgoCD application status shows "namespace X is not permitted in project 'infrastructure'", (3) ExternalSecret targets a namespace managed by a different ArgoCD project, (4) Using apps-of-apps pattern with separate infrastructure and application projects.

日本語の概要は準備中です。原文の説明を表示しています。

divinevideo/divine-mobile2662026年10月13日 更新

Art direction for any content — reads text, PDF, Word, HTML, PPT, then proposes 2-3 creative directions with photography style, mood, and visual language. After selection, generates AI image prompts and visual briefs section-by-section. Use when the user shares content and needs visual direction, image sourcing, or creative direction for any material.

日本語の概要は準備中です。原文の説明を表示しています。

divinevideo/divine-mobile2662026年10月13日 更新

Fix "Null check operator used on a null value" errors when an object is set to null during an async await. Use when: (1) Object reference is nullified while awaiting, (2) Code accesses object with ! after await returns, (3) Cancel/dispose operations run concurrently with async operations on same object. Solution: capture local reference before await.

日本語の概要は準備中です。原文の説明を表示しています。

divinevideo/divine-mobile2662026年10月13日 更新

Add custom metadata headers (x-amz-meta-*) to AWS v4 signed requests for GCS S3-compatible API. Use when: (1) Adding custom metadata to GCS uploads via S3 API, (2) Getting signature mismatch errors after adding new headers, (3) x-amz-meta-* headers being ignored or causing 403 errors. Custom headers MUST be included in canonical headers and signed headers list.

日本語の概要は準備中です。原文の説明を表示しています。

divinevideo/divine-mobile2662026年10月13日 更新

Fix password/secret authentication failures caused by trailing newlines when creating Google Cloud secrets (or similar) with bash here-strings. Use when: (1) Password authentication fails with correct password, (2) Secret created with `<<< "value"` syntax, (3) Error like "password authentication failed" or "invalid token" despite correct value. Bash here-strings (`<<<`) add a trailing newline that corrupts secrets.

日本語の概要は準備中です。原文の説明を表示しています。

divinevideo/divine-mobile2662026年10月13日 更新

Fix silent video/media processing failures caused by URL extraction code that filters on file extensions (.mp4, .webm, .webp). Use when: (1) Media moderation, transcoding, or analysis silently skips files from Blossom or content-addressed storage servers, (2) URL extraction from Nostr event tags (imeta, r tags) drops URLs without recognized extensions, (3) CDN fallback URLs append .mp4 but the actual server uses extensionless content-addressed paths like /{sha256}. Common in Nostr video events (kind 34236) where different clients use different URL formats.

日本語の概要は準備中です。原文の説明を表示しています。

divinevideo/divine-mobile2662026年10月13日 更新

divinevideo のスキルをすべて見る

このスキルの問題を報告する