doctrine / mongodb-odm

The Official PHP MongoDB ORM/ODM
https://www.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/
MIT License
1.09k stars 502 forks source link

Indices from sub classes always get re-created #2561

Open buffcode opened 10 months ago

buffcode commented 10 months ago

Bug Report

Q A
BC Break no
Version 2.5.3

Summary

Having an inheritance and defining indices on a subclass (eg. UniqueIndex) will always re-create the subclasses' indices.

Current behavior

Having an inheritance and defining indices on a subclass (eg. UniqueIndex) will result in the subclass indices to be dropped on every run of doctrine:mongodb:schema:update and re-created afterwards.

Notice that the index will exist after the command, so the collection looks okay, but the indices are effectively dropped and recreated. This is usually not such of a big problem but it put some our our databases down after dropping an index on a 0,5 TB collection and starting to recreate it for multiple hours.

How to reproduce

<?php

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

/**
 * @ODM\Document()
 * @ODM\InheritanceType("SINGLE_COLLECTION")
 * @ODM\DiscriminatorField("metadataType")
 */
abstract class Metadata
{
    /**
     * @ODM\Id()
     */
    protected ?string $id = null;

    public function getId(): ?string
    {
        return $this->id;
    }
}
<?php

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

/**
 * @ODM\Document()
 */
class PreAggregationMetadata extends Metadata
{
    /**
     * @ODM\UniqueIndex()
     * @ODM\Field(type="string")
     */
    private string $cacheKey;
}

For debugging purposes add the following code to \Doctrine\ODM\MongoDB\SchemaManager::updateDocumentIndexes, right before $collection->dropIndex(...):

dump('Dropping index ' . $mongoIndex['name'] . ' on ' . $documentName);

Run:

$ bin/console doctrine:mongodb:schema:update
Dropping index cacheKey on Metadata
Updated indexes for all classes
Updated validation for all classes

Expected behavior

No index drop

$ bin/console doctrine:mongodb:schema:update
Updated indexes for all classes
Updated validation for all classes

Workaround

All indices have to be defined on the root class.

malarzm commented 10 months ago

@buffcode thanks for the report! Would the same be true if Metadata was mapped as a ODM\MappedSuperclass instead of ODM\Document? That would be more correct from a configuration's point of view as Metadata can not be a document on its own

buffcode commented 10 months ago

@malarzm The effect seems to be the same. Having the parent being abstract without MappedSuperclass is used to be able to query all subclasses ("A mapped superclass cannot be a document and is not queryable."), while ODM automatically returns the correct type.

Nevertheless, while testing various combinations of abstract and MappedSuperclass I also had following error, resulting in no index at the end, presumably because the order of the index creation is not correct:

<?php

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

/**
 * @ODM\Document()
 * @ODM\InheritanceType("SINGLE_COLLECTION")
 * @ODM\DiscriminatorField("metadataType")
 */
class ClassA
{
    /**
     * @ODM\Id()
     */
    protected ?string $id = null;
}
<?php

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

/**
 * @ODM\Document()
 */
class ClassB extends ClassA
{
    /**
     * @ODM\Index()
     */
    protected ?string $foo = null;
}

Output:

$ console dev:doctrine:mongodb:schema:update -vv 2>&1 | grep 'Class'
07:28:02 INFO      [app] [MongoDB] Creating index on ClassA: foo_1
07:28:02 WARNING   [app] [MongoDB] Dropping index on ClassA: foo_1

Database view: image


IMHO the index comparison needs to take all subclasses into account and perform the index deletion/creation only once per collection. The subclasses can be skipped, as they have already been processed by the parent.

malarzm commented 10 months ago

Thanks for checking! You're right, we'll need to improve how indices are created. Most probably in the same vein as #2392 did. Mind taking a stab at it? :)