Not verified hands-on. This article was written from primary documentation rather than reproduced in an org, so it carries no tested-release badge. If you run it and something differs, say so in the comments and it gets tested and corrected.

Your custom object is not a table and your custom field is not a column. Salesforce stores every org’s records in a small number of shared structures, describes your schema in metadata rather than in DDL, and reconstructs objects at query time. Nearly every governor limit and every performance rule follows from that one design decision, which is why it is worth understanding even though you can never see it.

The model in outline

Salesforce has published the shape of this architecture, and the pieces that matter to you are these:

Piece Role
Metadata (the UDD) Describes your objects, fields, relationships and layouts as data, not as database schema
Data tables Hold record rows for all orgs, keyed by organisation and object type, with generic value columns
Pivot / index tables Hold copies of indexed field values in a typed, searchable form
Runtime engine Turns your SOQL into a query against the shared structures using the metadata

Creating a custom field writes metadata and does not run ALTER TABLE. That is why adding a field to an object with fifty million rows is near-instant, and why changing a field’s type is a different and much slower operation — the data has to be rewritten into a different storage representation.

Why a custom index is not a database index

Because field values live in generic columns shared across every org, an ordinary index on such a column would be useless. Instead, indexed values are copied into separate structures with the value stored in its proper type, alongside the org and object identifiers.

Two consequences follow directly, and both explain things people find arbitrary:

  • Only some field types can be indexed. Long text areas, rich text and multi-select picklists have no sensible typed representation to copy, so they cannot be indexed at all.
  • Nulls are generally not present. A field with no value has nothing to copy, which is why WHERE Field__c = null so often cannot use an index.

It also explains why “just index it” is a support request rather than a checkbox: there is maintenance cost per indexed field, borne by the platform, across every org.

Why the optimiser refuses queries instead of running them slowly

On a database you own, a bad query is your problem. Here, a scan of a shared structure competes with every other tenant on the instance. The optimiser therefore estimates cost and, in a governed context, declines rather than degrades — which is what a non-selective query exception is.

The selectivity thresholds (under 10% of the first million rows, under 5% beyond that, capped at 333,000) are the platform’s definition of “worth using an index for”. They are not tuned to your workload and cannot be.

Why governor limits exist in the shape they do

Limits are per transaction rather than per org per day, because the resource being protected is the application server handling the request, not your storage.

That explains some otherwise odd behaviour. CPU time counts application-server compute and excludes database time for SOQL and DML and time waiting on callouts — a transaction can run for well over ten seconds of wall clock without failing, as long as the time was spent waiting rather than computing. Heap is capped at 6 MB synchronous and 12 MB asynchronous because that memory is held on a shared server.

And async contexts get roughly doubled limits (200 SOQL queries, 60,000 ms CPU) not because the work is cheaper, but because it is not holding a user’s request open.

Why row locks behave the way they do

Updating a child in a master-detail relationship locks the parent. In a normalised database this is a design choice. Here it is how roll-up summaries stay consistent when rows for many orgs share structures. A transaction waits at most ten seconds for a contested lock before failing.

This is the mechanism behind data skew: 100,000 children under one parent means every write to any of them contends for one lock. Sorting a load file by parent Id so that same-parent rows land in the same batch is not folklore. It is the direct mitigation.

What this changes in how you design

  • Filter on something indexed and narrow, always. The platform will not compensate for a filter that cannot drive an index.
  • Model for query paths, not for normalisation elegance. A denormalised indexable flag often beats a join you cannot make selective.
  • Expect field type changes to be expensive and field additions to be cheap. That is the opposite of a self-managed database.
  • Treat parent-child ratios as a performance decision. Skew is a storage-level problem you cannot code around.

The tradeoff underneath all of this is worth stating plainly: you gave up control of the physical model and got upgrades, elasticity and no DBA in exchange. That is usually a good trade. It stops being one when a design needs guarantees (query plans you control, indexes you create) that the platform does not offer.

Frequently asked

Can I see any of these structures?

No. There is no way to query the underlying storage, and nothing about it is part of the supported API. What is observable is the Query Plan tool, which shows you the optimiser’s decisions without exposing the tables behind them.

Does a custom field cost storage when it is empty?

Record storage is charged per record at a flat rate per object row rather than by counting populated fields, so adding a field to an existing object does not change what you are billed for storage.

Why is deleting records slow when inserting them was fast?

Deletes are soft by default. The row stays for the Recycle Bin retention period and still counts toward the object’s size for selectivity purposes. Emptying the bin is what actually removes it.

Does this explain why tests need their own data?

Partly. Test isolation is a platform rule rather than a storage one, but the reason it is enforced so strictly is the same: your test must not depend on the state of a shared environment.

Is Hyperforce a different architecture?

Hyperforce changes the infrastructure the platform runs on, public cloud regions rather than Salesforce-operated data centres, not the multi-tenant data model described here. The storage model and the limits that follow from it are unchanged.


Reference: The Force.com Multitenant Architecture whitepaper, Best Practices for Deployments with Large Data Volumes, and Execution Governors and Limits, Salesforce.