mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
feat(geode): --no-search / [options].full_text_search — run without NIP-50
strfry implements no NIP-50 at all, so geode's default setup pays FTS tokenization on every searchable event for a feature the other side of a benchmark isn't providing. The new switch removes it cleanly: - geode: --no-search CLI flag and [options].full_text_search TOML key (default true). When off, the store skips the FTS index entirely, NIP-11 stops advertising 50 (an explicit [info] nips list stays operator-authoritative), and search filters match nothing via the existing QueryBuilder guard. RelayIndexingStrategy becomes relayIndexingStrategy(fullTextSearch) with the stock val kept. - relayBench: --geode-no-search runs the geode entry with the flag (relay named geode-nosearch in reports); README documents the apples-to-apples rationale and a recipe for benchmarking both geode flavors side by side. Alternating A/B on the 10k corpus: 4,767/4,792 ev/s without search vs 4,111/3,977 with — NIP-50 costs ~18% of ingest throughput at current write-path speed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NeoCvXnTxsKzqurkmjdC46
This commit is contained in:
@@ -59,6 +59,12 @@ verify_signatures = true
|
||||
# legacy in-policy verify path.
|
||||
# parallel_verify = true
|
||||
|
||||
# NIP-50 full-text search. Default: true. When false no FTS index is
|
||||
# built or maintained (inserts skip tokenization — a measurable share
|
||||
# of ingest cost), NIP-11 stops advertising NIP-50, and REQ filters
|
||||
# with a `search` term match nothing. CLI: --no-search.
|
||||
# full_text_search = true
|
||||
|
||||
# Require clients to NIP-42 AUTH before REQ/EVENT/COUNT.
|
||||
require_auth = false
|
||||
|
||||
|
||||
@@ -71,6 +71,11 @@ import java.io.File
|
||||
* --no-verify DO NOT verify event signatures (off by default
|
||||
* verify is on; use only for trusted-input
|
||||
* scenarios like fixture replay).
|
||||
* --no-search disable NIP-50 full-text search (sets
|
||||
* options.full_text_search = false): no FTS index is
|
||||
* built or maintained, NIP-11 stops advertising 50,
|
||||
* and `search` filters match nothing. Skips the
|
||||
* per-event tokenization cost on ingest.
|
||||
*/
|
||||
fun main(args: Array<String>) {
|
||||
val a = parseArgs(args)
|
||||
@@ -101,6 +106,10 @@ fun main(args: Array<String>) {
|
||||
// `[options].parallel_verify = false`.
|
||||
val parallelVerify =
|
||||
verifySigs && !a.flag("--no-parallel-verify") && config.options.parallel_verify
|
||||
// NIP-50 search is on by default; `--no-search` (or
|
||||
// `[options].full_text_search = false`) trades it for cheaper ingest —
|
||||
// e.g. to match relays that don't implement NIP-50 at all.
|
||||
val fullTextSearch = !a.flag("--no-search") && config.options.full_text_search
|
||||
|
||||
// Advertised URL: explicit `info.relay_url` wins, then build from
|
||||
// host/port/path. 0.0.0.0 bind → 127.0.0.1 in the URL so NIP-42
|
||||
@@ -111,9 +120,9 @@ fun main(args: Array<String>) {
|
||||
|
||||
val info =
|
||||
cliInfoFile?.let { RelayInfo.fromFile(it) }
|
||||
?: config.resolveInfo()
|
||||
?: config.resolveInfo(fullTextSearch)
|
||||
|
||||
val store: IEventStore = EventStore(dbName = dbFile, relay = advertisedUrl, indexStrategy = RelayIndexingStrategy)
|
||||
val store: IEventStore = EventStore(dbName = dbFile, relay = advertisedUrl, indexStrategy = relayIndexingStrategy(fullTextSearch))
|
||||
|
||||
val policyBuilder: () -> IRelayPolicy = {
|
||||
composePolicy(config, advertisedUrl, requireAuth, optionalAuth, verifySigs, parallelVerify)
|
||||
|
||||
@@ -35,8 +35,17 @@ import com.vitorpamplona.quartz.nip01Core.store.sqlite.DefaultIndexingStrategy
|
||||
* the gap grows linearly with the table. The extra index costs one B-tree
|
||||
* insert per event, which the same benchmark shows is noise next to the
|
||||
* Schnorr verify + tag indexing already paid on the write path.
|
||||
*
|
||||
* @param fullTextSearch maintain the NIP-50 FTS index. Off skips the
|
||||
* per-event tokenization on ingest (relayBench measured it at roughly a
|
||||
* quarter of write cost) at the price of `search` filters matching
|
||||
* nothing — pair it with a NIP-11 doc that doesn't advertise 50.
|
||||
*/
|
||||
val RelayIndexingStrategy =
|
||||
fun relayIndexingStrategy(fullTextSearch: Boolean = true) =
|
||||
DefaultIndexingStrategy(
|
||||
indexEventsByCreatedAtAlone = true,
|
||||
indexFullTextSearch = fullTextSearch,
|
||||
)
|
||||
|
||||
/** Stock relay strategy — everything on, matching geode's defaults. */
|
||||
val RelayIndexingStrategy = relayIndexingStrategy()
|
||||
|
||||
@@ -46,7 +46,7 @@ data class StaticConfig(
|
||||
val admin: AdminSection = AdminSection(),
|
||||
val negentropy: NegentropySection = NegentropySection(),
|
||||
) {
|
||||
fun resolveInfo(): RelayInfo =
|
||||
fun resolveInfo(fullTextSearch: Boolean = true): RelayInfo =
|
||||
RelayInfo(
|
||||
Nip11RelayInformation(
|
||||
name = info.name ?: RelayInfo.NAME,
|
||||
@@ -58,7 +58,9 @@ data class StaticConfig(
|
||||
version = info.version ?: RelayInfo.VERSION,
|
||||
supported_nips =
|
||||
info.supported_nips?.map(Int::toString)
|
||||
?: RelayInfo.SUPPORTED_NIPS,
|
||||
// An explicit [info] nips list is operator-authoritative;
|
||||
// the default list stays honest about search.
|
||||
?: if (fullTextSearch) RelayInfo.SUPPORTED_NIPS else RelayInfo.SUPPORTED_NIPS - "50",
|
||||
privacy_policy = info.privacy_policy,
|
||||
terms_of_service = info.terms_of_service,
|
||||
relay_countries = info.relay_countries,
|
||||
@@ -123,6 +125,15 @@ data class StaticConfig(
|
||||
val verify_signatures: Boolean = true,
|
||||
/** CPU fan-out verification in the IngestQueue. No-op when [verify_signatures] is false. */
|
||||
val parallel_verify: Boolean = true,
|
||||
/**
|
||||
* NIP-50 full-text search. On by default. When off, no FTS index
|
||||
* is created or maintained (inserts skip tokenization — a
|
||||
* measurable share of ingest cost), NIP-11 stops advertising
|
||||
* NIP-50, and REQ filters carrying a `search` term match nothing.
|
||||
* Turn off when search isn't needed, or to compare against relays
|
||||
* that don't offer NIP-50 at all (strfry, for example).
|
||||
*/
|
||||
val full_text_search: Boolean = true,
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
@@ -80,6 +80,27 @@ benchmark artifact.
|
||||
> and manifest/fingerprint convention are designed so other relay authors can
|
||||
> run the exact same workload and publish comparable numbers.
|
||||
|
||||
## Feature parity: NIP-50 search
|
||||
|
||||
geode maintains a NIP-50 full-text index by default; strfry has no search
|
||||
at all. That skews the *ingest* comparison — geode tokenizes every
|
||||
searchable event into the FTS index (~a quarter of its write cost) for a
|
||||
feature strfry isn't providing. `--geode-no-search` runs geode with
|
||||
`--no-search` (no FTS index, NIP-11 stops advertising 50) for the
|
||||
apples-to-apples write path:
|
||||
|
||||
```bash
|
||||
./relayBench/run.sh --geode-no-search # geode(no NIP-50) vs strfry
|
||||
```
|
||||
|
||||
To see the price of search inline, run both geode flavors side by side:
|
||||
|
||||
```bash
|
||||
GEODE=geode/build/install/geode/bin/geode
|
||||
./relayBench/run.sh --geode-no-search \
|
||||
--relay "geode-fts=$GEODE --host 127.0.0.1 --port {port} --db {dir}/geode.sqlite"
|
||||
```
|
||||
|
||||
## Adding another relay
|
||||
|
||||
No code needed if the relay can be launched from a command line:
|
||||
|
||||
@@ -57,6 +57,7 @@ import kotlin.system.exitProcess
|
||||
*/
|
||||
class Options(
|
||||
val geodeBin: String?,
|
||||
val geodeNoSearch: Boolean,
|
||||
val strfryBin: String?,
|
||||
val customRelays: List<Pair<String, String>>,
|
||||
val events: Int,
|
||||
@@ -87,6 +88,10 @@ private val USAGE =
|
||||
|
||||
Relays (at least one):
|
||||
--geode-bin PATH geode launcher (from :geode:installDist)
|
||||
--geode-no-search run geode with --no-search (NIP-50 off) so the
|
||||
write path matches relays that have no FTS at
|
||||
all, e.g. strfry — the apples-to-apples ingest
|
||||
comparison
|
||||
--strfry-bin PATH strfry binary
|
||||
--relay NAME=CMD any other relay; CMD may use {port} and {dir}
|
||||
(repeatable)
|
||||
@@ -199,7 +204,7 @@ fun main(args: Array<String>) {
|
||||
|
||||
private fun buildRelays(options: Options): List<RelayUnderTest> =
|
||||
buildList {
|
||||
options.geodeBin?.let { add(GeodeRelay(it)) }
|
||||
options.geodeBin?.let { add(GeodeRelay(it, noSearch = options.geodeNoSearch)) }
|
||||
options.strfryBin?.let { add(StrfryRelay(it, options.maxEventBytes, options.maxTags)) }
|
||||
options.customRelays.forEach { (name, cmd) -> add(CustomRelay(name, cmd)) }
|
||||
}
|
||||
@@ -307,6 +312,7 @@ private fun parseArgs(args: Array<String>): Options? {
|
||||
val moduleDir = File("relayBench").takeIf { it.isDirectory } ?: File(".")
|
||||
return Options(
|
||||
geodeBin = one("--geode-bin"),
|
||||
geodeNoSearch = "--geode-no-search" in flags,
|
||||
strfryBin = one("--strfry-bin"),
|
||||
customRelays =
|
||||
(map["--relay"] ?: emptyList()).map {
|
||||
|
||||
@@ -133,23 +133,32 @@ data class Nip11Info(
|
||||
val version: String?,
|
||||
)
|
||||
|
||||
/** Geode — the standalone JVM relay from this repo (`:geode:installDist`). */
|
||||
/**
|
||||
* Geode — the standalone JVM relay from this repo (`:geode:installDist`).
|
||||
*
|
||||
* [noSearch] passes `--no-search`, dropping NIP-50 (no FTS index, no
|
||||
* per-event tokenization on ingest). strfry has no NIP-50 at all, so this
|
||||
* is the apples-to-apples ingest configuration against it; the default
|
||||
* keeps geode's stock feature set and eats the indexing cost.
|
||||
*/
|
||||
class GeodeRelay(
|
||||
private val bin: String,
|
||||
) : RelayUnderTest("geode") {
|
||||
private val noSearch: Boolean = false,
|
||||
) : RelayUnderTest(if (noSearch) "geode-nosearch" else "geode") {
|
||||
override fun command(
|
||||
port: Int,
|
||||
dataDir: File,
|
||||
): List<String> =
|
||||
listOf(
|
||||
bin,
|
||||
"--host",
|
||||
"127.0.0.1",
|
||||
"--port",
|
||||
"$port",
|
||||
"--db",
|
||||
File(dataDir, "geode.sqlite").absolutePath,
|
||||
)
|
||||
buildList {
|
||||
add(bin)
|
||||
add("--host")
|
||||
add("127.0.0.1")
|
||||
add("--port")
|
||||
add("$port")
|
||||
add("--db")
|
||||
add(File(dataDir, "geode.sqlite").absolutePath)
|
||||
if (noSearch) add("--no-search")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user