Skip to content

Running simultaneous queries with relations stalls typeorm #4738

@oskarious

Description

@oskarious

Issue type:

[ ] question
[x] bug report
[ ] feature request
[ ] documentation issue

Database system/driver:

[ ] cordova
[ ] mongodb
[ ] mssql
[ ] mysql / mariadb
[ ] oracle
[x] postgres
[ ] cockroachdb
[ ] sqlite
[ ] sqljs
[ ] react-native
[ ] expo

TypeORM version:

[ ] latest
[x] @next
[ ] 0.x.x (or put your version here)

I ran into an issue where typeorm would just stop responding occasionally, and when looking at the postgres stats I noticed that typeorm kept 10 (the size of the pool) connections open and in idle and didn't seem to ever close them afterwards.

The connections showed up as Idle | WaitEvent: Client: ClientRead.

Some more investigations seem to indicate that it the issue presents itself when trying to find entities with relations (with the getManager().find(Entity, options) syntax).

Increasing the pool size can help mitigate the problem, and using the query builder works as a workaround, but is obviously something we'd want to avoid as much as possible in favour of shorter, and clearer syntax.

I don't believe it's a config issue either

let entities = ['./dist/api/src/entities/**/*.js'];

module.exports = {
  type: 'postgres',
  synchronize, // True locally
  ssl,
  host,
  database,
  username,
  password,
  entities,
  timezone: 'Z',
  logging,
  logger,
  maxQueryExecutionTime: 1000,
  extra: {
    poolSize, // 10
    idleTimeoutMillis: 5000, // Drop connections that are stalled
    connectionTimeoutMillis: 10000, // Drop connections that are stalled
  },

  cli: {
    entitiesDir: 'api/src/entities/**',
    migrationsDir: 'api/src/migration',
    subscribersDir: 'api/src/subscriber',
  },
};

Steps to reproduce or a small repository showing the problem:

/* eslint-disable @typescript-eslint/no-use-before-define */
import { createConnection, Entity, getManager, ManyToOne, OneToMany, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Entity2 {
  @PrimaryGeneratedColumn()
  id!: number;

  @ManyToOne(() => Entity1, ent1 => ent1.ent2)
  ent1!: Entity1;
}

@Entity()
export class Entity1 {
  @PrimaryGeneratedColumn()
  id!: number;

  @OneToMany(() => Entity2, ent2 => ent2.ent1)
  ent2!: Entity2[];
}

const getWithRelations = () => {
  getManager()
    .find(Entity1, { relations: { ent2: true } })
    .then(e => {
      console.log(e);
    });
};

const getSimple = () => {
  getManager()
    .find(Entity1)
    .then(console.log);
};

const getWithQb = () => {
  getManager()
    .getRepository(Entity1)
    .createQueryBuilder('ent')
    .leftJoinAndSelect('ent.ent2', 'ent2')
    .getMany()
    .then(console.log);
};

(async () => {
  await createConnection();

  const parent = await getManager().save(new Entity1());

  const child1 = new Entity2();
  child1.ent1 = parent;
  await getManager().save(child1);

  const child2 = new Entity2();
  child2.ent1 = parent;
  await getManager().save(child2);

  for (let i = 0; i < 35; i++) {
    getWithRelations(); // Will stall
  }

  for (let i = 0; i < 35; i++) {
    getSimple(); // Runs as expected
  }

  for (let i = 0; i < 35; i++) {
    getWithQb(); // Runs as expected
  }
})();

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions