Advanced Features

Manage Indexes

Create and manage indexes for optimal query performance

Index Management

Indexes are essential for database performance. They allow MongoDB to efficiently locate documents without scanning every document in a collection.

Why Use Indexes?

Without an index:

Query: Find player by steamid
Process: Scan ALL documents → Find match → Return

With an index:

Query: Find player by steamid
Process: Look up in index → Jump to document → Return

Creating Indexes

Syntax

local indexName = collection:CreateIndex(keys, unique, name)

Parameters

  • keys (table): Fields to index with sort direction (1 = ascending, -1 = descending)
  • unique (boolean): Enforce unique values
  • name (string): Custom index name

Returns

  • string: Name of created index
  • nil: On failure

Index Types

Single Field Index

Index on one field:

-- Ascending index on steamid (unique)
local name = players:CreateIndex(
    { steamid = 1 },
    true,               -- unique
    "steamid_unique"
)
print("Created index:", name)

-- Descending index on level
players:CreateIndex(
    { level = -1 },
    false,
    "level_desc"
)

Compound Index

Index on multiple fields:

-- Index on class and level
players:CreateIndex(
    { class = 1, level = -1 },
    false,
    "class_level"
)

-- Useful for queries like:
players:Find({ class = "Warrior", level = { ["$gte"] = 10 } })

Text Index

For text search:

-- Create text index on username
players:CreateIndex(
    { username = "text" },
    false,
    "username_text"
)

-- Now you can search:
players:Find({
    ["$text"] = { ["$search"] = "player" }
})

Listing Indexes

Get all indexes on a collection:

local indexes = collection:ListIndexes()

if indexes then
    print("Indexes on collection:")
    for i, index in ipairs(indexes) do
        print(string.format("%d. %s", i, index.name or "unnamed"))

        -- Print keys
        if index.key then
            local keys = {}
            for field, direction in pairs(index.key) do
                table.insert(keys, field .. ":" .. tostring(direction))
            end
            print("   Keys: " .. table.concat(keys, ", "))
        end

        -- Show if unique
        if index.unique then
            print("   Unique: true")
        end
    end
end

Dropping Indexes

Drop Specific Index

local success = collection:DropIndex("index_name")

if success then
    print("✓ Index dropped")
else
    print("✗ Failed to drop index")
end

Drop All Indexes

-- Drop all indexes except _id
collection:DropIndex("*")

The _id index cannot be dropped and is always present.

Index Strategies

For Player Lookups

-- Most common query: find by SteamID
players:CreateIndex(
    { steamid = 1 },
    true,  -- Unique!
    "steamid_unique"
)

For Leaderboards

-- Sort by score descending
players:CreateIndex(
    { score = -1 },
    false,
    "score_desc"
)

-- Or compound for filtered leaderboards
players:CreateIndex(
    { banned = 1, score = -1 },
    false,
    "active_score"
)

For Filtering

-- Common filter fields
players:CreateIndex({ class = 1 }, false, "class_index")
players:CreateIndex({ level = 1 }, false, "level_index")
players:CreateIndex({ vip = 1 }, false, "vip_index")

-- Or compound for common query patterns
players:CreateIndex(
    { class = 1, level = 1, vip = 1 },
    false,
    "filter_compound"
)

For Time-Based Queries

-- Index on timestamps
players:CreateIndex(
    { last_login = -1 },
    false,
    "recent_login"
)

-- TTL index for auto-expiring documents
-- (Note: TTL indexes are managed by MongoDB server)

Best Practices

1. Index Query Patterns

Create indexes based on how you query:

-- If you query like this:
players:Find({ class = "Warrior", level = { ["$gte"] = 10 } })

-- Create this index:
players:CreateIndex({ class = 1, level = 1 }, false, "class_level")

2. Consider Index Direction

Direction matters for sorting:

-- For descending sorts (highest first)
players:CreateIndex({ score = -1 }, false, "score_desc")

-- Query uses the index efficiently
players:Find({}, { sort = { score = -1 } })

3. Compound Index Order

Put equality filters before range filters:

-- Good: Equality (class) before range (level)
{ class = 1, level = 1 }

-- For query:
{ class = "Warrior", level = { ["$gte"] = 10 } }

4. Don't Over-Index

Each index:

  • Uses disk space
  • Slows down writes
  • Needs maintenance

Only create indexes you need.

5. Use Unique for Identifiers

-- Prevent duplicate SteamIDs
players:CreateIndex({ steamid = 1 }, true, "steamid_unique")

-- Trying to insert duplicate will fail

Practical Examples

Setup for Player System

function SetupPlayerIndexes()
    local players = db:Collection("players")

    -- Primary lookup
    players:CreateIndex(
        { steamid = 1 },
        true,
        "steamid_unique"
    )

    -- Leaderboard
    players:CreateIndex(
        { score = -1 },
        false,
        "score_desc"
    )

    -- Active players
    players:CreateIndex(
        { last_login = -1 },
        false,
        "recent_login"
    )

    -- Filtering
    players:CreateIndex(
        { class = 1, level = 1 },
        false,
        "class_level"
    )

    print("✓ Player indexes created")
end

-- Call on server start
SetupPlayerIndexes()

Setup for Logs

function SetupLogIndexes()
    local logs = db:Collection("logs")

    -- Query by category
    logs:CreateIndex(
        { category = 1, timestamp = -1 },
        false,
        "category_time"
    )

    -- Query by player
    logs:CreateIndex(
        { steamid = 1, timestamp = -1 },
        false,
        "player_time"
    )

    print("✓ Log indexes created")
end

Index Management Utility

local IndexManager = {}

function IndexManager.EnsureIndexes(collection, indexes)
    local existing = collection:ListIndexes() or {}
    local existingNames = {}

    for _, idx in ipairs(existing) do
        existingNames[idx.name] = true
    end

    for _, index in ipairs(indexes) do
        if not existingNames[index.name] then
            local created = collection:CreateIndex(
                index.keys,
                index.unique or false,
                index.name
            )
            if created then
                print("Created index:", index.name)
            end
        end
    end
end

-- Usage
IndexManager.EnsureIndexes(players, {
    { name = "steamid_unique", keys = { steamid = 1 }, unique = true },
    { name = "score_desc", keys = { score = -1 } },
    { name = "class_level", keys = { class = 1, level = 1 } }
})

Monitoring Index Usage

Check if your queries use indexes:

-- List indexes to verify they exist
local indexes = collection:ListIndexes()
print("Current indexes:")
for _, index in ipairs(indexes) do
    print("  -", index.name)
end

-- Get collection stats
local stats = db:Stats("players")
if stats then
    print("Index size:", stats.totalIndexSize, "bytes")
end

Next Steps