[usage] Persist usage reports in database#11343
Conversation
e7340d6 to
31c0b55
Compare
| return "d_b_workspace_instance_usage" | ||
| } | ||
|
|
||
| func CreateUsageRecords(ctx context.Context, conn *gorm.DB, report map[AttributionID][]WorkspaceInstanceForUsage) error { |
There was a problem hiding this comment.
I'd suggest the DB layer only takes []WorksapceInstanceForUsage to store, rather than the map, and persists these. The re-mapping can be done in the controller layer. This allows us to keep the db package slightly more general.
There was a problem hiding this comment.
Or actually, it should take []WorkspaceInstanceUsage
There was a problem hiding this comment.
done. The conversion between reports and []WorkspaceInstanceUsage is done in the reconciler.
| // the same workspace again | ||
| ID: instanceID, | ||
| UsageAttributionID: teamAttributionID, | ||
| WorkspaceClass: "default", |
There was a problem hiding this comment.
There should be a constant for this default value
There was a problem hiding this comment.
no longer required now that the CreateUsageRecords function takes WorkspaceInstanceUsage structs directly.
| } | ||
|
|
||
| func TestCanHandleMultipleReportsWithDuplicateEntries(t *testing.T) { | ||
| conn := dbtest.ConnectForTests(t) |
There was a problem hiding this comment.
I'd recommend moving the conn into each test scenario, to ensure that after each test the data is deleted before the next test run
| } | ||
| log.Infof("Wrote usage report into %s", filepath.Join(dir, stat.Name())) | ||
|
|
||
| db.CreateUsageRecords(ctx, u.conn, report) |
| for _, scenario := range scenarios { | ||
| t.Run(scenario.Name, func(t *testing.T) { | ||
| for _, report := range scenario.Reports { | ||
| require.NoError(t, db.CreateUsageRecords(ctx, conn, report)) |
There was a problem hiding this comment.
You may need the helpers from dbtest which add hooks into the created records to delete them once the test completes. See dbtest.CreateWorkspace for example
There was a problem hiding this comment.
I've added t.Cleanup calls to each subtest to delete the usage records between each subtest.
There was a problem hiding this comment.
31c0b55 to
8cf9560
Compare
| conn := dbtest.ConnectForTests(t) | ||
| t.Run(scenario.Name, func(t *testing.T) { | ||
| t.Cleanup(func() { | ||
| require.NoError(t, conn.Where("1=1").Delete(&db.WorkspaceInstanceUsage{}).Error) |
There was a problem hiding this comment.
This was the cause of the problems with flaky tests. When more tests exist and do this, it deletes the data needed for other runs of the test. That's why the current approach adds a hook for each ID created to only deleted the IDs created in each test.
There was a problem hiding this comment.
🤔
None of these tests call t.Parallel, so AIUI the tests in this file will run in series, and within each test each subtest will also run in series. So where is the risk of having a subtest clearing the table after it finishes?
Is it tests running in different packages (which will be run in parallel by go test) that is the concern here?
There was a problem hiding this comment.
But tests in different files will be run concurrently. At some point, we'll add a test which also interacts with the WorkspaceInstanceUsage table and we'll be in trouble. The problem comes from the following interleaving:
test1: Create 10 records
test2: create 2 records
test1: Check we've got 10 records - fail, got 12
There was a problem hiding this comment.
The exact same issue happened previously, we've added tests for d_b_workspace_instance and then we also added tests for the controller (at higher level) which also created d_b_workspace_instance records
There was a problem hiding this comment.
#10971 has extra context on this, including a link to some literature
There was a problem hiding this comment.
But tests in different files will be run concurrently.
I don't think that's correct. See for example here:
By default, execution of test code using the testing package will be done sequentially. However, note that it is only the tests within a given package that run sequentially.
If tests from multiple packages are specified, the tests will be run in parallel at the package level. For example, imagine there are two packages, package a and package b. The test code in package a will be run sequentially, and the test code in package b will also be run sequentially. However, the tests for package a and package b will be run in parallel. Let’s look more closely into how these will be run in parallel.
So I guess I'm struggling to understand how test cleanup code within a package can interfere with other tests in the same package if there is no t.Parallel anywhere. Maybe there is a race in actually deleting the rows from the database?
There was a problem hiding this comment.
The issues we observed were across different packages. And the same will happen here once we add proper tests to the controller package.
There was a problem hiding this comment.
test cleanup changed to only remove records that were created by each test.
1f2fc4c to
cdaf021
Compare
|
started the job as gitpod-build-af-store-usage-data.7 because the annotations in the pull request description changed |
Add a GORM type that represents the "d_b_workspace_instance_usage" table and methods for working with it.
cdaf021 to
844259b
Compare
| ) | ||
|
|
||
| type WorkspaceInstanceUsage struct { | ||
| WorkspaceID uuid.UUID `gorm:"primary_key;column:workspaceId;type:char;size:36;" json:"workspaceId"` |
There was a problem hiding this comment.
Does it make sense to merge the other PR with the rename into this one...? I though it had already been merged.
💡 Or has the table defintion, but not the Go struct?
|
Doing a quick test... |
|
Works as advertised. I understood that credits and generationId will be set later. Only thing I noticed is that @andrew-farries Any idea why that might be? |
Rename the misnamed primary key `workspaceId` -> `instanceId`. Usage based billing is unreleased so just drop the table and recreate it - no data to migrate. Also add index names.
In the case where the instance has stopped.
373118e to
b9016e9
Compare
There was a problem hiding this comment.
Note that the current preview env is in an undefined state.
But I applied the migration manually, ran the test and 🎉 : it worked. 🧘

|
/werft run with-clean-slate-deployment=true with-preview=true 👍 started the job as gitpod-build-af-store-usage-data.12 |
|
/hold this is blocking the merge queue of a fix that fixes the merge queue 😢 I'll unhold when I've merged |
|
/unhold You will need to restart the werft job. Apologies for the downtime 🤦 |
|
/werft run with-clean-slate-deployment=true with-preview=true 👍 started the job as gitpod-build-af-store-usage-data.13 |
|
@andrew-farries What about runnign |
|
started the job as gitpod-build-af-store-usage-data.14 because the annotations in the pull request description changed |
|
/unhold |

Description
Store the usage report in the database table created in #11311 whenever usage reconciliation runs.
creditsUsedorgenerationIdfieldsRelated Issue(s)
Part of #10323
How to test
Run the usage component against the database in this PR's preview environment:
components/usage/config.jsonfor the usage controller to run every 10s.(get the password from
serverenvironment)mycliand observe the data ind_b_workspace_instance_usage.Unit tests.
Release Notes
Werft options: