Coder Social home page Coder Social logo

Case of extremely slow type checking about kysely HOT 29 OPEN

agj avatar agj commented on September 26, 2024 4
Case of extremely slow type checking

from kysely.

Comments (29)

koskimas avatar koskimas commented on September 26, 2024 11

The issue is somehow related to selectFrom overloads. If I replace all overloads with this single signature:

  selectFrom<
    TE extends
      | TableExpression<DB, never>
      | ReadonlyArray<TableExpression<DB, never>>,
  >(
    from: TE,
  ): TE extends ReadonlyArray<infer T>
    ? SelectQueryBuilder<From<DB, T>, FromTables<DB, never, T>, {}>
    : TE extends keyof DB & string
      ? SelectQueryBuilder<DB, ExtractTableAlias<DB, TE>, {}>
      : SelectQueryBuilder<From<DB, TE>, FromTables<DB, never, TE>, {}>

the type checking time goes from 12 seconds to 0.7 seconds. That type also passes all Kysely tests except this one:

async function testGenericSelect<T extends keyof Database>(
  db: Kysely<Database>,
  table: T,
) {
  const r1 = await db.selectFrom(table).select('id').executeTakeFirstOrThrow()
  expectAssignable<string | number>(r1.id)
}

which tests generic usage of selectFrom which we don't really support anyway.

from kysely.

koskimas avatar koskimas commented on September 26, 2024 10

Fixed updateTable and deleteFrom the same way.

Now I get this when there are no type errors
Screenshot 2024-07-15 at 16 45 55

And this when there are:
Screenshot 2024-07-15 at 16 46 35

from kysely.

agj avatar agj commented on September 26, 2024 7

Forgot to add that I'm immensely appreciative of the work done on Kysely. It's a great tool that lets us work with our database without fear. πŸ™

from kysely.

agj avatar agj commented on September 26, 2024 5

@koskimas You're right! I could reproduce the issue when there's a mistyped table name. Before the PR it's ~28 s, with the PR it's ~7 s. πŸŽ‰

from kysely.

agj avatar agj commented on September 26, 2024 3

Yeah, sure. Here's a Github gist with the relevant files, including an anonymized version of the database types.

from kysely.

koskimas avatar koskimas commented on September 26, 2024 3

A big issue on our project as well. Please let me know if there is anything I could assist with.

You can try profiling the typescript type checker and figuring out which type causes the issue.

from kysely.

koskimas avatar koskimas commented on September 26, 2024 3

There's now a PR for this. @agj Would you be able to test the PR branch in your codebase?

from kysely.

thelinuxlich avatar thelinuxlich commented on September 26, 2024 2

would a split into multiple schemas on the same interface, differentiated by the prefix, alleviate the performance issue?

from kysely.

koskimas avatar koskimas commented on September 26, 2024 1

I'm not able to reproduce this. Any chance to get a full reproduction? A complete project I can run and profile?

from kysely.

koskimas avatar koskimas commented on September 26, 2024 1

But we have the same issue with updateTable and all other QueryCreator methods that have been split into multiple overloads 😞

from kysely.

koskimas avatar koskimas commented on September 26, 2024 1

@imwexpex Could you also build the PR branch and test if it fixes the peformance issues you're having?

  1. pull the branch
  2. npm install
  3. npm run build
  4. Point your project to the local build

from kysely.

koskimas avatar koskimas commented on September 26, 2024 1

Well without any additional info, I won't be able to help you. I'm not seeing slow intellisense in vscode in any of our tests, including @agj's reproduction.

Intellisense uses the same type checker as the compiler.

from kysely.

koskimas avatar koskimas commented on September 26, 2024 1

Also, if the issue was in intellisense and not in the build, why did you send the build benchmark? Did you actually check the intellisense performance with the new branch?

from kysely.

agj avatar agj commented on September 26, 2024 1

Well @koskimas, not sure if this is good or bad news. The issue was for version 0.22.2, and I just confirmed that the issue persists in that version as well as in 0.22.3.

However, both 0.22.4 and the PR's version behave much better! In fact, indistinguishably so, both hover around 7 s for me, compared to the >60 s of the older versions. So, it looks like the changes in the PR don't do much when it comes to this particular issue, but something in v0.22.4 fixed the issue.

I'm very happy that the issue is under control, and I'm sorry that I wasn't able to identify in time that it was fixed in the latest release. πŸ™

from kysely.

koskimas avatar koskimas commented on September 26, 2024 1

@agj I didn't actually have any perf issues in your reproduction either before the fix when the types were correct, but once I mistyped something, I got the 12 second build time and 100s of thousands of instantations.

If you take 0.22.4 and test this

const demoQuery = (db: Kysely<DB>) =>
  db
    .selectFrom((eb) =>
      eb
        .selectFrom("my_table_LOOOOL")
        // Removing this `where` makes a dramatic difference in compilation time.
        .where((eb2) => eb2("my_table.id", "in", [1, 2, 3]))
        .selectAll()
        .as("test")
    )
    .selectAll();

versus the branch, do you see a difference?

from kysely.

koskimas avatar koskimas commented on September 26, 2024 1

@imwexpex There seems to be something in jsonArrayFrom and jsonObjectFrom types combined with inline nested queries + huge database that kills typescript language server. I'll take a look at those types.

Having said that, you can already avoid the slowness and arguably make your queries much more readable by splitting them to helper functions like this:

  const result = await db
    .selectFrom((eb) =>
      eb
        .selectFrom('my_table')
        .where('my_table.id', 'in', [1, 2, 3])
        .where('my_table.col_164b7896ec8e770207febe0812c5f052', 'is', 12)
        .select((eb) => [
          relation1(eb.ref('my_table.id')).as('my_table'),
          relation2(eb.ref('my_table.id')).as('data'),
        ])
        .as('test'),
    )
    .selectAll()
    .execute()

  function relation1(foreignKey: Expression<number>) {
    return jsonObjectFrom(
      db
        .selectFrom('table_1474a7e0348b1ca363ee4a2a5dc4a1ec')
        .selectAll('table_1474a7e0348b1ca363ee4a2a5dc4a1ec')
        .where('table_1474a7e0348b1ca363ee4a2a5dc4a1ec.id', '=', foreignKey),
    )
  }

  function relation2(foreignKey: Expression<number>) {
    return jsonArrayFrom(
      db
        .selectFrom('table_0b5ac72e03509e06683edcba4b3887ab')
        .selectAll('table_0b5ac72e03509e06683edcba4b3887ab')
        .where(({ eb, ref, selectFrom }) =>
          eb(
            ref('table_0b5ac72e03509e06683edcba4b3887ab.id'),
            'in',
            selectFrom('table_da9bc7793b7e3784c03d55128145b7e3')
              .where('id', '=', 1)
              .select('id'),
          ),
        )
        .where('table_0b5ac72e03509e06683edcba4b3887ab.id', '=', foreignKey)
        .select((eb1) => [
          relation3(
            eb1.ref(
              'table_0b5ac72e03509e06683edcba4b3887ab.col_1b6b0183f1a79f6d53cc5f8bd5f315d7',
            ),
          ).as('data'),

          relation4(eb1.ref('table_0b5ac72e03509e06683edcba4b3887ab.id')).as(
            'data2',
          ),
        ])
        .orderBy('col_15af44699327f9a6bb2f12da27fdf0ce', 'desc'),
    )
  }

  function relation3(foreignKey: Expression<string | null>) {
    return jsonObjectFrom(
      db
        .selectFrom('table_ede804aecaaa0ad1a5afffb9b2a0d927')
        .where('table_ede804aecaaa0ad1a5afffb9b2a0d927.id', '=', foreignKey)
        .where(
          'table_ede804aecaaa0ad1a5afffb9b2a0d927.col_80ae6f72f9c170f9e1bcae48c1677802',
          '!=',
          12,
        )
        .selectAll('table_ede804aecaaa0ad1a5afffb9b2a0d927')
        .select((eb) => [
          jsonObjectFrom(
            eb
              .selectFrom('table_81859a5a7cf340e00333e133ee83c6a3')
              .select([
                'col_2f76db193eac6ad0f152563313673ac9',
                'col_4d742b2f247bec99b41a60acbebc149a',
                'col_6137cde4893c59f76f005a8123d8e8e6',
              ])
              .whereRef(
                'table_ede804aecaaa0ad1a5afffb9b2a0d927.col_a7f4797b61012da3a9c1a8049c8a4974',
                '=',
                'table_81859a5a7cf340e00333e133ee83c6a3.col_aa6b7c7a9c7a177e3f1ba452783eb63b',
              ),
          ).as(
            'table_ede804aecaaa0ad1a5afffb9b2a0d927.col_aa6b7c7a9c7a177e3f1ba452783eb63b',
          ),

          jsonObjectFrom(
            eb
              .selectFrom('table_1084015020d8bed5c99852780cd060b5')
              .select([
                'table_1084015020d8bed5c99852780cd060b5.col_665cebec3ce1380ce4e3c90b1bedcbf5',
              ])
              .whereRef(
                'table_1084015020d8bed5c99852780cd060b5.id',
                '=',
                'table_1084015020d8bed5c99852780cd060b5.col_53ca581ab7a5aef5b659bc27c81d5d51',
              ),
          ).as(
            'table_1084015020d8bed5c99852780cd060b5.col_53ca581ab7a5aef5b659bc27c81d5d51',
          ),
        ]),
    )
  }

  function relation4(foreginKey: Expression<number>) {
    return jsonObjectFrom(
      db
        .selectFrom('table_736d84cc80e257dc78371b1eb0fb6b20')
        .selectAll('table_736d84cc80e257dc78371b1eb0fb6b20')
        .where('table_736d84cc80e257dc78371b1eb0fb6b20.id', '=', foreginKey),
    )
  }

from kysely.

igalklebanov avatar igalklebanov commented on September 26, 2024

Hey πŸ‘‹

Does this improve performance?

const demoQuery = (db: Kysely<DB>) =>
  db
    .selectFrom((eb) =>
      eb
        .selectFrom("my_table")
        .where("my_table.id", "in", [1, 2, 3])
        .selectAll()
        .as("test")
    )
    .selectAll();

from kysely.

agj avatar agj commented on September 26, 2024

Hi! I'm away from my computer, as I'm on vacation right now, but as soon as I'm back I'll give it a try!

from kysely.

agj avatar agj commented on September 26, 2024

@igalklebanov It did improve it quite a bit! But it's still not great…

Files:              435
Lines:           119236
Identifiers:     112578
Symbols:         246727
Types:             7593
Instantiations: 1355207
Memory used:    212941K
I/O read:         0.05s
I/O write:        0.00s
Parse time:       0.69s
Bind time:        0.30s
Check time:      11.30s
Emit time:        0.00s
Total time:      12.29s

from kysely.

kevin-johne avatar kevin-johne commented on September 26, 2024

Hello there, and thank you for the amazing project, I introduced the typesafe query builder into our project which containes alot of tables. I wonder if Kysely is made for a scale of 100s and 100s of tables with 100s of complex queries.

Without Kyseley type checking is around 8 seconds, after starting generating the types of our db and only include ~100tables and written complex queries which use the expression builder to compose sub queries with joints. The query results are efficient and build for purpose And I start already plunching in the 60 seconds type checking.
This makes the intellisense so slow and with delay that I'm not able to write more queries.

If this performance is odd I will go deeper into stats and post them here.
Thank you

TypeScript version 5.1.6

from kysely.

igalklebanov avatar igalklebanov commented on September 26, 2024

I'd strongly consider splitting data access to several Kysely instances with their own limited view of the world, if possible.
I believe kysely-codegen has a filtering mechanism, should come in handy.

Kysely is built around, for the most part, string literal type comparisons. The more unique column and table names in DB interface, the slower type-checking gets and harder language server works.

from kysely.

kevin-johne avatar kevin-johne commented on September 26, 2024

thank you for your quick reply, I will give this a try.

from kysely.

imwexpex avatar imwexpex commented on September 26, 2024

A big issue on our project as well. Please let me know if there is anything I could assist with.

from kysely.

koskimas avatar koskimas commented on September 26, 2024

The issue here is basically due to the metric shit-ton of tables and columns and some O(N^X) type checking corner-case. The typescript type checker is so insanely complex that debugging this is difficult.

There probably is some way to make this case faster, but the only way to find it is blind trial and error. Or alternatively getting the help of one of the ~10 people on earth that actually understand how typescript internals work.

from kysely.

imwexpex avatar imwexpex commented on September 26, 2024

@imwexpex Could you also build the PR branch and test if it fixes the peformance issues you're having?

  1. pull the branch
  2. npm install
  3. npm run build
  4. Point your project to the local build

Unfortunately, the fix did not have a big impact on my project

Latest release:

PR Branch:
image

from kysely.

koskimas avatar koskimas commented on September 26, 2024

Unfortunately, the fix did not have a big impact on my project

1.86 seconds is not a long time. Looking at the numbers, you don't actually have any performance issues. Especially given that the parsing time is also 1.5 seconds. Type checking 3000 files in 1.86 seconds is actually insanely fast.

from kysely.

imwexpex avatar imwexpex commented on September 26, 2024

Unfortunately, the fix did not have a big impact on my project

1.86 seconds is not a long time. Looking at the numbers, you don't actually have any performance issues. Especially given that the parsing time is also 1.5 seconds.

Agree with you, there is no issue with compilation time, but rather with WebStorm/VSCode Intellisense. For some reason, hard queries kill IDE suggestions/linting.

This was also mentioned by @kevin-johne

This makes the intellisense so slow and with delay that I'm not able to write more queries.

from kysely.

imwexpex avatar imwexpex commented on September 26, 2024

Also, if the issue was in intellisense and not in the build, why did you send the build benchmark? Did you actually check the intellisense performance with the new branch?

Sure, I checked. 3+ sec lag for any suggestion and the RAM usage goes up to 3GB on the opened file.

I've tried to reproduce the same query in your test file, and it looks like everything works smoothly. So probably an issue somewhere else...

from kysely.

imwexpex avatar imwexpex commented on September 26, 2024

Query example, to be clear:

db
    .selectFrom((eb) =>
      eb
        .selectFrom('my_table')
        .where('my_table.id', 'in', [1, 2, 3])
        .where('my_table.col_164b7896ec8e770207febe0812c5f052', 'is', 12)
        .select((eb) => [
          jsonObjectFrom(
            eb
              .selectFrom('table_1474a7e0348b1ca363ee4a2a5dc4a1ec')
              .selectAll('table_1474a7e0348b1ca363ee4a2a5dc4a1ec')
              .whereRef(
                'my_table.id',
                '=',
                'table_1474a7e0348b1ca363ee4a2a5dc4a1ec.id',
              ),
          ).as('my_table'),
          jsonArrayFrom(
            eb
              .selectFrom('table_0b5ac72e03509e06683edcba4b3887ab')
              .selectAll('table_0b5ac72e03509e06683edcba4b3887ab')
              .where(({ eb: eb1, ref, selectFrom }) =>
                eb1(
                  ref('table_0b5ac72e03509e06683edcba4b3887ab.id'),
                  'in',
                  selectFrom('table_da9bc7793b7e3784c03d55128145b7e3')
                    .where('id', '=', 1)
                    .select('id'),
                ),
              )
              .whereRef(
                'my_table.id',
                '=',
                'table_0b5ac72e03509e06683edcba4b3887ab.id',
              )
              .select((eb1) => [
                jsonObjectFrom(
                  eb1
                    .selectFrom('table_ede804aecaaa0ad1a5afffb9b2a0d927')
                    .whereRef(
                      'table_ede804aecaaa0ad1a5afffb9b2a0d927.id',
                      '=',
                      'table_0b5ac72e03509e06683edcba4b3887ab.col_1b6b0183f1a79f6d53cc5f8bd5f315d7',
                    )
                    .where(
                      'table_ede804aecaaa0ad1a5afffb9b2a0d927.col_80ae6f72f9c170f9e1bcae48c1677802',
                      '!=',
                      12,
                    )
                    .selectAll('table_ede804aecaaa0ad1a5afffb9b2a0d927')
                    .select((eb2) => [
                      jsonObjectFrom(
                        eb2
                          .selectFrom('table_81859a5a7cf340e00333e133ee83c6a3')
                          .select([
                            'col_0191c0aa0d39de591b5236b304496123',
                            'col_08de056b6d3139a95d0aaf99f8e3c52e',
                            'col_195367e97caee2b9ed726514b7a38efc',
                          ])
                          .whereRef(
                            'table_ede804aecaaa0ad1a5afffb9b2a0d927.col_a7f4797b61012da3a9c1a8049c8a4974',
                            '=',
                            'table_81859a5a7cf340e00333e133ee83c6a3.col_aa6b7c7a9c7a177e3f1ba452783eb63b',
                          ),
                      ).as(
                        'table_ede804aecaaa0ad1a5afffb9b2a0d927.col_aa6b7c7a9c7a177e3f1ba452783eb63b',
                      ),
                      jsonObjectFrom(
                        eb2
                          .selectFrom('table_1084015020d8bed5c99852780cd060b5')
                          .select([
                            'table_1084015020d8bed5c99852780cd060b5.col_665cebec3ce1380ce4e3c90b1bedcbf5',
                          ])
                          .whereRef(
                            'table_1084015020d8bed5c99852780cd060b5.id',
                            '=',
                            'table_1084015020d8bed5c99852780cd060b5.col_53ca581ab7a5aef5b659bc27c81d5d51',
                          ),
                      ).as(
                        'table_1084015020d8bed5c99852780cd060b5.col_53ca581ab7a5aef5b659bc27c81d5d51',
                      ),
                    ]),
                ).as('table_ede804aecaaa0ad1a5afffb9b2a0d927.data'),
                jsonObjectFrom(
                  eb1
                    .selectFrom('table_736d84cc80e257dc78371b1eb0fb6b20')
                    .selectAll('table_736d84cc80e257dc78371b1eb0fb6b20')
                    .whereRef(
                      'table_736d84cc80e257dc78371b1eb0fb6b20.id',
                      '=',
                      'table_0b5ac72e03509e06683edcba4b3887ab.id',
                    ),
                ).as('table_736d84cc80e257dc78371b1eb0fb6b20'),
              ])
              .orderBy('col_15af44699327f9a6bb2f12da27fdf0ce', 'desc'),
          ).as('data'),
        ])
        .as('test'),
    )
    .selectAll()

from kysely.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.