Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Most efficient type to use Uuids as identities in doctrine entities #253

Open
flaushi opened this issue Dec 19, 2018 · 8 comments
Open

Most efficient type to use Uuids as identities in doctrine entities #253

flaushi opened this issue Dec 19, 2018 · 8 comments
Labels

Comments

@flaushi
Copy link

flaushi commented Dec 19, 2018

Hi,
I have switched to Uuids as primary keys for my doctrine project. All entities have such a field:

/**
     * @var UuidInterface
     *
     * @ORM\Id
     * @ORM\Column(type="uuid", unique=true)
     */
    protected $id;

    public function __construct() {
        $this->id = Uuid::uuid4();
    }

Attached you find an profiler screenshot.
bildschirmfoto 2018-12-19 um 09 04 32

As one can see, almost 10% of the time are spent in the unserialize method of this lib. Would it maybe be more efficient to store as String? Like so:

/**
     * @var string
     *
     * @ORM\Id
     * @ORM\Column(type="string", length=64, unique=true)
     */
    protected $id;

    public function __construct() {
        $this->id = Uuid::uuid4()->toString();
    }

Please note, I want to keep my code and model compatible with Postgres and MySQL, which is the case currently. @Ocramius

@flaushi
Copy link
Author

flaushi commented Dec 19, 2018

I should add that I have enabled doctrine's second level cache (redis backend). This component caches database entities according to their identity in redis. I think the Uuid::de/serialisation is a big and unneccessary overhead here.
The question is at which side one should address it: Should I change my model, or maybe one should stop recommending my type of usage of Uuid at the doctrine side?

@martijnhartlief
Copy link

martijnhartlief commented Apr 15, 2020

I would use a prePersist listener and only generate an UUID right before you persist. Because every initiation of an entity would result in generation of a new UUID while you already have one generated persisted.

@flaushi
Copy link
Author

flaushi commented Apr 15, 2020

Because every initiation of an entity would result in generation of a new UUID

AFAIK, the constructor that I write into my entity class is not called during doctrine's hydration phase. https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/architecture.html#entities

@pounard
Copy link

pounard commented Apr 15, 2020

Most often I set the id property as being nullable, and lazy create the new UUID if not set in the getId() method, such as:

class Foo
{
    private ?UuidInterface $id = null;

    public function getId(): UuidInterface
    {
        return $this->id ?? ($this->id = Uuid::uuid4());
    }
}

Yet UUID creation is very, very slow I noticed (especially the fromString() method).

I'm not sure my method is the best, but it fits right in most our use cases, when hydrating our entities we have the entity from database, and when creating a new one, the new UUID is created application-side for sending to the database insert query transparently.

@flaushi
Copy link
Author

flaushi commented Apr 15, 2020

If we only use the uuid's for getting a long random identity of database entities, what is the advantage of having a uuid class instance at all? Wouldn't it be sufficient to just have the string (see my second proposal)?

@pounard
Copy link

pounard commented Apr 15, 2020

I would always recommend to use types as much as you can, this way to are sure that an invalid UUID never will reach your business code. And this is enforced by both the UUID validation when created, but also by PHP static typing whenever it exists.

Each time we have codified identifiers (with business meaning for our clients) we create immutable value objects capable of formating, validating, fetching business meaning etc... from it, and thus even when it's a simple string. It makes code much more robust, especially when you drag those values all over your code, repository methods that takes an id, business layer, etc... It globally makes the code self-documented and less error-prone.

@devrck
Copy link
Contributor

devrck commented May 1, 2020

Can these be of some help @flaushi ? #251, #63,

@flaushi
Copy link
Author

flaushi commented May 1, 2020

Thanks @devrck, but AFAIU these issues are about binary representations at the database side.

I was wondering about the doctrine side. If I declare my ids as recommended

     /**
     * @var UuidInterface
     *
     * @ORM\Id
     * @ORM\Column(type="uuid", unique=true)
     */

then doctrine hydrates a Uuid instance (although postgres will store the uuid as a string). This is overhead in my opinion, and just makes all equality comparisons more costly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants