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
Sometimes calling em.populate does not init a relation #3383
Comments
Well, using |
What would be the proper way to query data with mikro-orm in GraphQL then? For us the current logic is that |
I guess we could fork |
As a note the issue is specifically with
^ everything works as expected |
I will need to review this first, maybe its something stupid we can fix. I believe the read operations should be rather fine with Can't help with GQL, not my cup of tea. edit: But I know there is one extension you can use to generate nested populate definition for GQL query or something like that. Not sure how up to date is that or whether there are better solutions. https://github.com/driescroons/graphql-fields-to-relations Also be sure to check this discussion: |
We were looking at recreating something similar for ourselves, but I think that we still had issues:
|
I am not following, what issues? |
Let me put #3383 (comment) in production to verify again, but I think somehow even though on the top level we were pre-populating the fields children could not get access to them, so in my example |
Then are you really sure there is a value in the database? :] But it should be |
Can you update the repro so it creates some inital data too? And maybe run things multiple times to catch the error? |
I could imagine a race condition where the entity gets added to identity map before it is hydrated - but we need that, not much we can do here. And creating entity is sync, so its weird anyway. |
I am pretty sure that data is in the database :) I still cannot replicate it in the test case, I'll try to assemble a closer-resembling one with GraphQL API, fastify, etc... Is there any debug info that I could collect on production to help with resolving this? |
This is happening to me to. It's been happening for a while. I haven't reported it yet because I wasn't able to put together a reproduction for the bug, a didn't have time to do it anyway. By the way, this only started happening to me after moving from v4 to v5 (I don't remember the exact versions). Before that the same code worked well. I'll try to provide a way of reproducing this when I have time. |
Tried to reproduce based on the code in OP. Ran the final part 10000 times in a row, havent seen that error single time 🤷 Not much I can do without (at least sometimes) failing repro. Here is what I tried, maybe you can make it fail :] import { Collection, Entity, Enum, ManyToOne, MikroORM, OneToMany, OptionalProps, PrimaryKey, Property } from '@mikro-orm/core';
import type { SqliteDriver } from '@mikro-orm/sqlite';
import { randomUUID } from 'node:crypto';
abstract class BaseEntity {
@PrimaryKey({ length: 25, type: 'string' })
id = randomUUID();
@Property({
type: 'Date',
fieldName: 'createdAt',
})
createdAt = new Date();
@Property({
type: 'Date',
fieldName: 'updatedAt',
onUpdate: () => new Date(),
})
updatedAt = new Date();
}
@Entity({ collection: 'Trait' })
class Trait extends BaseEntity {
[OptionalProps]?: 'createdAt' | 'updatedAt';
@OneToMany({ entity: 'Translation', mappedBy: 'trait' })
translations = new Collection<Translation>(this);
@OneToMany({ entity: 'Translation', mappedBy: 'traitDescription' })
descriptions = new Collection<Translation>(this);
}
enum LOCALE {
DE = 'DE',
EN = 'EN',
FR = 'FR',
ES = 'ES',
PT = 'PT',
}
@Entity({ collection: 'Translation' })
class Translation extends BaseEntity {
[OptionalProps]?: 'createdAt' | 'updatedAt';
@Enum({ items: () => LOCALE, type: 'LOCALE' })
locale!: LOCALE;
@Property({ columnType: 'text', type: 'string' })
text!: string;
@ManyToOne({
entity: () => Trait,
fieldName: 'trait',
onDelete: 'cascade',
nullable: true,
})
trait?: Trait;
@ManyToOne({
entity: () => Trait,
fieldName: 'traitDescription',
onDelete: 'cascade',
nullable: true,
})
traitDescription?: Trait;
}
describe('GH issue 3383', () => {
let orm: MikroORM<SqliteDriver>;
beforeAll(async () => {
orm = await MikroORM.init({
entities: [Translation],
dbName: ':memory:',
type: 'sqlite',
});
await orm.getSchemaGenerator().createSchema();
});
afterAll(async () => {
await orm.close(true);
});
test(`GH issue 3383`, async () => {
const t = orm.em.create(Trait, {
descriptions: [
{ locale: LOCALE.EN, text: 'descr ENENENEN' },
{ locale: LOCALE.ES, text: 'descr ESESESES' },
{ locale: LOCALE.DE, text: 'descr DEDEDEDE' },
],
translations: [
{ locale: LOCALE.EN, text: 'trans ENENENEN' },
{ locale: LOCALE.ES, text: 'trans ESESESES' },
{ locale: LOCALE.DE, text: 'trans DEDEDEDE' },
],
});
await orm.em.persist(t).flush();
orm.em.clear();
const getTranslation = async (parent: Trait) => {
await orm.em.populate(parent, ['translations']);
const translation = parent.translations
.getItems()
.find(({ locale }) => locale === 'EN');
return translation?.text;
};
const getDescription = async (parent: Trait) => {
await orm.em.populate(parent, ['descriptions']);
const description = parent.descriptions
.getItems()
.find(({ locale }) => locale === 'EN');
return description?.text;
};
for (let i = 1; i <= 10000; i++) {
const traits = await orm.em.find(Trait, {});
await Promise.all(traits.map(traits => {
return Promise.all([getTranslation(traits), getDescription(traits)]);
}));
orm.em.clear();
}
});
}); |
I will try to provide a better (failing) example, but one thing to note is that most likely you will not be able to replicate it with |
Let me try with postgres, but I dont think this is about pooling, it needs to be a race in hydrating the entity which happens at ORM level. edit: same with postgres as expected |
In the meantime do you think we can provide any useful debug info from our prod environment? I could install some patched version of mikro-orm if needed |
Where is that coming from, is it your code (as in the repro in OP) or something called by the ORM (e.g. serialization)? I am having hard times believing that |
This is the place where we conditionally add incomplete entities to the identity map so we can resolve cycles, that would be my first suspect. |
I am not 100% sure if/how this is related, but there is one flaky test that is doing upserts without explicit transaction (which is in general bad idea, but should be doable from the ORM point of view), and contains a race condition in flushing new entities. The consequtive flush via I will try to implement some simple locking on the internal entity state object to get around it, probably also in the entity creation part which might help with this issue - would be great if you could use the dev version once its there to see if it helps. Will keep you posted. |
Would be happy to test the dev version to assist |
It looks like I discovered another problem when debugging this. I turned that flaky test into a heavy load test - doing ~1 million requests totally, using 50 entities via And the weirdest part? When I comment that problematic line, all tests are passing, including the #3005 issue test that was added together with that change. I guess it got fixed by something else (might be even a dependency update). Will ship this and let's see, worst case I will revert it 🤷 |
@wasd171 also, have you verified what I was asking for above? Whether the issue is coming from |
@B4nan I have added a check in the code to see whether field existed pre- and post-populate, will update you tomorrow |
@B4nan so far our logs show the following:
Same happens (via a different path) when we do not pre-populate |
I've added more logs to check for |
also or always? i dont see how Are you using the default select in loading strategy? |
I am not sure I am following the question. Does it happen for every request: no, but sometimes it does. Error on the field resolver level ( Does an error on the query level always imply an error on the resolver level? Yes, so far it looks like it. In case after
Not sure exactly what you mean but I think that this is something that I am doing in the example above
I am using joined strategy but open to try default one |
We do not use
Could it be that |
My point is that to me this all sounds like the problem is not in The thing is, you cant expect
You do
Now that is a very important detail that was not present in the repro nor in your OP.
I dont think so. |
If I understand you correctly, I've added this snippet to check, will update you when I have more data
But I think you are right, most likely the property was already
Even non-populated collections should always be defined, right?
Sorry about that, I did not add it because I think it broke for us in the same fashion a few months ago when using the default select in strategy. I have changed our strategy to select in and now I do not see this issue anymore |
For initialized/loaded entities yes, for entity references (not initialized entities) - nope, there it can/should be undefined, as those should only have the PK set.
Ok, like I can still imagine it won't matter, if its caused by some race condition in hydration. I see the build got broken and my fix I was referring to above is not yet published, will try to do something about that so you can also fix it. |
I already got first logs from #3383 (comment) with
|
The loading strategy wont matter without the populate hint ( Ok so my hunch was correct. I just fixed the build in master, so please the new dev version (dev 19). |
Unfortunately I can still reproduce this behaviour #3383 (comment) on |
I am quite sure that you will be able to reproduce this (as long as you actually have the parts responsible for breaking it, not like in the repro from OP), the same way as I did - repeating the test for e.g. 10000 times. I am affraid I can't help much more without any reproduciton. Let's try to deliver that instead of debugging your production app, as that might only help you to understand it, not me to fix it :] |
I will close this, without some repro its not actionable. |
Describe the bug
Sometimes calling
em.populate
does not init a relationStack trace
or
To Reproduce
Steps to reproduce the behavior:
Unfortunately I was not able to reliably reproduce this behaviour. Mostly it works for us but typically a few times per day we get this error in the same parts of the code. Given its unstable nature I would suspect that there is a race condition somewhere. If needed, I'll be happy to add some debugging info into our code to get more information from runtime exceptions.
We operate a GraphQL API and relevant entities look like the following:
1:N / M:N
1:1
Expected behavior
Calling
em.populate
does not result in a property beingundefined
Additional context
One thing that comes to my mind is that for both these cases entities are connected twice through different fields:
Versions
The text was updated successfully, but these errors were encountered: