Weiko bd65dbd47a Cache metadata lookups during ORM result formatting (#23593)
## Context

Production profiling identified `formatResult` as a recurring CPU
hotspot on read paths, especially for list queries and nested relations.

The formatter receives one metadata snapshot for the complete result,
but previously rebuilt metadata-derived lookup structures for every
record. For each record, including recursively formatted relation
records, it rebuilt or rescanned:

- field name and join-column maps
- composite field property maps
- required composite properties
- date and date-time field collections

This metadata does not change while one result is being formatted, so
the repeated work scaled with the number of records without changing the
output. It also created short-lived allocations that added GC pressure
on busy server pods.

## What changed

- Create a private cache for each top-level `formatResult` invocation.
- Lazily derive formatter metadata once per object metadata ID.
- Reuse it across records in an array and recursively formatted
relations.
- Precompute required composite property names and date-time field
metadata.
- Remove the DATE post-processing pass, which assigned each value back
to itself.
- Keep the exported `formatResult` signature unchanged.

The cache is discarded when the formatting call returns.

## Why use a call-scoped cache

The derived structures are valid for the metadata maps passed to one
`formatResult` call. Keeping the cache local provides reuse for the
complete result batch without adding cross-request state, invalidation
rules, or another long-lived memory cache.

This also preserves existing callers and keeps recursive implementation
details private.

## Safety

- Formatting behavior and returned shapes are unchanged.
- Nested relation formatting still resolves metadata for each target
object type.
- Composite null and default handling, and DATE_TIME validation, are
unchanged.
- Metadata is recomputed for every top-level invocation, so a later
request cannot reuse data derived from an older metadata snapshot.
- No Redis, workspace-cache, database, or public API behavior changes.

## Expected impact

Metadata preparation now scales with the number of object types in a
result instead of the number of records. The largest benefit is expected
for list queries and nested relations, with lower CPU usage and fewer
short-lived allocations.

This is a targeted result-formatting optimization. It does not address
every source of API tail latency or retained cache memory.

## Validation

- Added a nested-relation regression test that verifies unchanged
output.
- The test verifies metadata resolution is bounded per object type
within one invocation and recomputed for a separate invocation.
- Focused formatter Jest suite.
- Existing chart relation-label Jest suite, 10 tests.
- Type-aware Oxlint.
- Oxfmt.
- `yarn nx typecheck twenty-server`.
2026-07-30 16:31:44 +00:00
2026-06-11 11:02:28 +02:00

Twenty logo

The #1 Open-Source CRM

Website · Documentation · Roadmap · Discord · Figma

Twenty banner


Why Twenty

Twenty gives technical teams the building blocks for a custom CRM that meets complex business needs and quickly adapts as the business evolves. Twenty is the CRM you build, ship, and version like the rest of your stack.

Learn more about why we built Twenty


Installation

Cloud

The fastest way to get started. Sign up at twenty.com and spin up a workspace in under a minute, with no infrastructure to manage and always up to date.

Build an app

Scaffold a new app with the Twenty CLI:

npx create-twenty-app my-app

Define objects, fields, and views as code:

import { defineObject, FieldType } from 'twenty-sdk/define';

export default defineObject({
  nameSingular: 'deal',
  namePlural: 'deals',
  labelSingular: 'Deal',
  labelPlural: 'Deals',
  fields: [
    { name: 'name', label: 'Name', type: FieldType.TEXT },
    { name: 'amount', label: 'Amount', type: FieldType.CURRENCY },
    { name: 'closeDate', label: 'Close Date', type: FieldType.DATE_TIME },
  ],
});

Then ship it to your workspace:

npx twenty app:publish --private

See the app development guide for objects, views, agents, and logic functions.

Self-hosting

Run Twenty on your own infrastructure with Docker Compose, or contribute locally via the local setup guide.



Everything you need

Twenty gives you the building blocks of a modern CRM (objects, views, workflows, and agents) and lets you extend them as code. Here's a tour of what's in the box.

Want to go deeper? Read the User Guide for product walkthroughs, or the Documentation for developer reference.

Create your apps

Learn more about apps in doc

Stay on top with version control

Learn more about version control in doc

All the tools you need to build anything

Learn more about primitives in doc

Customize your layouts

Learn more about layouts in doc

AI agents and chats

Learn more about AI in doc

Plus all the tools of a good CRM

Learn more about CRM features in doc


Stack

Thanks

Greptile      Sentry      Crowdin

Thanks to these amazing services that we use and recommend for code review (Greptile), catching bugs (Sentry) and translating (Crowdin).

Join the Community

Star the repo · Discord · Feature requests · Releases · X · LinkedIn · Crowdin · Contribute

S
Description
The open alternative to Salesforce, designed for AI.
Readme AGPL-3.0 1.4 GiB
Languages
TypeScript 79.6%
MDX 17.3%
JavaScript 2.7%
Python 0.2%
SCSS 0.1%