bbolt

package
v0.48.10 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 9, 2026 License: Unlicense Imports: 33 Imported by: 0

Documentation

Index

Constants

View Source
const (
	EdgeTypeAuthor   byte = 0 // Event author relationship
	EdgeTypePTag     byte = 1 // P-tag reference (event mentions pubkey)
	EdgeTypeETag     byte = 2 // E-tag reference (event references event)
	EdgeTypeFollows  byte = 3 // Kind 3 follows relationship
	EdgeTypeReaction byte = 4 // Kind 7 reaction
	EdgeTypeRepost   byte = 5 // Kind 6 repost
	EdgeTypeReply    byte = 6 // Reply (kind 1 with e-tag)
)

Edge type constants

Variables

View Source
var ErrBatcherStopped = &batcherStoppedError{}

ErrBatcherStopped is returned when adding to a stopped batcher

Functions

This section is empty.

Types

type B

type B struct {
	Logger *Logger
	// contains filtered or unexported fields
}

B implements the database.Database interface using BBolt as the storage backend. Optimized for HDD with write batching and adjacency list graph storage.

func New

func New(
	ctx context.Context, cancel context.CancelFunc, dataDir, logLevel string,
) (b *B, err error)

New creates a new BBolt database instance with default configuration.

func NewWithConfig

func NewWithConfig(
	ctx context.Context, cancel context.CancelFunc, cfg *BboltConfig,
) (b *B, err error)

NewWithConfig creates a new BBolt database instance with full configuration.

func (*B) AddNIP43Member

func (b *B) AddNIP43Member(pubkey []byte, inviteCode string) error

AddNIP43Member adds a NIP-43 member.

func (*B) BuildIndexes added in v0.48.8

func (b *B) BuildIndexes(ctx context.Context) error

BuildIndexes builds all query indexes from stored events. Call this after importing events with SaveEventMinimal. Processes events in chunks to avoid OOM on large databases.

func (*B) CacheEvents

func (b *B) CacheEvents(f *filter.F, events event.S)

CacheEvents caches events for a filter (stub - no caching in bbolt).

func (*B) CacheMarshaledJSON

func (b *B) CacheMarshaledJSON(f *filter.F, marshaledJSON [][]byte)

CacheMarshaledJSON caches JSON for a filter (stub - no caching in bbolt).

func (*B) CheckForDeleted

func (b *B) CheckForDeleted(ev *event.E, admins [][]byte) error

CheckForDeleted checks if an event has been deleted.

func (*B) Close

func (b *B) Close() (err error)

Close releases resources and closes the database.

func (*B) CountEvents

func (b *B) CountEvents(c context.Context, f *filter.F) (count int, approximate bool, err error)

CountEvents counts events matching a filter.

func (*B) DeleteEvent

func (b *B) DeleteEvent(c context.Context, eid []byte) error

DeleteEvent deletes an event by ID.

func (*B) DeleteEventBySerial

func (b *B) DeleteEventBySerial(c context.Context, ser *types.Uint40, ev *event.E) error

DeleteEventBySerial deletes an event by serial.

func (*B) DeleteExpired

func (b *B) DeleteExpired()

DeleteExpired deletes expired events.

func (*B) DeleteInviteCode

func (b *B) DeleteInviteCode(code string) error

DeleteInviteCode deletes an invite code.

func (*B) DeleteMarker

func (b *B) DeleteMarker(key string) error

DeleteMarker deletes a marker.

func (*B) EdgeExists

func (b *B) EdgeExists(srcSerial, dstSerial uint64, edgeType byte) (bool, error)

EdgeExists checks if an edge exists between two serials. Uses bloom filter for fast negative lookups.

func (*B) EventIdsBySerial

func (b *B) EventIdsBySerial(start uint64, count int) (evs []uint64, err error)

EventIdsBySerial gets event IDs by serial range.

func (*B) Export

func (b *B) Export(c context.Context, w io.Writer, pubkeys ...[]byte)

Export exports events to a writer.

func (*B) ExtendBlossomSubscription

func (b *B) ExtendBlossomSubscription(pubkey []byte, tier string, storageMB int64, daysExtended int) error

ExtendBlossomSubscription extends a Blossom subscription.

func (*B) ExtendSubscription

func (b *B) ExtendSubscription(pubkey []byte, days int) error

ExtendSubscription extends a subscription.

func (*B) FetchEventBySerial

func (b *B) FetchEventBySerial(ser *types.Uint40) (ev *event.E, err error)

FetchEventBySerial fetches an event by its serial number.

func (*B) FetchEventsBySerials

func (b *B) FetchEventsBySerials(serials []*types.Uint40) (events map[uint64]*event.E, err error)

FetchEventsBySerials fetches multiple events by their serial numbers.

func (*B) FindEventByAuthorAndKind

func (b *B) FindEventByAuthorAndKind(authorSerial uint64, kindNum uint16) (uint64, error)

FindEventByAuthorAndKind finds an event serial by author and kind. For replaceable events like kind-3, returns the most recent one.

func (*B) GetAllNIP43Members

func (b *B) GetAllNIP43Members() ([][]byte, error)

GetAllNIP43Members gets all NIP-43 members.

func (*B) GetBlossomStorageQuota

func (b *B) GetBlossomStorageQuota(pubkey []byte) (quotaMB int64, err error)

GetBlossomStorageQuota gets Blossom storage quota.

func (*B) GetCachedEvents

func (b *B) GetCachedEvents(f *filter.F) (event.S, bool)

GetCachedEvents gets cached events for a filter (stub - no caching in bbolt).

func (*B) GetCachedJSON

func (b *B) GetCachedJSON(f *filter.F) ([][]byte, bool)

GetCachedJSON gets cached JSON for a filter (stub - no caching in bbolt).

func (*B) GetETagsFromEvent

func (b *B) GetETagsFromEvent(eventSerial uint64) ([]uint64, error)

GetETagsFromEvent returns event serials referenced by an event.

func (*B) GetEventAccessInfo

func (b *B) GetEventAccessInfo(serial uint64) (lastAccess int64, accessCount uint32, err error)

GetEventAccessInfo gets event access information.

func (*B) GetEventVertex

func (b *B) GetEventVertex(eventSerial uint64) (*EventVertex, error)

GetEventVertex retrieves the adjacency list for an event.

func (*B) GetEventsAuthoredBy

func (b *B) GetEventsAuthoredBy(pubkeySerial uint64) ([]uint64, error)

GetEventsAuthoredBy returns event serials authored by a pubkey.

func (*B) GetEventsMentioning

func (b *B) GetEventsMentioning(pubkeySerial uint64) ([]uint64, error)

GetEventsMentioning returns event serials that mention a pubkey.

func (*B) GetFollowsFromPubkeySerial

func (b *B) GetFollowsFromPubkeySerial(pubkeySerial *types.Uint40) ([]*types.Uint40, error)

GetFollowsFromPubkeySerial returns the pubkey serials that a user follows. This extracts p-tags from the user's kind-3 contact list event.

func (*B) GetFullIdPubkeyBySerial

func (b *B) GetFullIdPubkeyBySerial(ser *types.Uint40) (fidpk *store.IdPkTs, err error)

GetFullIdPubkeyBySerial gets full event ID and pubkey by serial.

func (*B) GetFullIdPubkeyBySerials

func (b *B) GetFullIdPubkeyBySerials(sers []*types.Uint40) (fidpks []*store.IdPkTs, err error)

GetFullIdPubkeyBySerials gets full event IDs and pubkeys for multiple serials.

func (*B) GetLeastAccessedEvents

func (b *B) GetLeastAccessedEvents(limit int, minAgeSec int64) (serials []uint64, err error)

GetLeastAccessedEvents gets least accessed events.

func (*B) GetMarker

func (b *B) GetMarker(key string) (value []byte, err error)

GetMarker gets a metadata marker.

func (*B) GetNIP43Membership

func (b *B) GetNIP43Membership(pubkey []byte) (*database.NIP43Membership, error)

GetNIP43Membership gets NIP-43 membership details.

func (*B) GetOrCreateRelayIdentitySecret

func (b *B) GetOrCreateRelayIdentitySecret() (skb []byte, err error)

GetOrCreateRelayIdentitySecret gets or creates the relay's identity secret.

func (*B) GetPTagsFromEvent

func (b *B) GetPTagsFromEvent(eventSerial uint64) ([]uint64, error)

GetPTagsFromEvent returns pubkey serials tagged in an event.

func (*B) GetPaymentHistory

func (b *B) GetPaymentHistory(pubkey []byte) ([]database.Payment, error)

GetPaymentHistory gets payment history.

func (*B) GetPubkeyVertex

func (b *B) GetPubkeyVertex(pubkeySerial uint64) (*PubkeyVertex, error)

GetPubkeyVertex retrieves the adjacency list for a pubkey.

func (*B) GetReferencingEvents

func (b *B) GetReferencingEvents(targetSerial uint64) ([]uint64, error)

GetReferencingEvents returns event serials that reference a target event via e-tag.

func (*B) GetRelayIdentitySecret

func (b *B) GetRelayIdentitySecret() (skb []byte, err error)

GetRelayIdentitySecret gets the relay's identity secret key.

func (*B) GetSerialById

func (b *B) GetSerialById(id []byte) (ser *types.Uint40, err error)

GetSerialById gets the serial for an event ID.

func (*B) GetSerialsByIds

func (b *B) GetSerialsByIds(ids *tag.T) (serials map[string]*types.Uint40, err error)

GetSerialsByIds gets serials for multiple event IDs.

func (*B) GetSerialsByIdsWithFilter

func (b *B) GetSerialsByIdsWithFilter(ids *tag.T, fn func(ev *event.E, ser *types.Uint40) bool) (serials map[string]*types.Uint40, err error)

GetSerialsByIdsWithFilter gets serials with a filter function.

func (*B) GetSerialsByRange

func (b *B) GetSerialsByRange(idx database.Range) (serials types.Uint40s, err error)

GetSerialsByRange gets serials within a key range.

func (*B) GetSerialsFromFilter

func (b *B) GetSerialsFromFilter(f *filter.F) (sers types.Uint40s, err error)

GetSerialsFromFilter returns serials matching a filter.

func (*B) GetSubscription

func (b *B) GetSubscription(pubkey []byte) (*database.Subscription, error)

GetSubscription gets a user's subscription.

func (*B) HasMarker

func (b *B) HasMarker(key string) bool

HasMarker checks if a marker exists.

func (*B) Import

func (b *B) Import(rr io.Reader)

Import imports events from a reader (legacy interface).

func (*B) ImportEventsFromReader

func (b *B) ImportEventsFromReader(ctx context.Context, rr io.Reader) error

ImportEventsFromReader imports events from an io.Reader containing JSONL data

func (*B) ImportEventsFromStrings

func (b *B) ImportEventsFromStrings(ctx context.Context, eventJSONs []string, policyManager interface {
	CheckPolicy(action string, ev *event.E, pubkey []byte, remote string) (bool, error)
}) error

ImportEventsFromStrings imports events from a slice of JSON strings with policy filtering

func (*B) Init

func (b *B) Init(path string) error

Init initializes the database with the given path.

func (*B) InvalidateQueryCache

func (b *B) InvalidateQueryCache()

InvalidateQueryCache invalidates the query cache (stub - no caching in bbolt).

func (*B) IsFirstTimeUser

func (b *B) IsFirstTimeUser(pubkey []byte) (bool, error)

IsFirstTimeUser checks if this is a first-time user.

func (*B) IsNIP43Member

func (b *B) IsNIP43Member(pubkey []byte) (isMember bool, err error)

IsNIP43Member checks if pubkey is a NIP-43 member.

func (*B) IsSubscriptionActive

func (b *B) IsSubscriptionActive(pubkey []byte) (bool, error)

IsSubscriptionActive checks if a subscription is active.

func (*B) Path

func (b *B) Path() string

Path returns the path where the database files are stored.

func (*B) ProcessDelete

func (b *B) ProcessDelete(ev *event.E, admins [][]byte) error

ProcessDelete processes a deletion event. For migration from other backends, deletions have already been processed, so this is a no-op. Full implementation needed for production use.

func (*B) PublishNIP43MembershipEvent

func (b *B) PublishNIP43MembershipEvent(kind int, pubkey []byte) error

PublishNIP43MembershipEvent publishes a NIP-43 membership event.

func (*B) QueryAllVersions

func (b *B) QueryAllVersions(c context.Context, f *filter.F) (evs event.S, err error)

QueryAllVersions queries all versions of events matching a filter.

func (*B) QueryDeleteEventsByTargetId

func (b *B) QueryDeleteEventsByTargetId(c context.Context, targetEventId []byte) (evs event.S, err error)

QueryDeleteEventsByTargetId queries delete events targeting a specific event.

func (*B) QueryEvents

func (b *B) QueryEvents(c context.Context, f *filter.F) (evs event.S, err error)

QueryEvents queries events matching a filter.

func (*B) QueryEventsWithOptions

func (b *B) QueryEventsWithOptions(c context.Context, f *filter.F, includeDeleteEvents bool, showAllVersions bool) (evs event.S, err error)

QueryEventsWithOptions queries events with additional options.

func (*B) QueryForIds

func (b *B) QueryForIds(c context.Context, f *filter.F) (idPkTs []*store.IdPkTs, err error)

QueryForIds queries and returns event ID/pubkey/timestamp tuples.

func (*B) QueryForSerials

func (b *B) QueryForSerials(c context.Context, f *filter.F) (serials types.Uint40s, err error)

QueryForSerials queries and returns only the serials.

func (*B) Ready

func (b *B) Ready() <-chan struct{}

Ready returns a channel that closes when the database is ready to serve requests.

func (*B) RecordEventAccess

func (b *B) RecordEventAccess(serial uint64, connectionID string) error

RecordEventAccess records an event access.

func (*B) RecordPayment

func (b *B) RecordPayment(pubkey []byte, amount int64, invoice, preimage string) error

RecordPayment records a payment.

func (*B) RemoveNIP43Member

func (b *B) RemoveNIP43Member(pubkey []byte) error

RemoveNIP43Member removes a NIP-43 member.

func (*B) RunMigrations

func (b *B) RunMigrations()

RunMigrations runs database migrations.

func (*B) SaveEvent

func (b *B) SaveEvent(c context.Context, ev *event.E) (replaced bool, err error)

SaveEvent saves an event to the database using the write batcher.

func (*B) SaveEventForImport added in v0.48.8

func (b *B) SaveEventForImport(ev *event.E) error

SaveEventForImport saves an event optimized for bulk import. It skips duplicate checking, deletion checking, and graph vertex creation to maximize import throughput. Use only for trusted data migration.

func (*B) SaveEventMinimal added in v0.48.8

func (b *B) SaveEventMinimal(ev *event.E) error

SaveEventMinimal stores only the essential event data for fast bulk import. It skips all indexes - call BuildIndexes after import completes.

func (*B) SetLogLevel

func (b *B) SetLogLevel(level string)

SetLogLevel changes the logging level.

func (*B) SetMarker

func (b *B) SetMarker(key string, value []byte) error

SetMarker sets a metadata marker.

func (*B) SetRelayIdentitySecret

func (b *B) SetRelayIdentitySecret(skb []byte) error

SetRelayIdentitySecret sets the relay's identity secret key.

func (*B) StoreInviteCode

func (b *B) StoreInviteCode(code string, expiresAt time.Time) error

StoreInviteCode stores an invite code.

func (*B) Sync

func (b *B) Sync() error

Sync flushes the database buffers to disk.

func (*B) ValidateInviteCode

func (b *B) ValidateInviteCode(code string) (valid bool, err error)

ValidateInviteCode validates an invite code.

func (*B) Wipe

func (b *B) Wipe() error

Wipe deletes all data in the database.

func (*B) WouldReplaceEvent

func (b *B) WouldReplaceEvent(ev *event.E) (bool, types.Uint40s, error)

WouldReplaceEvent checks if the event would replace existing events.

type BatchedWrite

type BatchedWrite struct {
	BucketName []byte
	Key        []byte
	Value      []byte
	IsDelete   bool
}

BatchedWrite represents a single write operation

type BatcherStats

type BatcherStats struct {
	TotalBatches      uint64
	TotalEvents       uint64
	TotalBytes        uint64
	AverageLatencyMs  float64
	LastFlushTime     time.Time
	LastFlushDuration time.Duration
}

BatcherStats contains batcher statistics

type BboltConfig

type BboltConfig struct {
	DataDir  string
	LogLevel string

	// Batch settings (tuned for 7200rpm HDD)
	BatchMaxEvents    int           // Max events before flush (default: 5000)
	BatchMaxBytes     int64         // Max bytes before flush (default: 128MB)
	BatchFlushTimeout time.Duration // Max time before flush (default: 30s)

	// Bloom filter settings
	BloomSizeMB int // Bloom filter size in MB (default: 16)

	// BBolt settings
	NoSync          bool // Disable fsync for performance (DANGEROUS)
	InitialMmapSize int  // Initial mmap size in bytes
}

BboltConfig holds bbolt-specific configuration

type BloomStats

type BloomStats struct {
	ApproxCount uint64 // Approximate number of elements
	Cap         uint   // Capacity in bits
}

BloomStats contains bloom filter statistics.

type EdgeBloomFilter

type EdgeBloomFilter struct {
	// contains filtered or unexported fields
}

EdgeBloomFilter provides fast negative lookups for edge existence checks. Uses a bloom filter to avoid disk seeks when checking if an edge exists.

func NewEdgeBloomFilter

func NewEdgeBloomFilter(sizeMB int, db *bolt.DB) (*EdgeBloomFilter, error)

NewEdgeBloomFilter creates or loads the edge bloom filter. sizeMB is the approximate size in megabytes. With 1% false positive rate, 16MB can hold ~10 million edges.

func (*EdgeBloomFilter) Add

func (ebf *EdgeBloomFilter) Add(srcSerial, dstSerial uint64, edgeType byte)

Add adds an edge to the bloom filter. An edge is represented by source and destination serials plus edge type.

func (*EdgeBloomFilter) AddBatch

func (ebf *EdgeBloomFilter) AddBatch(edges []EdgeKey)

AddBatch adds multiple edges to the bloom filter.

func (*EdgeBloomFilter) MayExist

func (ebf *EdgeBloomFilter) MayExist(srcSerial, dstSerial uint64, edgeType byte) bool

MayExist checks if an edge might exist. Returns false if definitely doesn't exist (no disk access needed). Returns true if might exist (need to check disk to confirm).

func (*EdgeBloomFilter) Persist

func (ebf *EdgeBloomFilter) Persist(db *bolt.DB) error

Persist saves the bloom filter to the database.

func (*EdgeBloomFilter) Reset

func (ebf *EdgeBloomFilter) Reset()

Reset clears the bloom filter.

func (*EdgeBloomFilter) Stats

func (ebf *EdgeBloomFilter) Stats() BloomStats

Stats returns bloom filter statistics.

type EdgeKey

type EdgeKey struct {
	SrcSerial uint64
	DstSerial uint64
	EdgeType  byte
}

EdgeKey represents an edge for batch operations.

type EventBatch

type EventBatch struct {
	Serial         uint64
	EventData      []byte                // Serialized compact event data
	Indexes        []BatchedWrite        // Index entries
	EventVertex    *EventVertex          // Graph vertex for this event
	PubkeyUpdate   *PubkeyVertexUpdate   // Update to author's pubkey vertex
	MentionUpdates []*PubkeyVertexUpdate // Updates to mentioned pubkeys
	EdgeKeys       []EdgeKey             // Edge keys for bloom filter
}

EventBatch represents a complete event with all its indexes and graph updates

type EventVertex

type EventVertex struct {
	AuthorSerial uint64   // Serial of the author pubkey
	Kind         uint16   // Event kind
	PTagSerials  []uint64 // Serials of pubkeys mentioned (p-tags)
	ETagSerials  []uint64 // Serials of events referenced (e-tags)
}

EventVertex stores the adjacency list for an event. Contains the author and all edges to other events/pubkeys.

func (*EventVertex) Decode

func (ev *EventVertex) Decode(data []byte) error

Decode deserializes bytes into an EventVertex.

func (*EventVertex) Encode

func (ev *EventVertex) Encode() []byte

Encode serializes the EventVertex to bytes. Format: author(5) | kind(2) | ptag_count(varint) | [ptag_serials(5)...] | etag_count(varint) | [etag_serials(5)...]

type Logger

type Logger struct {
	Level atomic.Int32
	Label string
}

Logger wraps the lol logger for BBolt

func NewLogger

func NewLogger(level int, dataDir string) *Logger

NewLogger creates a new Logger instance

func (*Logger) Debugf

func (l *Logger) Debugf(format string, args ...interface{})

Debugf logs a debug message

func (*Logger) Errorf

func (l *Logger) Errorf(format string, args ...interface{})

Errorf logs an error message

func (*Logger) Infof

func (l *Logger) Infof(format string, args ...interface{})

Infof logs an info message

func (*Logger) SetLogLevel

func (l *Logger) SetLogLevel(level int)

SetLogLevel updates the log level

func (*Logger) Tracef

func (l *Logger) Tracef(format string, args ...interface{})

Tracef logs a trace message

func (*Logger) Warningf

func (l *Logger) Warningf(format string, args ...interface{})

Warningf logs a warning message

type PubkeyVertex

type PubkeyVertex struct {
	AuthoredEvents []uint64 // Event serials this pubkey authored
	MentionedIn    []uint64 // Event serials that mention this pubkey (p-tags)
}

PubkeyVertex stores the adjacency list for a pubkey. Contains all events authored by or mentioning this pubkey.

func (*PubkeyVertex) AddAuthored

func (pv *PubkeyVertex) AddAuthored(eventSerial uint64)

AddAuthored adds an event serial to the authored list if not already present.

func (*PubkeyVertex) AddMention

func (pv *PubkeyVertex) AddMention(eventSerial uint64)

AddMention adds an event serial to the mentioned list if not already present.

func (*PubkeyVertex) Decode

func (pv *PubkeyVertex) Decode(data []byte) error

Decode deserializes bytes into a PubkeyVertex.

func (*PubkeyVertex) Encode

func (pv *PubkeyVertex) Encode() []byte

Encode serializes the PubkeyVertex to bytes. Format: authored_count(varint) | [serials(5)...] | mentioned_count(varint) | [serials(5)...]

func (*PubkeyVertex) HasAuthored

func (pv *PubkeyVertex) HasAuthored(eventSerial uint64) bool

HasAuthored checks if the pubkey authored the given event.

func (*PubkeyVertex) IsMentionedIn

func (pv *PubkeyVertex) IsMentionedIn(eventSerial uint64) bool

IsMentionedIn checks if the pubkey is mentioned in the given event.

func (*PubkeyVertex) RemoveAuthored

func (pv *PubkeyVertex) RemoveAuthored(eventSerial uint64)

RemoveAuthored removes an event serial from the authored list.

func (*PubkeyVertex) RemoveMention

func (pv *PubkeyVertex) RemoveMention(eventSerial uint64)

RemoveMention removes an event serial from the mentioned list.

type PubkeyVertexUpdate

type PubkeyVertexUpdate struct {
	PubkeySerial uint64
	AddAuthored  uint64 // Event serial to add to authored (0 if none)
	AddMention   uint64 // Event serial to add to mentions (0 if none)
}

PubkeyVertexUpdate represents an update to a pubkey's vertex

type WriteBatcher

type WriteBatcher struct {
	// contains filtered or unexported fields
}

WriteBatcher accumulates writes and flushes them in batches. Optimized for HDD with large batches and periodic flushes.

func NewWriteBatcher

func NewWriteBatcher(db *bolt.DB, bloom *EdgeBloomFilter, cfg *BboltConfig, logger *Logger) *WriteBatcher

NewWriteBatcher creates a new write batcher

func (*WriteBatcher) Add

func (wb *WriteBatcher) Add(batch *EventBatch) error

Add adds an event batch to the pending writes

func (*WriteBatcher) Flush

func (wb *WriteBatcher) Flush() error

Flush writes all pending batches to BBolt

func (*WriteBatcher) PendingCount

func (wb *WriteBatcher) PendingCount() int

PendingCount returns the number of pending events

func (*WriteBatcher) Shutdown

func (wb *WriteBatcher) Shutdown() error

Shutdown gracefully shuts down the batcher

func (*WriteBatcher) Stats

func (wb *WriteBatcher) Stats() BatcherStats

Stats returns current batcher statistics

Source Files

  • batcher.go
  • bbolt.go
  • bloom.go
  • fetch-event.go
  • get-serial-by-id.go
  • graph.go
  • helpers.go
  • identity.go
  • import-export.go
  • import-minimal.go
  • init.go
  • logger.go
  • markers.go
  • query-graph.go
  • save-event-bulk.go
  • save-event.go
  • serial.go
  • stubs.go

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL