Open4

prisma wasm のビルドサイズの調査

mizchimizchi

prisma を cloudflare d1 で動かしたい。その調査

prismaの qruey-engine の wasm build ができた。

https://github.com/prisma/prisma/pull/21382

d1 用のアダプタがまだ存在しないので、sqlite のクエリエンジン部分をどう動かすかを考える。

手元でビルドする方法

$ git clone https://github.com/prisma/prisma-engines
$ cd prisma-engines
$ cargo build
$ cd query-engine/query-engine-wasm
$ bash build.sh # pkg/query-engine-bg.wasm

現状3.0MB

mizchimizchi

とりあえず手元で動いた。

/**
 * Run with: `node --experimental-wasm-modules ./example.js`
 * on Node.js 18+.
 */
import { readFile } from 'fs/promises'
import { PrismaLibSQL } from '@prisma/adapter-libsql'
import { createClient } from '@libsql/client'
import { bindAdapter } from '@prisma/driver-adapter-utils'
import { QueryEngine, getBuildTimeInfo } from '@prisma/query-engine-wasm';

async function main() {
  // Always initialize the Wasm library before using it.
  // This sets up the logging and panic hooks.

  const client = createClient({ url: "file:./prisma/dev.db" })
  const adapter = new PrismaLibSQL(client)
  const driverAdapter = bindAdapter(adapter)

  console.log('buildTimeInfo', getBuildTimeInfo())

  const datamodel = await readFile('prisma/schema.prisma', 'utf8')

  const options = {
    datamodel,
    logLevel: 'info',
    logQueries: true,
    datasourceOverrides: {},
    env: process.env,
    configDir: '/tmp',
    ignoreEnvVarErrors: true,
  }
  const callback = () => { console.log('log-callback') }

  const queryEngine = new QueryEngine(options, callback, driverAdapter)

  await queryEngine.connect('trace')

  const created = await queryEngine.query(JSON.stringify({
    modelName: 'User',
    action: 'createOne',
    query: {
      arguments: {
        data: {
          id: 1235,
        },
      },
      selection: {
        $scalars: true
      }
    }
  }), 'trace')

  console.log({ created })

  const res = await queryEngine.query(JSON.stringify({
    modelName: 'User',
    action: 'findMany',
    query: {
      arguments: {},
      selection: {
        $scalars: true
      }
    }
  }), 'trace')
  const parsed = JSON.parse(res);
  console.log('query result = ')
  console.dir(parsed, { depth: null })

  const error = parsed.errors?.[0]?.user_facing_error
  if (error?.error_code === 'P2036') {
    console.log('js error:', driverAdapter.errorRegistry.consumeError(error.meta.id))
  }

  // console.log('before disconnect')
  await queryEngine.disconnect('trace')
  // console.log('after disconnect')

  // console.log('before free')
  queryEngine.free()
  // console.log('after free')
}

main()
mizchimizchi

d1 用にビルドサイズを減らすためのアイデア

現状 3.0MB

prisma.schema のパースとバリデーションを追い出す

スキーマのバリデーションだけでビルドしてみると、660k。
runtime では事前にチェックしてあるとして、シリアライズ済みの schema を受け取れば 660kb のうちいくらかは減るはず。

// src/wasm/engine.rs

// QueryEngine をコメントアウトして次のコードでビルド

#[wasm_bindgen]
pub async fn init0(options: ConstructorOptions, adapter: JsObject) -> Result<(), wasm_bindgen::JsError> {
    let ConstructorOptions {
        datamodel,
        log_level,
        log_queries,
        engine_protocol,
    } = options;
    let connector_registry: ConnectorRegistry<'_> = &[SQLITE];
    let mut schema = psl::parse_without_validation(datamodel.into(), connector_registry);
    // --- 660k

    let js_queryable = driver_adapters::from_js(adapter);
    // js_queryable.instrument(span)
    // --- 661k
    sql_connector::activate_driver_adapter(Arc::new(js_queryable));
    // --- 1.0MB

    let enable_tracing = false; // config.preview_features().contains(PreviewFeature::Tracing);
    let engine_protocol = engine_protocol.unwrap_or(EngineProtocol::Json);
    // --- 1.0MB
}

おそらく psl パッケージ

d1 専用ビルド(sqlite only)

実装に mssql, mongodb, mysql が入ってきているので、sqlite 以外の実装を落とす。

そもそも現状は cloudflare d1 用のアダプタがないので、それごと作る必要がある。

bindAdapter() でブリッジを生成している部分を d1 用に差し替えてやる必要がある?

そもそも query builder だけを取り出す

d1 でほしいのは prisma の型定義ファイルから生成される json query => sql への変換部分。

queryable.build_query がそれっぽく見えるのだが、自信がない。

tokio => wasm_bindgen_features

非同期ランタイムに tokio を使っているが、wasm 環境なら spawn_local でなんとかなる。

https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen_futures/fn.spawn_local.html

mizchimizchi

ビルドサイズは3MB なので workers の bundled ($5/m)にのせることはできる。

が、 d1 で動かすにはアダプタが足りない。これは mongo adapter あたりを参考にすると動く?

そもそも prisma の吐く sqlite が d1 でパースできるかの問題があるので、beta リリースされたらまた考える。