2023-07-31 02:37:09 -07:00
|
|
|
import { Service } from 'typedi';
|
perf(core): Improve caching service (#8213)
Story: https://linear.app/n8n/issue/PAY-1188
- Implement Redis hashes on the caching service, based on Micha's work
in #7747, adapted from `node-cache-manager-ioredis-yet`. Optimize
workflow ownership lookups and manual webhook lookups with Redis hashes.
- Simplify the caching service by removing all currently unused methods
and options: `enable`, `disable`, `getCache`, `keys`, `keyValues`,
`refreshFunctionEach`, `refreshFunctionMany`, `refreshTtl`, etc.
- Remove the flag `N8N_CACHE_ENABLED`. Currently some features on
`master` are broken with caching disabled, and test webhooks now rely
entirely on caching, for multi-main setup support. We originally
introduced this flag to protect against excessive memory usage, but
total cache usage is low enough that we decided to drop this setting.
Apparently this flag was also never documented.
- Overall caching service refactor: use generics, reduce branching, add
discriminants for cache kinds for better type safety, type caching
events, improve readability, remove outdated docs, etc. Also refactor
and expand caching service tests.
Follow-up to: https://github.com/n8n-io/n8n/pull/8176
---------
Co-authored-by: Michael Auerswald <michael.auerswald@gmail.com>
2024-01-05 02:52:44 -08:00
|
|
|
import { CacheService } from '@/services/cache/cache.service';
|
2024-08-27 07:44:32 -07:00
|
|
|
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
|
2024-08-27 08:24:20 -07:00
|
|
|
import { UserRepository } from '@/databases/repositories/user.repository';
|
2023-12-05 01:11:18 -08:00
|
|
|
import type { ListQuery } from '@/requests';
|
2024-08-27 07:44:32 -07:00
|
|
|
import type { Project } from '@/databases/entities/project';
|
2024-05-17 01:53:15 -07:00
|
|
|
import { ProjectRepository } from '@/databases/repositories/project.repository';
|
|
|
|
import type { User } from '@/databases/entities/User';
|
2024-08-27 07:44:32 -07:00
|
|
|
import { ProjectRelationRepository } from '@/databases/repositories/project-relation.repository';
|
2023-07-31 02:37:09 -07:00
|
|
|
|
|
|
|
@Service()
|
|
|
|
export class OwnershipService {
|
|
|
|
constructor(
|
|
|
|
private cacheService: CacheService,
|
2023-12-11 04:35:09 -08:00
|
|
|
private userRepository: UserRepository,
|
2024-05-17 01:53:15 -07:00
|
|
|
private projectRepository: ProjectRepository,
|
|
|
|
private projectRelationRepository: ProjectRelationRepository,
|
2023-07-31 02:37:09 -07:00
|
|
|
private sharedWorkflowRepository: SharedWorkflowRepository,
|
|
|
|
) {}
|
|
|
|
|
|
|
|
/**
|
2024-05-17 01:53:15 -07:00
|
|
|
* Retrieve the project that owns the workflow. Note that workflow ownership is **immutable**.
|
2023-07-31 02:37:09 -07:00
|
|
|
*/
|
2024-05-17 01:53:15 -07:00
|
|
|
async getWorkflowProjectCached(workflowId: string): Promise<Project> {
|
|
|
|
const cachedValue = await this.cacheService.getHashValue<Project>(
|
|
|
|
'workflow-project',
|
perf(core): Improve caching service (#8213)
Story: https://linear.app/n8n/issue/PAY-1188
- Implement Redis hashes on the caching service, based on Micha's work
in #7747, adapted from `node-cache-manager-ioredis-yet`. Optimize
workflow ownership lookups and manual webhook lookups with Redis hashes.
- Simplify the caching service by removing all currently unused methods
and options: `enable`, `disable`, `getCache`, `keys`, `keyValues`,
`refreshFunctionEach`, `refreshFunctionMany`, `refreshTtl`, etc.
- Remove the flag `N8N_CACHE_ENABLED`. Currently some features on
`master` are broken with caching disabled, and test webhooks now rely
entirely on caching, for multi-main setup support. We originally
introduced this flag to protect against excessive memory usage, but
total cache usage is low enough that we decided to drop this setting.
Apparently this flag was also never documented.
- Overall caching service refactor: use generics, reduce branching, add
discriminants for cache kinds for better type safety, type caching
events, improve readability, remove outdated docs, etc. Also refactor
and expand caching service tests.
Follow-up to: https://github.com/n8n-io/n8n/pull/8176
---------
Co-authored-by: Michael Auerswald <michael.auerswald@gmail.com>
2024-01-05 02:52:44 -08:00
|
|
|
workflowId,
|
|
|
|
);
|
2023-07-31 02:37:09 -07:00
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
if (cachedValue) return this.projectRepository.create(cachedValue);
|
2023-07-31 02:37:09 -07:00
|
|
|
|
|
|
|
const sharedWorkflow = await this.sharedWorkflowRepository.findOneOrFail({
|
2024-01-24 04:38:57 -08:00
|
|
|
where: { workflowId, role: 'workflow:owner' },
|
2024-05-17 01:53:15 -07:00
|
|
|
relations: ['project'],
|
2023-07-31 02:37:09 -07:00
|
|
|
});
|
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
void this.cacheService.setHash('workflow-project', { [workflowId]: sharedWorkflow.project });
|
2023-07-31 02:37:09 -07:00
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
return sharedWorkflow.project;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve the user that owns the project, or null if it's not an ownable project. Note that project ownership is **immutable**.
|
|
|
|
*/
|
|
|
|
async getProjectOwnerCached(projectId: string): Promise<User | null> {
|
|
|
|
const cachedValue = await this.cacheService.getHashValue<User | null>(
|
|
|
|
'project-owner',
|
|
|
|
projectId,
|
|
|
|
);
|
|
|
|
|
|
|
|
if (cachedValue) this.userRepository.create(cachedValue);
|
|
|
|
if (cachedValue === null) return null;
|
|
|
|
|
|
|
|
const ownerRel = await this.projectRelationRepository.getPersonalProjectOwners([projectId]);
|
|
|
|
const owner = ownerRel[0]?.user ?? null;
|
|
|
|
void this.cacheService.setHash('project-owner', { [projectId]: owner });
|
|
|
|
|
|
|
|
return owner;
|
2023-07-31 02:37:09 -07:00
|
|
|
}
|
2023-08-22 04:19:37 -07:00
|
|
|
|
2023-12-05 01:11:18 -08:00
|
|
|
addOwnedByAndSharedWith(
|
|
|
|
rawWorkflow: ListQuery.Workflow.WithSharing,
|
|
|
|
): ListQuery.Workflow.WithOwnedByAndSharedWith;
|
|
|
|
addOwnedByAndSharedWith(
|
|
|
|
rawCredential: ListQuery.Credentials.WithSharing,
|
|
|
|
): ListQuery.Credentials.WithOwnedByAndSharedWith;
|
|
|
|
addOwnedByAndSharedWith(
|
|
|
|
rawEntity: ListQuery.Workflow.WithSharing | ListQuery.Credentials.WithSharing,
|
|
|
|
): ListQuery.Workflow.WithOwnedByAndSharedWith | ListQuery.Credentials.WithOwnedByAndSharedWith {
|
2024-05-17 01:53:15 -07:00
|
|
|
const shared = rawEntity.shared;
|
|
|
|
const entity = rawEntity as
|
2023-12-05 01:11:18 -08:00
|
|
|
| ListQuery.Workflow.WithOwnedByAndSharedWith
|
|
|
|
| ListQuery.Credentials.WithOwnedByAndSharedWith;
|
2023-08-22 04:19:37 -07:00
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
Object.assign(entity, {
|
|
|
|
homeProject: null,
|
|
|
|
sharedWithProjects: [],
|
|
|
|
});
|
|
|
|
|
|
|
|
if (shared === undefined) {
|
|
|
|
return entity;
|
|
|
|
}
|
2023-09-04 01:37:16 -07:00
|
|
|
|
2024-05-17 01:53:15 -07:00
|
|
|
for (const sharedEntity of shared) {
|
|
|
|
const { project, role } = sharedEntity;
|
2023-09-04 01:37:16 -07:00
|
|
|
|
2024-01-24 04:38:57 -08:00
|
|
|
if (role === 'credential:owner' || role === 'workflow:owner') {
|
2024-05-17 01:53:15 -07:00
|
|
|
entity.homeProject = {
|
|
|
|
id: project.id,
|
|
|
|
type: project.type,
|
|
|
|
name: project.name,
|
|
|
|
};
|
2023-09-04 01:37:16 -07:00
|
|
|
} else {
|
2024-05-17 01:53:15 -07:00
|
|
|
entity.sharedWithProjects.push({
|
|
|
|
id: project.id,
|
|
|
|
type: project.type,
|
|
|
|
name: project.name,
|
|
|
|
});
|
2023-09-04 01:37:16 -07:00
|
|
|
}
|
2024-05-17 01:53:15 -07:00
|
|
|
}
|
2023-09-04 01:37:16 -07:00
|
|
|
|
2023-12-05 01:11:18 -08:00
|
|
|
return entity;
|
2023-09-04 01:37:16 -07:00
|
|
|
}
|
2023-12-11 04:35:09 -08:00
|
|
|
|
|
|
|
async getInstanceOwner() {
|
2024-01-17 07:08:50 -08:00
|
|
|
return await this.userRepository.findOneOrFail({
|
2024-01-24 04:38:57 -08:00
|
|
|
where: { role: 'global:owner' },
|
2023-12-11 04:35:09 -08:00
|
|
|
});
|
|
|
|
}
|
2023-07-31 02:37:09 -07:00
|
|
|
}
|