State of Tokenomics: September 2026
Read the report
Assets
This work is licensed under CC BY 4.0 - Read how use or adaptation requires attribution

Manage Version Sharding with Multiple FOCUS Data Generator Exports

Summary: Learn what version sharding is and why it occurs as FOCUS versions and data generator adoption advances. Practitioners can visualize mock scenarios of how FOCUS versions stack across disparate data generators. Use the three-step process outlined to begin managing multiple versioned datasets.

The FinOps Open Cost and Usage Specification (FOCUS) unifies cost and usage data from many data generators into one consistent dataset. Data generators are the cloud, SaaS, and other technology providers that produce billing data.

FOCUS is also an open specification that grows with its community and data generator adoption, and a new version is ratified roughly every six months. Since each data generator upgrades on its own schedule, most practitioners will eventually hold FOCUS data from several versions in the same data warehouse.

We call this version sharding.

What Is FOCUS Version Sharding?

Version sharding means practitioners having to build and manage one unified view, combining data generator exports that use different versions of the specification. It can look like the undoing of a specification meant to simplify, with different columns and rules in each dataset and more time spent reconciling data than doing FinOps.

Managing version sharding isn’t as complicated as it seems. FOCUS versions nest, and changes between them are classified and documented. FinOps practitioners can learn and use the following approach: record the data generator and FOCUS version on every row, query the columns all generators share, add the rest on your own schedule, and document a decision for every gap.

How FOCUS Versions Nest

A common assumption is that FOCUS versions behave like operating system releases: different schemas, potentially major changes, and a reconciliation project between you and various data generator exports. Instead, each FOCUS version contains nearly everything in the one before it, like the layers of an onion expanding outward.

Of the 43 columns defined in v1.0, 41 are still present as of v1.4. Both of the removals were deprecated a full release before v1.4 so that users could plan ahead.

Changes to FOCUS Are Bounded and Classified

Here are three features of how FOCUS manages change to keep its shared core stable.

Every Change Is Classified by Its Impact on Queries

FOCUS contributors classify each change in each release into one of three categories:

  • Compatible: Existing queries keep working.
  • Migration Compatible: Queries need a defined, documented change.
  • Incompatible: Queries break with no migration path.

The project defined this classification in v1.2 as a governance commitment and first applied it in the v1.3 changelog. It lives in the project’s specification change guidelines and is applied release by release. Both the v1.3 and v1.4 changelogs record no Incompatible changes. The record is short, and anyone can check it.

The classification also shows where to focus: Migration Compatible changes. Compatible changes can still require work, as the section on definitions below explains.

The Shared Core Is the Whole Original Specification

No column was removed between v1.0 and v1.3, so all 43 columns defined in v1.0 are present in every version through v1.3. Version 1.4 is the first to remove any, and it removes two.

Column Removal Came with a Full Cycle of Notice

Version 1.4 removed ProviderName and PublisherName because their definitions conflicted with each other. Both were formally deprecated in v1.3, using the Deprecated and PreviousColumnName metadata properties added in v1.2. Their use cases moved to ServiceProviderName, HostProviderName, InvoiceIssuerName, and DataGenerator metadata. Generators had a full release cycle, roughly six months, with old and new columns present together and a documented mapping between them. The removal affects practitioners only when a data generator upgrades to v1.4.

The project settled its vocabulary up to and including v1.0. Since that baseline, the column record has been additive, with one deprecate-then-remove cycle across four releases. Version 1.4 also retired three attributes and two glossary entries without a deprecation cycle, all classified Migration Compatible.

Version Sharding Visualized

The walkthrough below follows three data generators, each exporting FOCUS datasets on a different version. The counter under the grid tracks the columns you can query across all three. It holds steady as the generators drift four releases apart, drops once when the v1.4 removal lands, and then flags two columns that kept their names but changed their meaning.

Setup Step 1 of 13

Two generators, one schema, day one

A and B both emit FOCUS 1.0. Same column names, same definitions, no mapping layer in between. A cross-generator total works immediately.

Generator C Step 2 of 13

A non-native source joins through the FOCUS Converter

C does not emit FOCUS itself. Its proprietary export is converted to the 1.0 shape, history included, because you kept the native data. That is backfill outcome two: re-derived from the native export.

Generator A Step 3 of 13

A moves to FOCUS 1.1

Two columns you have never had arrive on A. Nothing breaks: every 1.0 column is still there, because no column was removed between 1.0 and 1.3. But the new columns have no history behind them.

You Step 4 of 13

Decide about history, column by column

One new column derives from columns you already hold, so you backfill it deterministically. The other was never computed upstream, so it is null forever. Write the reason down beside the column: the failure is not choosing badly, it is letting nulls happen to you.

Generator B Step 5 of 13

B jumps straight to 1.2, skipping 1.1

Generators do not move in lockstep, and they do not move one release at a time. B lands four new columns at once and is now a release ahead of A.

You Step 6 of 13

Your floor has not moved, and it is not a thin core

Three generators, three different ceilings, and the floor is still every column FOCUS 1.0 defined. Cross-generator queries run against the floor and cover the full original specification. This is the crawl tier, and it costs you nothing to be on it.

Generator A Step 7 of 13

A catches up to 1.2

A and B now share a ceiling. Per-generator dashboards can use 1.2 columns for both, while cross-generator totals still run at the floor, because C is on 1.0. That is the walk tier: two clearly labelled scopes.

Generator B Step 8 of 13

B leaps to 1.4, skipping 1.3 entirely

Four more columns at once, and B is now two releases beyond A and four beyond C. This is the spread the whole article is about, and so far every column you already had is still exactly where it was.

Removed at 1.4 Step 9 of 13

The same upgrade brings the first removal since v1.0

ProviderName and PublisherName are gone from B, their definitions having conflicted with each other. Both were deprecated a release earlier in 1.3, with their use cases moved to ServiceProviderName, HostProviderName, InvoiceIssuerName and DataGenerator metadata. A full release cycle of notice.

You Step 10 of 13

Which puts a hole in your floor, and it is a visible one

A cross-generator query on ProviderName now returns B as null. Your floor is the minimum declared version minus anything a higher generator removed. History keeps the old columns and the successors carry the meaning forward, so repoint the query.

Watch out Step 11 of 13

And two columns kept their names while changing what they count

BilledCost and EffectiveCost were both heavily revised at 1.4. Same names, same positions, different definitions. Nesting protects your queries, not your numbers, and nothing in the schema will tell you which is which. Read the changed-columns section of the changelog for every version you cross.

Generator A Step 12 of 13

A moves to 1.3

A keeps ProviderName and PublisherName, because they are deprecated at 1.3 rather than removed. Two generators, same two column names, different status. This is exactly why version belongs in your warehouse as a column.

End state Step 13 of 13

Three ceilings, one floor, and every gap explainable

C is capped at 1.0 by its converter, A is on 1.3, and B is on 1.4 with one column retired and two definitions changed. Four releases of spread and not one silent failure: every gap has a reason you can name. The change still happened, but you saw it coming.

v1.0
v1.1
v1.2
v1.3
v1.4
A
B
C
Cross-generator query surface: 6 of 14 columns · floor is FOCUS 1.0
Cross-generator query surface: 6 of 14 columns · floor is FOCUS 1.0
Cross-generator query surface: 6 of 14 columns · floor is FOCUS 1.0
Cross-generator query surface: 6 of 14 columns · floor is FOCUS 1.0
Cross-generator query surface: 6 of 14 columns · floor is FOCUS 1.0
Cross-generator query surface: 6 of 14 columns · floor is FOCUS 1.0
Cross-generator query surface: 6 of 14 columns · floor is FOCUS 1.0
Cross-generator query surface: 6 of 14 columns · floor is FOCUS 1.0
Cross-generator query surface: 5 of 14 columns · floor is FOCUS 1.0 · minus what 1.4 removed
Cross-generator query surface: 5 of 14 columns · floor is FOCUS 1.0 · minus what 1.4 removed
Cross-generator query surface: 5 of 14 columns · floor is FOCUS 1.0 · 2 of them redefined · minus what 1.4 removed
Cross-generator query surface: 5 of 14 columns · floor is FOCUS 1.0 · 2 of them redefined · minus what 1.4 removed
Cross-generator query surface: 5 of 14 columns · floor is FOCUS 1.0 · 2 of them redefined · minus what 1.4 removed
Collected Via FOCUS Converter Backfilled: derived Backfilled: null, and documented Same name, revised definition Removed at 1.4

Column counts are illustrative, not to scale. Generators are composites, not specific vendors.

Start Here: Record the Generator and Version on Every Row

If you want to begin managing multiple versions of FOCUS across your data generators, the highest-leverage step costs two lines in your ingest process.

FOCUS rows do not carry version information, so hard-code Generator and FocusVersion at ingest time for each generator and each export. Take the version from the generator’s published FOCUS metadata where it exists. Once these are ordinary columns, each of the following questions becomes a simple GROUP BY:

  • Which generators are on which version?
  • Which columns are safe to aggregate across version boundaries?
  • Which caveats belong on which dashboard?
  • Which backfills do you still owe?

Before: Versions never recorded

-- Ingest
INSERT INTO focus_costs
SELECT * FROM read_parquet('s3://billing/gen_a/cost_and_usage/*.parquet');

-- What is my cross-generator spend?
SELECT SUM(EffectiveCost)
FROM focus_costs;
-- 4,182,930.11: one number, with no way to know it spans
-- two different specification versions

-- Which generators are on which version?
-- Not answerable. Go ask each vendor.

After: Two columns hard-coded on every row

-- Ingest (hard-coded values for this generator and export)
INSERT INTO focus_costs
SELECT d.*,
       'gen_a' AS Generator,   -- hard-coded
       '1.3'   AS FocusVersion -- hard-coded
FROM read_parquet('s3://billing/gen_a/cost_and_usage/*.parquet') d;

-- Repeat this pattern for every other dataset the generator emits

-- What is my cross-generator spend?
SELECT FocusVersion, SUM(EffectiveCost)
FROM focus_costs
GROUP BY 1;
-- 1.2    3,004,117.55
-- 1.3    1,178,812.56
-- Same total, with the version boundary now visible

-- Which generators are on which version?
SELECT FocusVersion, Generator, COUNT(*)
FROM focus_costs
GROUP BY 1, 2;

Next: Climb FOCUS Versions Deliberately

Follow each step one at a time and apply them to one question: how much of the version difference does your data warehouse absorb before a query runs?

Step 1: Query the Floor

Find the lowest FOCUS version across your generators, then subtract any columns that a generator on a higher version has removed. The result is your floor. Run every cross-generator query against it.

Because no column was removed between v1.0 and v1.3, the subtraction matters only when a dataset is on v1.4. Within the floor, you get a defensible cross-generator total from day one, with known exclusions.

Step 2: Build Two Tiers

Use the identified floor columns to aggregate across generators, and use ceiling columns, the full column set of each generator’s version, to go deeper on one generator at a time. Label the two dashboard tiers clearly so each is explicit about its scope.

Step 3: Normalize Up to the Ceiling

Bring older-version data up to newer versions. Declare a backfill policy for each column, using the four outcomes in the next section.

When generators begin delivering recency metadata, DatasetInstanceComplete and TimeSectorComplete will show whether a period is final. Until then, record your own load dates so data consumers can tell a true zero from data that has not arrived yet.

Pro tip: Practitioners often get caught here. A column defined in a version is not necessarily populated by every generator. Conditional and Recommended columns can be legitimately null, depending on a generator’s business model. The version number tells you the schema. Where the generator publishes them, two documents tell you the rest:

  • The Column Mapping Reference shows which FOCUS columns the generator derives and from which native fields.
  • The Conformance Gap Report lists where the generator departs from the specification.

A column that is correctly null because its condition never applies appears in the Column Mapping Reference, not in the Conformance Gap Report. Read both.

Decide What to Do about History, Column by Column

When a data generator moves up a version, you inherit new columns with no history behind them. Each column has one of four outcomes. Choose one explicitly for each column and record the decision.

  1. Derived. The specification defines the arithmetic, and you already hold every input. ListCost must equal ListUnitPrice multiplied by PricingQuantity when both are non-null, and the same relationship holds for ContractedCost and ContractedUnitPrice. This category is smaller than it looks. All five of these columns date to v1.0, so they arrive together rather than at a version boundary. That makes derivation the path for a generator that starts emitting FOCUS partway through your history, not for one that moves up a version. Watch for the near miss: PricingCurrencyEffectiveCost must be the PricingCurrency-denominated equivalent of EffectiveCost. That reads like arithmetic, but the specification carries no exchange rate, so this column belongs in the next category.
  2. Re-derived from the native export. You kept the generator’s proprietary data, and the new FOCUS column maps from a field the generator always had. This is how a source that was not FOCUS-native gains history retroactively. The generator’s Column Mapping Reference often already names the native field behind each FOCUS column.
  3. Re-requested. Only the generator can produce the history, by re-emitting it in the new version. Usually it will not. Ask anyway and record the answer, because the answer tells you whether the gap is permanent.
  4. Unrecoverable. No one ever computed the underlying fact. For example, commitment eligibility details for a period before the generator computed eligibility cannot be recovered by any amount of engineering. Record a documented null and scope the business question to the period where data exists.

A mature FOCUS practice still has gaps. It knows exactly which cells are empty and why.

Check Definitions, Not Just Column Names

A column can keep its name across a version boundary and change what it measures. That makes a version move a definition change as well as a schema change, and a diagram of column names cannot show it.

Version 1.4 substantially revised the normative requirements for BilledCost (see the v1.3 and v1.4 definitions) and EffectiveCost (see the v1.3 and v1.4 definitions), including rules for amortizing covering charges and a cross-record sum validation. A covering charge is a purchase whose cost offsets other charges, such as a commitment discount purchase. As a result, SUM(EffectiveCost) across generators on either side of the boundary compares two differently defined quantities, even though both datasets use the same column name.

Watch null rates too. InvoiceId moved from Recommended to Conditional in v1.4, a change the specification classifies as Compatible. Your queries keep parsing, but your alerts may not stay quiet: on a dashboard, a shift in population rate looks identical to a data quality regression.

When a generator moves versions, read the changed-columns section of the FOCUS changelog for every version you are crossing, alongside that generator’s Conformance Gap Report. This matters most as the first v1.4 generators ship. With a written policy in place, you can safely ingest data from generators on mixed versions. To query the specification without reading the full changelog, use the FOCUS Model Context Protocol (MCP) server with your AI assistant.

Four Questions for Your Data Generator’s Account Team

  1. Do you publish FOCUS metadata, and where? Metadata confirms which version you are receiving.
  2. When you move versions, do you re-emit history at the new version, or does the change apply from a cutover date forward?
  3. Where are your Conformance Gap Report and Column Mapping Reference published?
  4. Which FOCUS version are you certified for, which datasets did that certification cover, and is that the version you are emitting?

The last question is easy to skip. FOCUS certification applies per version and per generator, and under the program rules, “No, those are different versions” is a legitimate answer. It just needs to be stated. Coverage is a separate question from version: the currently certifiable version defines a single dataset, so a certificate says nothing about a generator’s Contract Commitment, Invoice Detail, or Billing Period data.

Get Ahead of Version Sharding (and Its Costs)

The fear was that six-month releases would leave practitioners reconciling versions instead of doing FinOps. The record shows two columns removed since v1.0, both with a full cycle of notice and named successors, and no Incompatible changes recorded since the classification began.

Carrying several specification versions still costs more than carrying one, but the cost is bounded, visible, and documented. Before FOCUS, the alternative was maintaining hand-built bridges between many vendors’ private schemas and finding out one had broken from a board deck.

Get started now by stamping the generator and version on every row. Then compute your floor, query against it, and climb on your own schedule.

Related People

Beau Nelford

Beau Nelford

FinOps Foundation