Problem
Lifecycle type (buildpack, docker, cnb) is not stored directly on apps, droplets, or builds. It is derived at runtime by checking for the existence of associated lifecycle data records:
# Current: checks multiple associations
def lifecycle_type
return 'buildpack' if buildpack_lifecycle_data
return 'cnb' if cnb_lifecycle_data
'docker'
end
This causes:
- Expensive list queries -
GET /v3/apps?lifecycle_type=docker requires excluding apps from both buildpack_lifecycle_data and cnb_lifecycle_data tables via subqueries
- Unnecessary eager loading - Presenters load lifecycle data associations just to determine the type string, even when the full lifecycle data is not needed
- Implicit semantics - "docker" is the absence of other types, not an explicit value
Proposed Solution
Add a lifecycle_type VARCHAR column (with index) to apps, droplets, and builds tables.
Deployment Strategy (Two Phases, Zero Downtime)
Deploy 1 (PR: #5065):
- Add nullable columns with concurrent index
- Populate for new records via
AppCreate, V2::AppCreate, and before_create hooks on droplets/builds
- Fallback to association lookup for existing records
- Model validation ensures only valid lifecycle types (buildpack, cnb, docker)
Deploy 2:
- Backfill existing records based on lifecycle data tables
- Make columns non-nullable
- Remove fallback logic
- Optimize
AppListFetcher: subqueries → simple WHERE clause
- Optimize presenters: skip eager-loading lifecycle data for type determination
Impact After Deploy 2
| Area |
Before |
After |
AppListFetcher lifecycle_type filter |
3 subqueries on lifecycle data tables |
1 simple WHERE lifecycle_type = ? |
| Presenters (App, Build, Droplet, Process) |
Eager-load lifecycle data associations to determine type |
Read column value directly |
| Lifecycle type checks in controllers |
Association lookups |
O(1) column read |
| Docker type semantics |
Implicit (absence of other types) |
Explicit column value |
Endpoints That Benefit
Critical: GET /v3/apps with lifecycle_type filter (subquery elimination)
High: All list and detail endpoints for apps, droplets, builds, and processes (presenter eager-load avoidance)
Medium: POST /v3/apps, POST /v3/builds, POST /v3/apps/:guid/actions/start, POST /v3/apps/:guid/actions/restage, POST /v3/spaces/:guid/actions/apply_manifest, POST /v3/droplets/:guid/copy, staging completion (controller-level type checks)
Problem
Lifecycle type (buildpack, docker, cnb) is not stored directly on apps, droplets, or builds. It is derived at runtime by checking for the existence of associated lifecycle data records:
This causes:
GET /v3/apps?lifecycle_type=dockerrequires excluding apps from bothbuildpack_lifecycle_dataandcnb_lifecycle_datatables via subqueriesProposed Solution
Add a
lifecycle_typeVARCHAR column (with index) toapps,droplets, andbuildstables.Deployment Strategy (Two Phases, Zero Downtime)
Deploy 1 (PR: #5065):
AppCreate,V2::AppCreate, andbefore_createhooks on droplets/buildsDeploy 2:
AppListFetcher: subqueries → simpleWHEREclauseImpact After Deploy 2
AppListFetcherlifecycle_type filterWHERE lifecycle_type = ?Endpoints That Benefit
Critical:
GET /v3/appswithlifecycle_typefilter (subquery elimination)High: All list and detail endpoints for apps, droplets, builds, and processes (presenter eager-load avoidance)
Medium:
POST /v3/apps,POST /v3/builds,POST /v3/apps/:guid/actions/start,POST /v3/apps/:guid/actions/restage,POST /v3/spaces/:guid/actions/apply_manifest,POST /v3/droplets/:guid/copy, staging completion (controller-level type checks)