Benchmarks
Run them yourself:
bash scripts/bench.shIt packs the workspace, installs the tarballs into an empty project alongside drizzle-orm@1.0.0-rc.4, generates from one schema, and compares DRZL's output against drizzle-orm/zod's on the same table.
Not part of CI. A benchmark on a shared runner measures the runner, and a number that moves with the weather gets ignored or, worse, chased.
The table
Eight columns shaped like a real one, four of them carrying a CHECK:
export const users = pgTable('users', {
id: integer().notNull(),
email: varchar({ length: 255 }).notNull(),
name: text().notNull(),
age: integer().notNull(),
tier: text().notNull(),
score: integer().notNull(),
active: boolean().notNull(),
createdAt: timestamp({ mode: 'string' }).notNull(),
}, (t) => [
check('age_c', sql`${t.age} >= 18`),
check('score_c', sql`${t.score} BETWEEN 0 AND 100`),
check('tier_c', sql`${t.tier} IN ('free', 'pro', 'team')`),
check('name_c', sql`length(${t.name}) >= 2`),
]);Results
Node 22.22, one laptop, so read the ratios and not the absolute numbers.
| DRZL | drizzle-orm | |
|---|---|---|
| constraints the database enforces, reproduced | 4/4 | 0/4 |
| generated bytes for this table | 2036 | not a file |
| parses/sec, row accepted | 2,852,472 | 3,437,648 |
| parses/sec, row mistyped | 146,957 | 150,181 |
parses/sec, row violates a CHECK | 143,786 | 3,310,413 |
Reading them honestly
The last row is not a comparison. drizzle-orm runs at three million parses per second on a row that violates a CHECK because it accepts the row. It is fast in the way that returning true unconditionally is fast. Throughput is only comparable between two validators that enforce the same thing, and these two do not.
The first row is the whole point. Four constraints the database will refuse; DRZL reproduces all four, drizzle-orm/zod reproduces none. Every one of those is a row that passes validation and then fails at the database, which is the worst place to find out.
The second-to-last row is the one that matters most in practice. An API spends its validation time on requests that fail, and there the two are within 3%: the cost of a rejected parse is dominated by building the error, not by the checks.
On the happy path DRZL is about 17% slower, and that is the real cost of enforcing four extra constraints. It used to be 35%. A numeric CHECK was emitting
z.number().int().gte(-2147483648).lte(2147483647).refine((v) => v >= 18)a bound that can never fail, plus a closure saying what the bound should have said. It now folds into the range:
z.number().int().gte(18).lte(2147483647)which is faster, 28% smaller for this table, and produces zod's own error, Too small, expected number to be >=18, with the bound machine-readable on the issue rather than inside a string a generator wrote.
What is not measured here
Correctness against the database, which is checked separately and continuously. verify-packed.sh runs the emitted schemas against three real databases on every commit: Postgres in-process via PGlite, SQLite via node:sqlite, and MySQL as a CI service container.
1440 probes against a real Postgres (40 columns)
agree with the database: DRZL 1048, drizzle-orm 1013
DRZL closer than drizzle-orm on 35, further on 0
53 CHECK probes against a real Postgres (13 constrained columns)
rows Postgres rejects and the validator accepts: DRZL 0, drizzle-orm 22
9 defaulted columns, 9 reproduced by applyDefaults
32 CHECK probes against a real SQLite (10 constrained columns)
37 probes against a real MySQL
