wildcat

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: May 20, 2025 License: MPL-2.0 Imports: 17 Imported by: 0

README

Wildcat is a high-performance embedded key-value database or you can also call it a storage engine written in Go. It incorporates modern database design principles including LSM tree architecture, MVCC (Multi-Version Concurrency Control), and automatic background operations to deliver excellent read/write performance with strong consistency guarantees.

Features

  • LSMT(log-structure-merged-tree) architecture optimized for high write throughput
  • Minimal to no blocking concurrency for readers and writers
  • Per-transaction WAL logging with recovery and rehydration
  • Version-aware skip list for fast in-memory MVCC access
  • Atomic write path, safe for multithreaded use
  • Scalable design with background flusher and compactor
  • Durable and concurrent block storage
  • Atomic LRU for active block manager handles
  • Memtable lifecycle management and snapshot durability
  • Configurable Sync options, None, Partial (w/ background interval), Full
  • MVCC with snapshot isolation (with read timestamp)
  • WALs ensure durability and crash recovery of state, including active transactions
  • Automatic multi-threaded background compaction that maintains optimal performance over time
  • Full ACID transaction support
  • Bidirectional iteration for efficient data scanning
  • Can sustain write throughput at 100K+ txns/sec
  • Handles hundreds of thousands of concurrent read ops/sec with default settings
  • Bloom filter per sstable for fast key lookups

Basic Usage

Opening a Database

// Create default options
opts := &wildcat.Options{
Directory: "/path/to/database",
    // Use defaults for other settings
}

// Open or create the database
db, err := wildcat.Open(opts)
if err != nil {
    log.Fatalf("Failed to open database: %v", err)
}
defer db.Close()

Simple Key-Value Operations

The easiest way to interact with Wildcat is through the Update method, which handles transactions automatically.

// Write a value
err := db.Update(func(txn *wildcat.Txn) error {
    return txn.Put([]byte("hello"), []byte("world"))
})
if err != nil {
    log.Fatalf("Failed to write: %v", err)
}

// Read a value
var result []byte
err = db.Update(func(txn *wildcat.Txn) error {
    var err error
    result, err = txn.Get([]byte("hello"))
    return err
})

if err != nil {
    log.Fatalf("Failed to read: %v", err)
} else {
    fmt.Println("Value:", string(result)) // Outputs: Value: world
}

Manual Transaction Management

For more complex operations, you can manually manage transactions.

// Begin a transaction
txn := db.Begin()

// Perform operations
err := txn.Put([]byte("key1"), []byte("value1"))
if err != nil {
    txn.Rollback()
    log.Fatal(err)
}

value, err := txn.Get([]byte("key1"))
if err != nil {
    txn.Rollback()
    log.Fatal(err)
}

// Commit or rollback
err = txn.Commit()
if err != nil {
    txn.Rollback()
    log.Fatal(err)
}

Batch Operations

You can perform multiple operations in a single transaction.

err := db.Update(func(txn *wildcat.Txn) error {
    // Write multiple key-value pairs
    for i := 0; i < 1000; i++ {
        key := []byte(fmt.Sprintf("key%d", i))
        value := []byte(fmt.Sprintf("value%d", i))
        if err := txn.Put(key, value); err != nil {
            return err
        }
    }
    return nil
})
if err != nil {
    log.Fatalf("Failed to write batch: %v", err)
}

Log Channel

Wildcat provides a log channel for real-time logging. You can set up a goroutine to listen for log messages.

// Create a log channel
logChannel := make(chan string, 100) // Buffer size of 100 messages

// Set up options with the log channel
opts := &wildcat.Options{
    Directory:       "/path/to/db",
    LogChannel:      logChannel,
    // Other options...
}

// Open the database
db, err := wildcat.Open(opts)
if err != nil {
    // Handle error
}

wg := &sync.WaitGroup{}

wg.Add(1)

// Start a goroutine to listen to the log channel
go func() {
    defer wg.Done()
    for msg := range logChannel {
        // Process log messages
        fmt.Println("wildcat:", msg)

        // You could also write to a file, send to a logging service, etc.
        // log.Println(msg)
    }
}()

// Use..

wg.Wait() // Wait for the goroutine to finish

// When you're done, close the database
defer db.Close()

Iterating Keys

Wildcat supports bidirectional, MVCC-consistent iteration within a transaction using txn.NewIterator(startKey, prefix).

// Begin a transaction
txn := db.Begin()

// Create an iterator from a given start key (or nil to begin from the start)
iter := txn.NewIterator(nil, nil)

// Forward iteration
fmt.Println("Forward scan:")
for {
    key, val, ts, ok := iter.Next()
    if !ok {
        break
    }
    fmt.Printf("Key=%s Value=%s Timestamp=%d\n", key, val, ts)
}

// Reverse iteration
fmt.Println("Reverse scan:")
for {
    key, val, ts, ok := iter.Prev()
    if !ok {
        break
    }
    fmt.Printf("Key=%s Value=%s Timestamp=%d\n", key, val, ts)
}

OR

err := db.Update(func(txn *wildcat.Txn) error {
    iter := txn.NewIterator(nil, nil) // Full forward scan

    fmt.Println("Keys in snapshot:")
    for {
        key, value, ts, ok := iter.Next()
        if !ok {
            break
        }
        fmt.Printf("Key=%s Value=%s Timestamp=%d\n", key, value, ts)
    }

    return nil
})
if err != nil {
    log.Fatalf("Iteration failed: %v", err)
}

Read-Only Transactions with View

Wildcat provides a View method specifically designed for read-only operations. This is more efficient than using Update when you only need to read data.

// Read a value with View
var result []byte
err = db.View(func(txn *wildcat.Txn) error {
    var err error
    result, err = txn.Get([]byte("hello"))
    return err
})

if err != nil {
    log.Fatalf("Failed to read: %v", err)
} else {
    fmt.Println("Value:", string(result)) // Outputs: Value: world
}

Iterate through keys in read-only mode

err := db.View(func(txn *wildcat.Txn) error {
    iter := txn.NewIterator(nil, nil)

    for {
        key, value, ts, ok := iter.Next()
        if !ok {
            break
        }
        fmt.Printf("Key=%s Value=%s Timestamp=%d\n", key, value, ts)
    }

    return nil
})
if err != nil {
    log.Fatalf("Iteration failed: %v", err)
}

Advanced Configuration

Wildcat provides several configuration options for fine-tuning.

opts := &wildcat.Options{
    Directory:           "/path/to/database",
    WriteBufferSize:     32 * 1024 * 1024,        // 32MB memtable size
    SyncOption:          wildcat.SyncFull,        // Full sync for maximum durability
    SyncInterval:        128 * time.Millisecond,  // Only set when using SyncPartial, can be 0 otherwise
    LevelCount:          7,                       // Number of LSM levels
    LevelMultiplier:     10,                      // Size multiplier between levels
    BlockManagerLRUSize: 256,                     // Cache size for block managers
    BlockSetSize:        8 * 1024 * 1024,         // 8MB block set size, each klog block will have BlockSetSize of entries
    LogChannel:          make(chan string, 1000), // Channel for real time logging
    BloomFilter:         false,                   // Enable/disable sstable bloom filters
}

Configuration Options Explained

  1. Directory The path where the database files will be stored
  2. WriteBufferSize Size threshold for memtable before flushing to disk
  3. SyncOption Controls durability vs performance tradeoff:
    • SyncNone Fastest, but no durability guarantees
    • SyncPartial Balances performance and durability
    • SyncFull Maximum durability, slower performance
  4. SyncInterval Time between background sync operations
  5. LevelCount Number of levels in the LSM tree
  6. LevelMultiplier Size ratio between adjacent levels
  7. BlockManagerLRUSize Number of block managers to cache
  8. BlockSetSize Size of SSTable klog block sets
  9. LogChannel Channel for real-time logging, useful for debugging and monitoring
  10. BloomFilter Enable or disable bloom filters for SSTables to speed up key lookups

Implementation Details

Data Storage Architecture

Wildcat uses a multi-level Log-Structured Merge (LSM) tree architecture.

  • Memtable In-memory skiplist for recent writes (L0)
  • Immutable Memtables Memtables awaiting flush to disk
  • SSTables On-disk sorted string tables organized into levels
  • Write-Ahead Log (WAL) Ensures durability of in-memory data

Compaction Strategy

Wildcat employs a hybrid compaction strategy:

  • Size-Tiered Compaction (L1–L2) Merges similarly sized SSTables to reduce write amplification and flush pressure. This strategy groups SSTables of comparable size and merges them into the next level. Ideal for absorbing high write throughput efficiently.
  • Leveled Compaction (L3 and above) Maintains disjoint key ranges within each level to optimize lookup performance. Overlapping SSTables from the lower level are merged into the appropriate ranges in the higher level, minimizing read amplification and ensuring predictable query cost.

Compaction is triggered when

  1. A level size exceeds capacity
  2. Number of sstables exceeds threshold (CompactionSizeThreshold)
  3. Compaction score exceeds 1.0 score = sizeScore * WeightSize + countScore * WeightCount default:
    • CompactionScoreSizeWeight = 0.7
    • CompactionScoreCountWeight = 0.3
  4. Cooldown period passed (CompactionCooldownPeriod)

MVCC and Transactions

Wildcat uses a snapshot-isolated MVCC (Multi-Version Concurrency Control) model to provide full ACID-compliant transactions with zero reader/writer blocking and high concurrency.

Each transaction is assigned a unique, monotonic timestamp at creation time. This timestamp determines the visibility window of data for the entire duration of the transaction.

MVCC Model

  • Each key stores a chain of timestamped versions (latest → oldest).
  • Reads only see the latest version ≤ transaction timestamp.
  • Writes are buffered in a per-transaction WriteSet and flushed atomically on commit.
  • Deletions are tracked via timestamped tombstones in a DeleteSet.

Snapshot Isolation

  • All reads during a transaction reflect a consistent snapshot of the database as of the transaction's start time.
  • Writers can proceed concurrently without locking; they do not overwrite but create a new version.

WAL Durability

  • Transactions are logged to a write-ahead log (WAL) before commit, capturing the full WriteSet, DeleteSet, and ReadSet.
  • WALs are replayed on crash to restore in-flight or recently committed transactions.

Conflict Handling

Wildcat uses an optimistic concurrency model.

  • Write-write conflicts are resolved by timestamp ordering - later transactions take precedence.
  • No explicit read/write conflict detection.

Documentation

Overview

Package wildcat

(C) Copyright Alex Gaetano Padula

Licensed under the Mozilla Public License, v. 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

https://www.mozilla.org/en-US/MPL/2.0/

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Package wildcat

(C) Copyright Alex Gaetano Padula

Licensed under the Mozilla Public License, v. 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

https://www.mozilla.org/en-US/MPL/2.0/

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Package wildcat

(C) Copyright Alex Gaetano Padula

Licensed under the Mozilla Public License, v. 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

https://www.mozilla.org/en-US/MPL/2.0/

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Package wildcat

(C) Copyright Alex Gaetano Padula

Licensed under the Mozilla Public License, v. 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

https://www.mozilla.org/en-US/MPL/2.0/

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Package wildcat

(C) Copyright Alex Gaetano Padula

Licensed under the Mozilla Public License, v. 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

https://www.mozilla.org/en-US/MPL/2.0/

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Package wildcat

(C) Copyright Alex Gaetano Padula

Licensed under the Mozilla Public License, v. 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

https://www.mozilla.org/en-US/MPL/2.0/

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Package wildcat

(C) Copyright Alex Gaetano Padula

Licensed under the Mozilla Public License, v. 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

https://www.mozilla.org/en-US/MPL/2.0/

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Package wildcat

(C) Copyright Alex Gaetano Padula

Licensed under the Mozilla Public License, v. 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

https://www.mozilla.org/en-US/MPL/2.0/

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Package wildcat

(C) Copyright Alex Gaetano Padula

Licensed under the Mozilla Public License, v. 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

https://www.mozilla.org/en-US/MPL/2.0/

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Package wildcat

(C) Copyright Alex Gaetano Padula

Licensed under the Mozilla Public License, v. 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

https://www.mozilla.org/en-US/MPL/2.0/

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Package wildcat

(C) Copyright Alex Gaetano Padula

Licensed under the Mozilla Public License, v. 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

https://www.mozilla.org/en-US/MPL/2.0/

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Index

Constants

View Source
const (
	CompactionCooldownPeriod   = 1 * time.Second
	CompactionBatchSize        = 4   // Max number of SSTables to compact at once
	CompactionSizeRatio        = 1.2 // Level size ratio that triggers compaction
	CompactionSizeThreshold    = 4   // Number of files to trigger size-tiered compaction
	CompactionScoreSizeWeight  = 0.7 // Weight for size-based score
	CompactionScoreCountWeight = 0.3 // Weight for count-based score
	MaxCompactionConcurrency   = 2   // Maximum concurrent compactions
)

Constants for compaction policy

View Source
const (
	FlusherTickerInterval   = 64 * time.Microsecond // Interval for flusher ticker
	CompactorTickerInterval = 64 * time.Millisecond // Interval for compactor ticker
)

Constants for background operations

View Source
const (
	SSTablePrefix                = "sst_"     // Prefix for SSTable files
	LevelPrefix                  = "l"        // Prefix for level directories i.e. "l0", "l1", etc.
	WALFileExtension             = ".wal"     // Extension for Write Ahead Log files <timestamp>.wal
	KLogExtension                = ".klog"    // Extension for KLog files
	VLogExtension                = ".vlog"    // Extension for VLog files
	IDGSTFileName                = "idgstate" // Filename for ID generator state
	BloomFilterFalsePositiveRate = 0.01       // False positive rate for Bloom filter
)
View Source
const (
	DefaultWriteBufferSize     = 128 * 1024 * 1024
	DefaultSyncOption          = SyncNone
	DefaultSyncInterval        = 16 * time.Nanosecond
	DefaultLevelCount          = 7
	DefaultLevelMultiplier     = 4
	DefaultBlockManagerLRUSize = 128              // Size of the LRU cache for block managers
	DefaultBlockSetSize        = 64 * 1024 * 1024 // Size of the block set
	DefaultPermission          = 0750
)

Defaults

Variables

This section is empty.

Functions

This section is empty.

Types

type BlockSet

type BlockSet struct {
	Entries []*KLogEntry // List of entries in the block
	Size    int64        // Size of the block set
}

BlockSet is a specific block with a set of klog entries

type Compactor

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

Compactor is responsible for managing compaction jobs

type DB

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

DB represents the main Wildcat structure

func Open

func Open(opts *Options) (*DB, error)

Open initializes a new Wildcat instance with the provided options

func (*DB) Begin

func (db *DB) Begin() *Txn

Begin starts a new transaction

func (*DB) Close

func (db *DB) Close() error

Close closes the database and all open resources

func (*DB) ForceFlush

func (db *DB) ForceFlush() error

ForceFlush forces the flush of all memtables and immutable memtables

func (*DB) GetTxn

func (db *DB) GetTxn(id int64) (*Txn, error)

GetTxn retrieves a transaction by ID

func (*DB) Update

func (db *DB) Update(fn func(txn *Txn) error) error

Update performs an atomic update using a transaction

func (*DB) View

func (db *DB) View(fn func(txn *Txn) error) error

View performs a read-only transaction

type Flusher

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

Flusher is responsible for queuing and flushing memtables to disk

type IDGenerator

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

IDGenerator is a thread-safe ID generator

type IDGeneratorState

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

IDGeneratorState represents the state of the ID generator. When system shuts down the state is saved to disk and restored on next startup.

type IteratorItem

type IteratorItem struct {
	Key       []byte
	Value     []byte
	Timestamp int64
	Iter      IteratorWithPeek
}

IteratorItem represents an item in the merge iterator heap

type IteratorWithPeek

type IteratorWithPeek interface {
	Next() ([]byte, []byte, int64, bool)
	Prev() ([]byte, []byte, int64, bool)
	Peek() ([]byte, []byte, int64, bool)
}

IteratorWithPeek is an interface for iterators that support peeking

type KLogEntry

type KLogEntry struct {
	Key          []byte // Key of the entry
	Timestamp    int64  // Timestamp of the entry
	ValueBlockID int64  // Block ID of the value
}

KLogEntry represents a key-value entry in the KLog

type Level

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

Level is a disk level within Wildcat, which contains a list of immutable SSTables

type Memtable

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

Memtable is a memory table structure

type MergeIterator

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

MergeIterator combines results from multiple sources (memtables, immutable memtables, SSTables) in a sorted order based on key and timestamp

func NewMergeIterator

func NewMergeIterator(txn *Txn, startKey []byte, prefix []byte) *MergeIterator

NewMergeIterator creates a new merge iterator that combines multiple iterators

func (*MergeIterator) Next

func (mi *MergeIterator) Next() ([]byte, []byte, int64, bool)

Next returns the next key-value pair from the merged iterators

func (*MergeIterator) Prev

func (mi *MergeIterator) Prev() ([]byte, []byte, int64, bool)

Prev moves the iterator backward

type Options

type Options struct {
	Directory           string        // Directory for Wildcat
	WriteBufferSize     int64         // Size of the write buffer
	SyncOption          SyncOption    // Sync option for write operations
	SyncInterval        time.Duration // Interval for syncing the write buffer
	LevelCount          int           // Number of levels in the LSM tree
	LevelMultiplier     int           // Multiplier for the number of levels
	BlockManagerLRUSize int           // Size of the LRU cache for block managers
	BlockSetSize        int64         // Amount of entries per klog block (in bytes)
	Permission          os.FileMode   // Permission for created files
	LogChannel          chan string   // Channel for logging
	BloomFilter         bool          // Enable Bloom filter for SSTables
}

Options represents the configuration options for Wildcat

type SSTable

type SSTable struct {
	Id          int64                    // SStable ID
	Min         []byte                   // The minimum key in the SSTable
	Max         []byte                   // The maximum key in the SSTable
	Size        int64                    // The size of the SSTable in bytes
	EntryCount  int                      // The number of entries in the SSTable
	Level       int                      // The level of the SSTable
	BloomFilter *bloomfilter.BloomFilter // Optional bloom filter for fast lookups
	// contains filtered or unexported fields
}

SSTable represents a sorted string table

type SSTableIterator

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

SSTableIterator is an iterator for the SSTable

func (*SSTableIterator) Seek

func (it *SSTableIterator) Seek(key []byte) error

type SSTableIteratorWrapper

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

SSTableIteratorWrapper adapts SSTableIterator to the IteratorWithPeek interface

func (*SSTableIteratorWrapper) Next

func (s *SSTableIteratorWrapper) Next() ([]byte, []byte, int64, bool)

Next advances the iterator and returns the next key-value pair

func (*SSTableIteratorWrapper) Peek

func (s *SSTableIteratorWrapper) Peek() ([]byte, []byte, int64, bool)

Peek returns the current key-value pair without advancing

func (*SSTableIteratorWrapper) Prev

func (s *SSTableIteratorWrapper) Prev() ([]byte, []byte, int64, bool)

Prev moves to the previous key-value pair

type SyncOption

type SyncOption int
const (
	SyncNone SyncOption = iota
	SyncFull
	SyncPartial
)

type Txn

type Txn struct {
	Id        int64             // The transactions id, can be recovered
	ReadSet   map[string]int64  // Key -> Timestamp
	WriteSet  map[string][]byte // Key -> Value
	DeleteSet map[string]bool   // Key -> Deleted
	Timestamp int64             // The timestamp of the transaction
	Committed bool              // Whether the transaction is committed
	// contains filtered or unexported fields
}

Txn represents a transaction in Wildcat

func (*Txn) Commit

func (txn *Txn) Commit() error

Commit commits the transaction

func (*Txn) Delete

func (txn *Txn) Delete(key []byte) error

Delete removes a key from database

func (*Txn) Get

func (txn *Txn) Get(key []byte) ([]byte, error)

Get retrieves a value by key

func (*Txn) NewIterator

func (txn *Txn) NewIterator(startKey []byte, prefix []byte) *MergeIterator

NewIterator you can provide a startKey or a prefix to iterate over

func (*Txn) Put

func (txn *Txn) Put(key []byte, value []byte) error

Put adds key-value pair to database

func (*Txn) Rollback

func (txn *Txn) Rollback() error

Rollback rolls back the transaction

type WAL

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

WAL is a write-ahead log structure

Directories

Path Synopsis
Package blockmanager
Package blockmanager
Package bloomfilter
Package bloomfilter
Package lru
Package lru
Package queue
Package queue
Package skiplist
Package skiplist

Jump to

Keyboard shortcuts

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