Platform Capabilities & Constraints
The Salesforce platform is a shared multi-tenant environment. Every architectural decision must account for governor limits, shared resource constraints, and the boundaries between what the platform can and cannot do. The CTA who understands these constraints builds solutions that scale; the one who ignores them builds solutions that break.
Multi-Tenant Architecture
Salesforce runs on a shared infrastructure model where all customers (tenants) share the same compute, storage, and network resources. Governor limits exist to prevent any single tenant from monopolizing shared resources.
flowchart TD
subgraph Tenants["Customer Orgs (Tenants)"]
T1["Tenant A<br/>(OrgID: 00D1)"]
T2["Tenant B<br/>(OrgID: 00D2)"]
T3["Tenant C<br/>(OrgID: 00D3)"]
end
subgraph Kernel["Multitenant Kernel (Application Runtime)"]
MDE["Metadata Engine<br/>Reads UDD at runtime"]
QO["Query Optimizer<br/>Adds OrgID to every query"]
GOV["Governor Limit<br/>Enforcement"]
CACHE["Metadata Cache<br/>Per-org optimization"]
end
subgraph Data["Shared Database Layer"]
UDD["Universal Data Dictionary<br/>(metadata tables)"]
DT["Data Tables<br/>(all tenants, OrgID-partitioned)"]
IDX["Custom Indexes<br/>(per-tenant)"]
end
subgraph Infra["Infrastructure (Hyperforce)"]
AZ1["Availability Zone 1"]
AZ2["Availability Zone 2"]
end
T1 --> MDE
T2 --> MDE
T3 --> MDE
MDE --> QO
QO --> GOV
GOV --> CACHE
CACHE --> UDD
CACHE --> DT
DT --> IDX
Data --> AZ1
Data --> AZ2
style Tenants fill:#264653,color:#fff
style Kernel fill:#2d6a4f,color:#fff
style Data fill:#457b9d,color:#fff
style Infra fill:#e9c46a,color:#000
The kernel reads tenant-specific metadata from the Universal Data Dictionary (UDD) at runtime to dynamically construct each tenant’s application, business logic, and APIs. Every database query is automatically scoped by OrgID, ensuring strict tenant isolation at the data layer. Custom objects, fields, triggers, and validation rules are all stored as metadata — not compiled code — which is why Salesforce can deploy platform upgrades without affecting tenant customizations.
Multi-Tenancy Implications
| Aspect | Implication for Architects |
|---|---|
| Shared database | Cannot run arbitrary SQL; must use SOQL/SOSL within limits |
| Shared compute | CPU time limits per transaction; no long-running processes |
| Shared network | API call limits per 24-hour period; callout time limits |
| No raw file system | Cannot write to disk; must use ContentDocument or external storage |
| No custom OS processes | Cannot run background daemons; must use platform async frameworks |
| Metadata-driven | Custom objects, fields, and configuration stored as metadata; schema changes are API calls |
| Automatic upgrades | Three releases per year; cannot defer; must test in advance |
The architect’s mindset
Multi-tenancy is not a limitation to work around — it is the foundation that provides automatic scaling, zero-downtime upgrades, and enterprise security at commodity pricing. The CTA should embrace it and only go off-platform when the platform genuinely cannot serve the requirement.
Governor Limits Reference
Per-Transaction Limits (Synchronous)
| Resource | Limit | Notes |
|---|---|---|
| SOQL queries | 100 | Per Apex transaction |
| Records retrieved (SOQL) | 50,000 | Per transaction |
| SOSL searches | 20 | Per transaction |
| Records retrieved (SOSL) | 2,000 | Per transaction |
| DML statements | 150 | Per transaction |
| Records processed (DML) | 10,000 | Per transaction |
| Callouts | 100 | Per transaction |
| Callout timeout | 120 seconds (total) | Max 120s per callout; 120s total |
| CPU time | 10,000 ms | Per transaction |
| Heap size | 6 MB | Per synchronous transaction |
| Future calls | 50 | Per transaction |
| Queueable jobs | 50 | Per transaction |
| Email invocations | 10 | Per transaction |
| Push notification calls | 10 | Per transaction |
| EventBus.publish | 150 | Per transaction |
Per-Transaction Limits (Asynchronous)
| Resource | Limit | Notes |
|---|---|---|
| SOQL queries | 200 | Double the synchronous limit |
| Records retrieved (SOQL) | 50,000 | Same as synchronous |
| CPU time | 60,000 ms | 6x the synchronous limit |
| Heap size | 12 MB | Double the synchronous limit |
Per-24-Hour Limits
| Resource | Limit | Notes |
|---|---|---|
| API calls | 100,000+ | Base + per-user allocation (varies by edition) |
| Async Apex executions | 250,000 or 200 x licenses | Whichever is greater |
| Batch Apex jobs | 100 queued or active | At any one time |
| Future calls | 250,000 or 200 x licenses | Whichever is greater |
| Platform Events published | Based on entitlement | Varies; default 100K/day for most editions |
| Outbound emails | 5,000 external recipients/day (single + mass combined) | Per-blast limit: 500 (EE), 1,000 (UE/PE). Daily cap shared across single and mass email |
| Scheduled Apex jobs | 100 | Concurrent scheduled jobs |
Data and Storage Limits
| Resource | Limit | Notes |
|---|---|---|
| Custom objects | 200-2,000 | Varies by edition |
| Custom fields per object | Up to 500 | Varies by edition and field type — consult ‘Custom Fields Allowed Per Object’ help article for precise limits |
| Data storage | 10 GB base + per-user | See [[01-system-architecture/org-strategy |
| File storage | 10 GB base + per-user | See [[01-system-architecture/document-management |
| Record size | ~8 KB per record | Aggregate of all field values |
| SOQL query length | 100,000 characters | Including WHERE, ORDER BY |
| Formula field size | 5,000 characters compiled | Complex formulas may exceed this |
Synchronous vs Asynchronous Limits — Visual Comparison
flowchart LR
subgraph SYNC["Synchronous Context"]
direction TB
S1["SOQL Queries: 100"]
S2["CPU Time: 10,000 ms"]
S3["Heap Size: 6 MB"]
S4["DML Statements: 150"]
S5["Callouts: 100"]
end
subgraph ASYNC["Asynchronous Context"]
direction TB
A1["SOQL Queries: 200 (2x)"]
A2["CPU Time: 60,000 ms (6x)"]
A3["Heap Size: 12 MB (2x)"]
A4["DML Statements: 150 (same)"]
A5["Callouts: 100 (same)"]
end
SYNC -->|"Exceeds limits?"| DEC{"Move to async?"}
DEC -->|"Yes"| ASYNC
DEC -->|"Still exceeds"| OFF["Off-Platform<br/>Processing"]
style SYNC fill:#9d0208,color:#fff
style ASYNC fill:#2d6a4f,color:#fff
style OFF fill:#e9c46a,color:#000
The async escalation path
When a synchronous transaction hits governor limits, the first response should be to evaluate async processing. Async gives 2x SOQL, 6x CPU time, and 2x heap. If async limits are still insufficient, that is the signal to move off-platform. This escalation path — sync, then async, then off-platform — is a pattern CTA judges expect to see.
Limits you will hit
The limits CTAs most frequently encounter in real-world scenarios: (1) 100 SOQL per synchronous transaction — poor code design or AppExchange packages that query in loops, (2) 10,000 DML records — bulk data operations, (3) API calls per 24 hours — integration-heavy architectures, (4) CPU time — complex Flow automation chains.
On-Platform vs Off-Platform Decision Framework
The most fundamental system architecture question: should this capability run on the Salesforce platform or somewhere else?
Decision Matrix
| Factor | On-Platform | Off-Platform |
|---|---|---|
| Data ownership | Data is primarily Salesforce data | Data lives in external systems |
| User context | Users are already in Salesforce | Users are not Salesforce users |
| Transaction complexity | Within governor limits | Exceeds governor limits |
| Processing time | Under 10 seconds (sync) or 60 seconds (async) | Long-running processes (minutes to hours) |
| Custom UI | Lightning components are sufficient | Complex UI beyond Lightning capabilities |
| Device integration | Standard browser/mobile | IoT, sensors, specialized hardware |
| Compute requirements | Light computation | Heavy computation, ML model training |
| Real-time streaming | Platform Events (limited) | Kafka, Kinesis, or custom streaming |
| Data volume | Under LDV thresholds | Billions of records, data lake/warehouse |
| Regulatory | Standard compliance | Specialized compliance (PCI DSS, HIPAA BAA) |
On-Platform vs Off-Platform Architecture
flowchart TD
subgraph ONP["On-Platform (Salesforce)"]
direction TB
DEC["Declarative<br/>Flows, Validation Rules,<br/>Formulas, Page Layouts"]
CODE["Custom Code<br/>Apex, LWC, SOQL,<br/>Visualforce"]
ASYNC2["Async Processing<br/>Batch, Queueable,<br/>Future, Scheduled"]
PE["Event-Driven<br/>Platform Events, CDC,<br/>Outbound Messages"]
end
subgraph OFFP["Off-Platform"]
direction TB
MULE["MuleSoft / Integration<br/>Complex orchestration,<br/>API management"]
HEROKU["Heroku / Functions<br/>Custom compute,<br/>worker processes"]
CLOUD["AWS / GCP / Azure<br/>Heavy compute, ML,<br/>data pipelines"]
CUSTOM["Custom Web App<br/>Complex UI,<br/>non-SF users"]
end
SF["Salesforce Platform"] --> ONP
SF <-->|"APIs"| OFFP
style ONP fill:#2d6a4f,color:#fff
style OFFP fill:#457b9d,color:#fff
style SF fill:#264653,color:#fff
Decision Flowchart
flowchart TD
A[New Capability] --> B{Does it primarily<br/>serve Salesforce users?}
B -->|Yes| C{Within governor<br/>limits?}
B -->|No| D[Off-Platform<br/>Consider integration back]
C -->|Yes| E{Standard platform<br/>features sufficient?}
C -->|No| F{Can async processing<br/>solve the constraint?}
E -->|Yes| G[On-Platform<br/>Declarative]
E -->|No| H{Can Apex/LWC<br/>solve it?}
F -->|Yes| I[On-Platform<br/>Async Apex]
F -->|No| D
H -->|Yes| J[On-Platform<br/>Custom Code]
H -->|No| K{Is it a UI<br/>requirement?}
K -->|Yes, complex UI| L[Off-Platform UI +<br/>Salesforce API]
K -->|No, processing| D
style G fill:#2d6a4f,color:#fff
style I fill:#2d6a4f,color:#fff
style J fill:#457b9d,color:#fff
style D fill:#9d0208,color:#fff
style L fill:#9d0208,color:#fff
Asynchronous Apex Patterns
When synchronous processing hits governor limits, async patterns provide higher limits and background processing.
Async Pattern Comparison
| Pattern | Max Execution Time | Use Case | Chaining | Callouts | State |
|---|---|---|---|---|---|
| Future Methods | 60 seconds | Simple fire-and-forget | No | Yes (100) | No (primitives only) |
| Queueable Apex | 60 seconds | Complex async with state | Yes (default depth 5, configurable via AsyncOptions and MaximumQueueableStackDepth — Spring ‘25+) | Yes (100) | Yes (serializable) |
| Batch Apex | 60 seconds per batch | Large data processing | Yes (via finish) | Yes (per execute) | Limited (Database.Stateful) |
| Schedulable Apex | N/A (calls other async) | Time-based scheduling | Via Queueable/Batch | Via called method | N/A |
When to Use Each Pattern
flowchart TD
A[Need Async Processing] --> B{Data volume?}
B -->|Small: single record<br/>or small batch| C{Need state<br/>or chaining?}
B -->|Large: thousands<br/>to millions| D[Batch Apex]
C -->|No, simple fire-and-forget| E[Future Method]
C -->|Yes, need to track<br/>progress or chain| F[Queueable Apex]
D --> G{Need scheduled<br/>execution?}
F --> G
G -->|Yes| H[Schedulable Apex<br/>to invoke Batch/Queueable]
G -->|No| I[Execute directly]
style E fill:#2d6a4f,color:#fff
style F fill:#457b9d,color:#fff
style D fill:#9d0208,color:#fff
style H fill:#e9c46a,color:#000
Batch Apex Design Considerations
| Consideration | Recommendation |
|---|---|
| Scope size | Start with 200; adjust based on processing complexity |
| Error handling | Implement Database.Stateful to track errors across batches |
| Retry logic | Build retry capability for failed batches |
| Monitoring | Use AsyncApexJob for status; consider custom monitoring object |
| Testing | Test with 200+ records; test start, execute, finish independently |
| Chaining | Chain to follow-up batch in finish method for multi-step processing |
| Governor limits | Each execute invocation gets its own governor limit context |
Queueable over Future
Queueable Apex is the modern replacement for Future methods. It supports passing complex objects (not just primitives), chaining, and monitoring through AsyncApexJob. Default to Queueable unless you have a specific reason to use Future (such as calling from a trigger where simplicity is preferred).
Platform Events and Change Data Capture
Platform Events
Platform Events are a pub/sub messaging system built into the Salesforce platform, based on Apache Kafka under the hood.
| Feature | Detail |
|---|---|
| Publish | Apex, Flow, API, Process Builder |
| Subscribe | Apex triggers, Flow, Lightning components, external systems (CometD/gRPC) |
| Delivery | At-least-once (high-volume) or at-most-once |
| Retention | 72 hours (standard), configurable with add-on |
| Volume | Based on entitlement (typically 100K+/day) |
| Replay | ReplayId for event replay from a point in time |
| Transaction boundary | Published events are committed independently of the DML transaction |
Change Data Capture (CDC)
CDC publishes events when Salesforce records are created, updated, deleted, or undeleted.
| Feature | Detail |
|---|---|
| Events generated | Create, Update, Delete, Undelete |
| Configuration | Select objects to track in Setup |
| Payload | Changed fields only (delta), with header metadata |
| Subscription | Same as Platform Events (CometD, Apex triggers, gRPC) |
| Use case | Near-real-time data synchronization to external systems |
| Gap events | Published when events are missed (e.g., during maintenance) |
When to Use Platform Events vs CDC
| Scenario | Platform Events | CDC |
|---|---|---|
| Custom business events | Yes | No (CDC is record-change only) |
| Record change notifications | Can build manually | Automatic |
| Integration data sync | Manual event design | Automatic field-level changes |
| Audit trail | Custom design | Automatic change tracking |
| Custom event payloads | Full control | Salesforce-defined schema |
Big Objects
Big Objects are designed for storing and accessing massive datasets (billions of records) on the Salesforce platform without consuming standard data storage.
Big Object Characteristics
| Feature | Detail |
|---|---|
| Storage | Separate from standard data storage |
| Record capacity | Billions of records |
| Query | Standard SOQL on indexed fields (Async SOQL retired as of Summer ‘25); limited filtering (composite key only) |
| DML | insertImmediate (async bulk insert) |
| Relationships | Lookup to standard/custom objects |
| Reporting | Not available in standard reports |
| Automation | Cannot trigger workflows, flows, or Apex triggers |
| Use cases | Audit logs, historical data, IoT telemetry, transactional archives |
Big Object limitations
Big Objects are not a drop-in replacement for standard objects. You cannot use them in standard reports, dashboards, list views, or most declarative tools. They require SOQL queries on the composite key, which means you must know what you are querying for — they do not support ad-hoc querying. Design the composite key carefully based on access patterns.
When Big Objects Make Sense
- Archiving historical records (old cases, audit logs, transaction history)
- Storing IoT or telemetry data that arrives at high volume
- Compliance data that must be retained for years but rarely queried
- Pre-computed analytics data for dashboards
Custom Metadata Types vs Custom Settings
Both store configuration data, but they serve different purposes and have different deployment characteristics.
| Feature | Custom Metadata Types | Custom Settings (Hierarchy) | Custom Settings (List) |
|---|---|---|---|
| Deployable | Yes (metadata API, change sets) | No (data, must be loaded per env) | No (data, must be loaded per env) |
| Packageable | Yes | No | No |
| SOQL queries | No (uses getInstance, no SOQL limit) | No (uses getInstance, no SOQL limit) | No (uses getInstance, no SOQL limit) |
| Apex test visibility | Available without SeeAllData | Requires SeeAllData or setup in test | Requires SeeAllData or setup in test |
| Per-user/profile values | No | Yes (hierarchy) | No |
| Volume | Low (configuration) | Low (configuration) | Low-Medium |
| Use case | App configuration, mapping tables, feature flags | Per-user/profile settings, feature toggles | Lookup/reference data |
Default to Custom Metadata Types
For CTA exam scenarios, Custom Metadata Types should be your default recommendation for configuration data. The key advantage is deployability — they move through change sets, sandboxes, and packaging like metadata, not data. Custom Settings require manual data loading in each environment, which is error-prone and breaks CI/CD.
Platform Feature Matrix
A quick reference for which platform capabilities are available for common architectural patterns.
| Capability | Declarative | Apex | API | External |
|---|---|---|---|---|
| Record CRUD | Flows, Process Builder | Full CRUD | REST/SOAP | Via API |
| Complex logic | Flow decisions | Full programming | N/A | Full programming |
| Scheduled execution | Scheduled Flows | Schedulable Apex | N/A | Cron jobs |
| Bulk processing | Bulk Data Load | Batch Apex | Bulk API | ETL tools |
| External callouts | Flow HTTP callout | Apex HTTP | N/A | Direct |
| Email sending | Email Alerts | Apex Messaging | API | External email service |
| File processing | Limited | ContentVersion Apex | Content API | External processing |
| PDF generation | Limited | Visualforce renderAs | N/A | External service |
| Complex calculations | Formulas (limited) | Apex Math | N/A | External compute |
| ML / AI | Einstein (managed) | Einstein API | Einstein API | Custom ML |
Related Topics
- Org Strategy — Edition-specific limits and storage
- Decision Guides — On vs off platform decision flowchart
- Trade-Offs — On-platform vs off-platform trade-off analysis
- Licensing — License types affect available governor limits
- Data Architecture — LDV strategies and Big Object usage
- Integration — API limits and integration patterns
Sources
- Salesforce Developer Docs: Execution Governors and Limits
- Salesforce Developer Docs: Platform Events
- Salesforce Developer Docs: Change Data Capture
- Salesforce Developer Docs: Big Objects
- Salesforce Developer Docs: Custom Metadata Types
- Salesforce Developer Docs: Asynchronous Apex
- Salesforce Architects: Platform Multitenant Architecture
- Salesforce Architects: Asynchronous Processing Decision Guide
- Salesforce Well-Architected: Trusted