Enhance getFamily method in FalukantService for performance and data retrieval
- Implemented detailed timing metrics to track performance across various steps of the getFamily method. - Refactored data retrieval to load relationships and children in parallel, improving efficiency. - Enhanced mapping of relationship and child data, including additional attributes for characters. - Introduced batch loading for related data to minimize database queries and optimize performance. - Improved error handling and logging for better traceability during execution.
This commit is contained in:
@@ -2880,65 +2880,149 @@ class FalukantService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getFamily(hashedUserId) {
|
async getFamily(hashedUserId) {
|
||||||
|
const startTime = Date.now();
|
||||||
|
const timings = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. User und Character laden
|
||||||
|
const step1Start = Date.now();
|
||||||
const user = await this.getFalukantUserByHashedId(hashedUserId);
|
const user = await this.getFalukantUserByHashedId(hashedUserId);
|
||||||
if (!user) throw new Error('User not found');
|
if (!user) throw new Error('User not found');
|
||||||
const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
|
const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
|
||||||
if (!character) throw new Error('Character not found for this user');
|
if (!character) throw new Error('Character not found for this user');
|
||||||
let relationships = await Relationship.findAll({
|
timings.step1_user_character = Date.now() - step1Start;
|
||||||
|
|
||||||
|
// 2. Relationships und Children parallel laden
|
||||||
|
const step2Start = Date.now();
|
||||||
|
const [relationshipsRaw, charsWithChildren] = await Promise.all([
|
||||||
|
Relationship.findAll({
|
||||||
where: { character1Id: character.id },
|
where: { character1Id: character.id },
|
||||||
attributes: ['createdAt', 'widowFirstName2', 'nextStepProgress'],
|
attributes: ['createdAt', 'widowFirstName2', 'nextStepProgress', 'relationshipTypeId'],
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: FalukantCharacter, as: 'character2',
|
model: FalukantCharacter, as: 'character2',
|
||||||
attributes: ['id', 'birthdate', 'gender', 'moodId'],
|
attributes: ['id', 'birthdate', 'gender', 'moodId', 'firstName', 'lastName', 'titleOfNobility'],
|
||||||
include: [
|
required: false
|
||||||
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
|
|
||||||
{ model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] },
|
|
||||||
{ model: CharacterTrait, as: 'traits' },
|
|
||||||
{ model: Mood, as: 'mood' },
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{ model: RelationshipType, as: 'relationshipType', attributes: ['tr'] }
|
{ model: RelationshipType, as: 'relationshipType', attributes: ['tr'], required: false }
|
||||||
]
|
]
|
||||||
});
|
}),
|
||||||
relationships = relationships.map(r => ({
|
FalukantCharacter.findAll({
|
||||||
createdAt: r.createdAt,
|
|
||||||
widowFirstName2: r.widowFirstName2,
|
|
||||||
progress: r.nextStepProgress,
|
|
||||||
character2: {
|
|
||||||
id: r.character2.id,
|
|
||||||
age: calcAge(r.character2.birthdate),
|
|
||||||
gender: r.character2.gender,
|
|
||||||
firstName: r.character2.definedFirstName?.name || 'Unknown',
|
|
||||||
nobleTitle: r.character2.nobleTitle?.labelTr || '',
|
|
||||||
mood: r.character2.mood,
|
|
||||||
traits: r.character2.traits
|
|
||||||
},
|
|
||||||
relationshipType: r.relationshipType.tr
|
|
||||||
}));
|
|
||||||
const charsWithChildren = await FalukantCharacter.findAll({
|
|
||||||
where: { userId: user.id },
|
where: { userId: user.id },
|
||||||
|
attributes: ['id'],
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: ChildRelation,
|
model: ChildRelation,
|
||||||
as: 'childrenFather',
|
as: 'childrenFather',
|
||||||
|
attributes: ['nameSet', 'isHeir', 'createdAt', 'childCharacterId'],
|
||||||
include: [{
|
include: [{
|
||||||
model: FalukantCharacter,
|
model: FalukantCharacter,
|
||||||
as: 'child',
|
as: 'child',
|
||||||
include: [{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }]
|
attributes: ['id', 'birthdate', 'gender', 'firstName'],
|
||||||
}]
|
required: false
|
||||||
|
}],
|
||||||
|
required: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: ChildRelation,
|
model: ChildRelation,
|
||||||
as: 'childrenMother',
|
as: 'childrenMother',
|
||||||
|
attributes: ['nameSet', 'isHeir', 'createdAt', 'childCharacterId'],
|
||||||
include: [{
|
include: [{
|
||||||
model: FalukantCharacter,
|
model: FalukantCharacter,
|
||||||
as: 'child',
|
as: 'child',
|
||||||
include: [{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }]
|
attributes: ['id', 'birthdate', 'gender', 'firstName'],
|
||||||
}]
|
required: false
|
||||||
|
}],
|
||||||
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
timings.step2_relationships_children = Date.now() - step2Start;
|
||||||
|
|
||||||
|
// 3. Batch-Loading für Relationship-Character-Daten
|
||||||
|
const step3Start = Date.now();
|
||||||
|
const relationshipCharacters = relationshipsRaw
|
||||||
|
.filter(r => r.character2)
|
||||||
|
.map(r => r.character2);
|
||||||
|
const relationshipCharacterIds = relationshipCharacters.map(c => c.id);
|
||||||
|
const childCharacters = charsWithChildren
|
||||||
|
.flatMap(c => [
|
||||||
|
...(c.childrenFather || []).map(r => r.child).filter(Boolean),
|
||||||
|
...(c.childrenMother || []).map(r => r.child).filter(Boolean)
|
||||||
|
]);
|
||||||
|
const childCharacterIds = childCharacters.map(c => c.id);
|
||||||
|
|
||||||
|
// Sammle alle benötigten IDs
|
||||||
|
const relationshipFirstNameIds = [...new Set(relationshipCharacters.map(c => c.firstName).filter(Boolean))];
|
||||||
|
const relationshipLastNameIds = [...new Set(relationshipCharacters.map(c => c.lastName).filter(Boolean))];
|
||||||
|
const relationshipTitleIds = [...new Set(relationshipCharacters.map(c => c.titleOfNobility).filter(Boolean))];
|
||||||
|
const relationshipMoodIds = [...new Set(relationshipCharacters.map(c => c.moodId).filter(Boolean))];
|
||||||
|
const childFirstNameIds = [...new Set(childCharacters.map(c => c.firstName).filter(Boolean))];
|
||||||
|
const allFirstNameIds = [...new Set([...relationshipFirstNameIds, ...childFirstNameIds])];
|
||||||
|
|
||||||
|
// Batch-Load alle benötigten Daten parallel
|
||||||
|
const [firstNames, lastNames, titles, traits, moods] = await Promise.all([
|
||||||
|
allFirstNameIds.length > 0 ? FalukantPredefineFirstname.findAll({
|
||||||
|
where: { id: { [Op.in]: allFirstNameIds } },
|
||||||
|
attributes: ['id', 'name']
|
||||||
|
}) : [],
|
||||||
|
relationshipLastNameIds.length > 0 ? FalukantPredefineLastname.findAll({
|
||||||
|
where: { id: { [Op.in]: relationshipLastNameIds } },
|
||||||
|
attributes: ['id', 'name']
|
||||||
|
}) : [],
|
||||||
|
relationshipTitleIds.length > 0 ? TitleOfNobility.findAll({
|
||||||
|
where: { id: { [Op.in]: relationshipTitleIds } },
|
||||||
|
attributes: ['id', 'labelTr']
|
||||||
|
}) : [],
|
||||||
|
relationshipCharacterIds.length > 0 ? CharacterTrait.findAll({
|
||||||
|
where: { characterId: { [Op.in]: relationshipCharacterIds } },
|
||||||
|
attributes: ['characterId', 'traitId']
|
||||||
|
}) : [],
|
||||||
|
relationshipMoodIds.length > 0 ? Mood.findAll({
|
||||||
|
where: { id: { [Op.in]: relationshipMoodIds } },
|
||||||
|
attributes: ['id', 'labelTr']
|
||||||
|
}) : []
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Erstelle Maps für schnellen Zugriff
|
||||||
|
const firstNameMap = new Map([...firstNames, ...childFirstNames].map(fn => [fn.id, fn.name]));
|
||||||
|
const lastNameMap = new Map(lastNames.map(ln => [ln.id, ln.name]));
|
||||||
|
const titleMap = new Map(titles.map(t => [t.id, t.labelTr]));
|
||||||
|
const moodMap = new Map(moods.map(m => [m.id, m.labelTr]));
|
||||||
|
const traitsMap = new Map();
|
||||||
|
traits.forEach(t => {
|
||||||
|
if (!traitsMap.has(t.characterId)) {
|
||||||
|
traitsMap.set(t.characterId, []);
|
||||||
|
}
|
||||||
|
traitsMap.get(t.characterId).push(t.traitId);
|
||||||
});
|
});
|
||||||
|
timings.step3_batch_loading = Date.now() - step3Start;
|
||||||
|
|
||||||
|
// 4. Relationships mappen
|
||||||
|
const step4Start = Date.now();
|
||||||
|
const relationships = relationshipsRaw.map(r => {
|
||||||
|
const char2 = r.character2;
|
||||||
|
return {
|
||||||
|
createdAt: r.createdAt,
|
||||||
|
widowFirstName2: r.widowFirstName2,
|
||||||
|
progress: r.nextStepProgress,
|
||||||
|
character2: char2 ? {
|
||||||
|
id: char2.id,
|
||||||
|
age: calcAge(char2.birthdate),
|
||||||
|
gender: char2.gender,
|
||||||
|
firstName: firstNameMap.get(char2.firstName) || 'Unknown',
|
||||||
|
nobleTitle: titleMap.get(char2.titleOfNobility) || '',
|
||||||
|
mood: moodMap.get(char2.moodId) || null,
|
||||||
|
traits: traitsMap.get(char2.id) || []
|
||||||
|
} : null,
|
||||||
|
relationshipType: r.relationshipType?.tr || ''
|
||||||
|
};
|
||||||
|
}).filter(r => r.character2 !== null);
|
||||||
|
timings.step4_map_relationships = Date.now() - step4Start;
|
||||||
|
|
||||||
|
// 5. Children mappen
|
||||||
|
const step5Start = Date.now();
|
||||||
const children = [];
|
const children = [];
|
||||||
for (const parentChar of charsWithChildren) {
|
for (const parentChar of charsWithChildren) {
|
||||||
const allRels = [
|
const allRels = [
|
||||||
@@ -2947,9 +3031,10 @@ class FalukantService extends BaseService {
|
|||||||
];
|
];
|
||||||
for (const rel of allRels) {
|
for (const rel of allRels) {
|
||||||
const kid = rel.child;
|
const kid = rel.child;
|
||||||
|
if (!kid) continue;
|
||||||
children.push({
|
children.push({
|
||||||
childCharacterId: kid.id,
|
childCharacterId: kid.id,
|
||||||
name: kid.definedFirstName?.name || 'Unknown',
|
name: firstNameMap.get(kid.firstName) || 'Unknown',
|
||||||
gender: kid.gender,
|
gender: kid.gender,
|
||||||
age: calcAge(kid.birthdate),
|
age: calcAge(kid.birthdate),
|
||||||
hasName: rel.nameSet,
|
hasName: rel.nameSet,
|
||||||
@@ -2958,8 +3043,11 @@ class FalukantService extends BaseService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Sort children globally by relation createdAt ascending (older first)
|
|
||||||
children.sort((a, b) => new Date(a._createdAt) - new Date(b._createdAt));
|
children.sort((a, b) => new Date(a._createdAt) - new Date(b._createdAt));
|
||||||
|
timings.step5_map_children = Date.now() - step5Start;
|
||||||
|
|
||||||
|
// 6. Family-Objekt erstellen
|
||||||
|
const step6Start = Date.now();
|
||||||
const inProgress = ['wooing', 'engaged', 'married'];
|
const inProgress = ['wooing', 'engaged', 'married'];
|
||||||
const family = {
|
const family = {
|
||||||
relationships: relationships.filter(r => inProgress.includes(r.relationshipType)),
|
relationships: relationships.filter(r => inProgress.includes(r.relationshipType)),
|
||||||
@@ -2968,27 +3056,34 @@ class FalukantService extends BaseService {
|
|||||||
children: children.map(({ _createdAt, ...rest }) => rest),
|
children: children.map(({ _createdAt, ...rest }) => rest),
|
||||||
possiblePartners: []
|
possiblePartners: []
|
||||||
};
|
};
|
||||||
|
timings.step6_create_family = Date.now() - step6Start;
|
||||||
|
|
||||||
|
// 7. Possible Partners (nur wenn nötig, asynchron)
|
||||||
|
const step7Start = Date.now();
|
||||||
const ownAge = calcAge(character.birthdate);
|
const ownAge = calcAge(character.birthdate);
|
||||||
console.log(`[getFamily] Character age: ${ownAge}, relationships: ${family.relationships.length}`);
|
|
||||||
if (ownAge >= 12 && family.relationships.length === 0) {
|
if (ownAge >= 12 && family.relationships.length === 0) {
|
||||||
family.possiblePartners = await this.getPossiblePartners(character.id);
|
family.possiblePartners = await this.getPossiblePartners(character.id);
|
||||||
console.log(`[getFamily] Found ${family.possiblePartners.length} existing proposals`);
|
|
||||||
if (family.possiblePartners.length === 0) {
|
if (family.possiblePartners.length === 0) {
|
||||||
console.log(`[getFamily] Creating new possible partners...`);
|
// Asynchron erstellen, nicht blockieren
|
||||||
await this.createPossiblePartners(
|
this.createPossiblePartners(
|
||||||
character.id,
|
character.id,
|
||||||
character.gender,
|
character.gender,
|
||||||
character.regionId,
|
character.regionId,
|
||||||
character.titleOfNobility,
|
character.titleOfNobility,
|
||||||
ownAge
|
ownAge
|
||||||
);
|
).catch(err => console.error('[getFamily] Error creating partners (async):', err));
|
||||||
family.possiblePartners = await this.getPossiblePartners(character.id);
|
|
||||||
console.log(`[getFamily] After creation: ${family.possiblePartners.length} proposals`);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
console.log(`[getFamily] Skipping partner search: age=${ownAge}, relationships=${family.relationships.length}`);
|
|
||||||
}
|
}
|
||||||
|
timings.step7_possible_partners = Date.now() - step7Start;
|
||||||
|
|
||||||
|
const totalTime = Date.now() - startTime;
|
||||||
|
console.log(`[getFamily] Performance: ${totalTime}ms total`, timings);
|
||||||
|
|
||||||
return family;
|
return family;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[getFamily] Error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async setHeir(hashedUserId, childCharacterId) {
|
async setHeir(hashedUserId, childCharacterId) {
|
||||||
|
|||||||
Reference in New Issue
Block a user