MongoDB indexes are not free just because the schema is

The pitch for a document store is that you can add a field without a migration, and that part is true. It gets read as “no schema, no schema work”, which is where the trouble starts: a query with no index still reads every document in the collection, and nothing in the API hints that it did.

db.events.find({ tenant: "eu-1", type: "checkout" })
         .sort({ createdAt: -1 }).limit(50).explain()

{
    "cursor"       : "BasicCursor",
    "n"            : 50,
    "nscanned"     : 4180027,
    "scanAndOrder" : true,
    "millis"       : 3120
}

db.events.ensureIndex({ tenant: 1, type: 1, createdAt: -1 },
                      { background: true })

BasicCursor means no index was used, nscanned against n is how many documents were read to return fifty, and scanAndOrder means the sort happened in memory afterwards — which fails outright once the result set passes 32MB. The flexible schema makes none of that cheaper. Two costs come with fixing it. Indexes have to stay in RAM alongside the working set or the page faults give back more than the index saved, and field names are stored in full in every document, so key length is a real term in that budget. And on 2.4 the write lock is per database: building an index in the foreground blocks every write to that database until it finishes, so { background: true } is not optional on anything live. A compound index answers its prefixes only.