Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/hash-and-trigram-name-indexes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"ensindexer": minor
"@ensnode/ensdb-sdk": minor
---

Re-enable `subgraph_domain.name` indexes (originally disabled in #1819) by pairing a hash index for exact-match lookups with a GIN trigram index (`gin_trgm_ops`) for partial-match filters (`_contains`, `_starts_with`, `_ends_with`). The hash index avoids the btree 8191-byte row size limit triggered by spam names. The trigram index requires the `pg_trgm` Postgres extension, which ENSIndexer now installs automatically in the setup hook before Ponder creates indexes.
Comment thread
shrugs marked this conversation as resolved.
Outdated
15 changes: 14 additions & 1 deletion apps/ensindexer/src/lib/indexing-engines/ponder.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import type { Context, EventNames } from "ponder:registry";
import { beforeEach, describe, expect, expectTypeOf, it, vi } from "vitest";

import "@/lib/__test__/mockLogger";

import type { IndexingEngineContext, IndexingEngineEvent } from "./ponder";

const { mockPonderOn } = vi.hoisted(() => ({ mockPonderOn: vi.fn() }));

const mockWaitForEnsRainbow = vi.hoisted(() => vi.fn());

const mockEnsDbExecute = vi.hoisted(() => vi.fn());

vi.mock("ponder:registry", () => ({
ponder: {
on: (...args: unknown[]) => mockPonderOn(...args),
Expand All @@ -21,10 +25,19 @@ vi.mock("@/lib/ensrainbow/singleton", () => ({
waitForEnsRainbowToBeReady: mockWaitForEnsRainbow,
}));

vi.mock("@/lib/ensdb/singleton", () => ({
ensDbClient: {
ensDb: {
execute: (...args: unknown[]) => mockEnsDbExecute(...args),
},
},
}));

describe("addOnchainEventListener", () => {
beforeEach(async () => {
vi.clearAllMocks();
mockWaitForEnsRainbow.mockResolvedValue(undefined);
mockEnsDbExecute.mockResolvedValue(undefined);
// Reset module state to test idempotent behavior correctly
vi.resetModules();
});
Expand Down Expand Up @@ -347,7 +360,7 @@ describe("addOnchainEventListener", () => {
});
});

describe("setup events (no preconditions)", () => {
describe("setup events (ENSRainbow wait skipped)", () => {
it("skips ENSRainbow wait for :setup events", async () => {
const { addOnchainEventListener } = await getPonderModule();
const handler = vi.fn().mockResolvedValue(undefined);
Expand Down
34 changes: 34 additions & 0 deletions apps/ensindexer/src/lib/indexing-engines/ponder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ import {
type Event as PonderIndexingEvent,
ponder,
} from "ponder:registry";
import { sql } from "drizzle-orm";

import { ensDbClient } from "@/lib/ensdb/singleton";
import { waitForEnsRainbowToBeReady } from "@/lib/ensrainbow/singleton";
import { logger } from "@/lib/logger";

/**
* Context passed to event handlers registered with
Expand Down Expand Up @@ -146,6 +149,37 @@ async function initializeIndexingSetup(): Promise<void> {
* ENSIndexer relies on these indexing metrics being immediately available on startup to build and
* store the current Indexing Status in ENSDb.
*/

// Ensure all required Postgres extensions are installed before Ponder
// creates indexes that depend on them.
logger.debug({
msg: "Ensuring required Postgres extensions are installed",
module: "IndexingEngine",
});

try {
// `pg_trgm` necessary for GIN trigram indexes (partial string matching).
// Install into `public` so `gin_trgm_ops` is on the default search_path
// when Ponder issues the unqualified `CREATE INDEX ... USING gin (name gin_trgm_ops)`.
await ensDbClient.ensDb.execute(sql`CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public`);
} catch (cause) {
// Log the underlying Postgres error so ops can see it without walking the
// Error#cause chain (some log formatters don't surface `cause` by default).
logger.error({
msg: "Failed to install the `pg_trgm` Postgres extension",
error: cause,
module: "IndexingEngine",
});
throw new Error(
`Unable to create the pg_trgm extension in the connected Postgres: ensure the database user has permission to install extensions and that the 'pg_trgm' extension is available.`,
{ cause },
);
Comment thread
shrugs marked this conversation as resolved.
Outdated
}

logger.info({
msg: "Ensured required Postgres extensions are installed",
module: "IndexingEngine",
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This guide covers running ENSNode locally for development and contributions.
### Prerequisites

- [Git](https://git-scm.com/)
- [Postgres 17](https://www.postgresql.org/)
- [Postgres 17](https://www.postgresql.org/) with the [`pg_trgm`](https://www.postgresql.org/docs/current/pgtrgm.html) extension available for installation (ships with stock Postgres contrib; ENSIndexer runs `CREATE EXTENSION IF NOT EXISTS pg_trgm` at startup to back partial-name search indexes)
- [Node.js](https://nodejs.org/)
- It's recommended you install Node.js through [nvm](https://github.com/nvm-sh/nvm) or [asdf](https://asdf-vm.com/).
- see `.nvmrc` and `.tool-versions` for the specific version of Node.js
Expand Down
4 changes: 4 additions & 0 deletions docs/ensnode.io/src/content/docs/docs/deploying/docker.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import dockercompose from '@workspace/docker-compose.yml?raw';

The Docker images are the easiest way to run or deploy the ENSNode suite of services, both locally and in the cloud.

:::note[Postgres Requirement]
ENSIndexer runs `CREATE EXTENSION IF NOT EXISTS pg_trgm` at startup to back partial-name search indexes. If you're swapping out the bundled `postgres:17` image for a managed or custom Postgres, make sure the [`pg_trgm`](https://www.postgresql.org/docs/current/pgtrgm.html) extension is available for installation (it ships with stock Postgres contrib and is enabled by default on most managed providers).
Comment thread
shrugs marked this conversation as resolved.
Outdated
:::

<LinkCard
title="Configure Environment Variables"
href="/ensindexer/usage/configuration"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ These Terraform scripts are currently specific to ENSNode instances hosted by Na
- AWS account (for DNS management)
- AWS S3 bucket defined inside AWS account - `ensnode-terraform` (for Terraform state)

:::note[Postgres Requirement]
ENSIndexer runs `CREATE EXTENSION IF NOT EXISTS pg_trgm` at startup to back partial-name search indexes. Render's managed Postgres has [`pg_trgm`](https://www.postgresql.org/docs/current/pgtrgm.html) available by default; if you adapt this configuration to a different provider, confirm the extension is available for installation on your Postgres plan.
:::

## Configuration

Copy `.env.sample` to `.env.local` and fill in your configuration values:
Expand Down
4 changes: 4 additions & 0 deletions docs/ensnode.io/src/content/docs/docs/running/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ sidebar:

import { LinkCard } from '@astrojs/starlight/components';

:::note[Postgres Requirement]
ENSIndexer runs `CREATE EXTENSION IF NOT EXISTS pg_trgm` at startup to back partial-name search indexes. Any Postgres instance used as ENSDb must have the [`pg_trgm`](https://www.postgresql.org/docs/current/pgtrgm.html) extension available for installation (it ships with stock Postgres contrib and is enabled by default on most managed providers).
Comment thread
shrugs marked this conversation as resolved.
Outdated
:::

### Running in ens-test-env

`ens-test-env` provides a local Anvil chain with ENSNode, suitable for testing & developing ENS apps.
Expand Down
11 changes: 8 additions & 3 deletions packages/ensdb-sdk/src/ensindexer-abstract/subgraph.schema.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { sql } from "drizzle-orm";
import type { Address } from "enssdk";
import { index, onchainTable, relations } from "ponder";
Comment thread
shrugs marked this conversation as resolved.
Outdated

Expand Down Expand Up @@ -93,9 +94,13 @@ export const subgraph_domain = onchainTable(
expiryDate: t.bigint(),
}),
(t) => ({
// Temporarily disable the `byName` index to avoid index creation issues.
// For more details, see: https://github.com/namehash/ensnode/issues/1819
// byName: index().on(t.name),
// uses a hash index because some name values exceed the btree max row size (8191 bytes)
Comment thread
shrugs marked this conversation as resolved.
Comment thread
shrugs marked this conversation as resolved.
byExactName: index().using("hash", t.name),
Comment thread
shrugs marked this conversation as resolved.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised no other code is updated in relation to us making the schema changes here? Ex: code in API handlers?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

none! postgres planner will use the indexes without specification

// GIN trigram index for partial-match filters (_contains, _starts_with, _ends_with).
// Inline `gin_trgm_ops` via `sql` because passing it through `.op()` gets dropped
// by Ponder's index-emission layer, producing `USING gin (name)` with no opclass.
byFuzzyName: index().using("gin", sql`${t.name} gin_trgm_ops`),
Comment thread
shrugs marked this conversation as resolved.

byLabelhash: index().on(t.labelhash),
byParentId: index().on(t.parentId),
byOwnerId: index().on(t.ownerId),
Expand Down
Loading