Skip to content

Commit 978877a

Browse files
authored
Store authzIDs directly in order table (#8460)
The `orders` table gets a new column `authzs`, containing a protobuf-encoded list of authorization IDs as int64s. Storing this data directly in the `orders` table will allow us to get rid of the `orderToAuthz2` table, which is large (#8451). There's a new feature flag, `StoreAuthzsInOrders`, which controls whether the SA expects the new column to exist. The SA gracefully handles the case where the new column is NULL, by querying the `orderToAuthz2` table. Eventually all valid orders will have a non-NULL `authzs` column and we can remove the fallback. I removed `orderToModel` because it had only one call site, and that call site only used a subset of fields. This avoids having to update `orderToModel` to know about encoding authzs, only to have that code actually go unused in practice.
1 parent f68edb1 commit 978877a

File tree

11 files changed

+381
-146
lines changed

11 files changed

+381
-146
lines changed

features/features.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ type Config struct {
8181
// during certificate issuance. This flag must be set to true in the
8282
// RA, VA, and WFE2 services for full functionality.
8383
DNSAccount01Enabled bool
84+
85+
// StoreAuthzsInOrders causes the SA to write to the `authzs`
86+
// column in NewOrder and read from it in GetOrder. It should be enabled
87+
// after the migration to add that column has been run.
88+
StoreAuthzsInOrders bool
8489
}
8590

8691
var fMu = new(sync.RWMutex)

sa/database.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,9 @@ func initTables(dbMap *borp.DbMap) {
254254
if !features.Get().StoreARIReplacesInOrders {
255255
tableMap.ColMap("Replaces").SetTransient(true)
256256
}
257+
if !features.Get().StoreAuthzsInOrders {
258+
tableMap.ColMap("Authzs").SetTransient(true)
259+
}
257260

258261
dbMap.AddTableWithName(orderToAuthzModel{}, "orderToAuthz").SetKeys(false, "OrderID", "AuthzID")
259262
dbMap.AddTableWithName(orderFQDNSet{}, "orderFqdnSets").SetKeys(true, "ID")
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-- +migrate Up
2+
-- SQL in section 'Up' is executed when this migration is applied
3+
4+
ALTER TABLE `orders` ADD COLUMN `authzs` blob DEFAULT NULL;
5+
6+
-- +migrate Down
7+
-- SQL section 'Down' is executed when this migration is rolled back
8+
9+
ALTER TABLE `orders` DROP COLUMN `authzs`;

sa/model.go

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"encoding/json"
1010
"errors"
1111
"fmt"
12+
"google.golang.org/protobuf/proto"
1213
"math"
1314
"net/netip"
1415
"net/url"
@@ -346,42 +347,14 @@ type orderModel struct {
346347
BeganProcessing bool
347348
CertificateProfileName *string
348349
Replaces *string
350+
Authzs []byte
349351
}
350352

351353
type orderToAuthzModel struct {
352354
OrderID int64
353355
AuthzID int64
354356
}
355357

356-
func orderToModel(order *corepb.Order) (*orderModel, error) {
357-
// Make a local copy so we can take a reference to it below.
358-
profile := order.CertificateProfileName
359-
replaces := order.Replaces
360-
361-
om := &orderModel{
362-
ID: order.Id,
363-
RegistrationID: order.RegistrationID,
364-
Expires: order.Expires.AsTime(),
365-
Created: order.Created.AsTime(),
366-
BeganProcessing: order.BeganProcessing,
367-
CertificateSerial: order.CertificateSerial,
368-
CertificateProfileName: &profile,
369-
Replaces: &replaces,
370-
}
371-
372-
if order.Error != nil {
373-
errJSON, err := json.Marshal(order.Error)
374-
if err != nil {
375-
return nil, err
376-
}
377-
if len(errJSON) > mediumBlobSize {
378-
return nil, fmt.Errorf("Error object is too large to store in the database")
379-
}
380-
om.Error = errJSON
381-
}
382-
return om, nil
383-
}
384-
385358
func modelToOrder(om *orderModel) (*corepb.Order, error) {
386359
profile := ""
387360
if om.CertificateProfileName != nil {
@@ -391,6 +364,13 @@ func modelToOrder(om *orderModel) (*corepb.Order, error) {
391364
if om.Replaces != nil {
392365
replaces = *om.Replaces
393366
}
367+
if len(om.Authzs) > 0 {
368+
var decodedAuthzs sapb.Authzs
369+
err := proto.Unmarshal(om.Authzs, &decodedAuthzs)
370+
if err != nil {
371+
return nil, err
372+
}
373+
}
394374
order := &corepb.Order{
395375
Id: om.ID,
396376
RegistrationID: om.RegistrationID,

sa/model_test.go

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -223,26 +223,6 @@ func TestModelToOrderBadJSON(t *testing.T) {
223223
test.AssertEquals(t, string(badJSONErr.json), string(badJSON))
224224
}
225225

226-
func TestOrderModelThereAndBackAgain(t *testing.T) {
227-
clk := clock.New()
228-
now := clk.Now()
229-
order := &corepb.Order{
230-
Id: 1,
231-
RegistrationID: 2024,
232-
Expires: timestamppb.New(now.Add(24 * time.Hour)),
233-
Created: timestamppb.New(now),
234-
Error: nil,
235-
CertificateSerial: "2",
236-
BeganProcessing: true,
237-
CertificateProfileName: "phljny",
238-
}
239-
model, err := orderToModel(order)
240-
test.AssertNotError(t, err, "orderToModelv2 should not have errored")
241-
returnOrder, err := modelToOrder(model)
242-
test.AssertNotError(t, err, "modelToOrderv2 should not have errored")
243-
test.AssertDeepEquals(t, order, returnOrder)
244-
}
245-
246226
// TestPopulateAttemptedFieldsBadJSON tests that populating a challenge from an
247227
// authz2 model with an invalid validation error or an invalid validation record
248228
// produces the expected bad JSON error.

sa/proto/sadb.pb.go

Lines changed: 127 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sa/proto/sadb.proto

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
syntax = "proto3";
2+
3+
package sa;
4+
option go_package = "github.com/letsencrypt/boulder/sa/proto";
5+
6+
// Used internally for storage in the DB, not for RPCs.
7+
message Authzs {
8+
repeated int64 authzIDs = 1;
9+
}

sa/sa.go

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/json"
88
"errors"
99
"fmt"
10+
"google.golang.org/protobuf/proto"
1011
"strings"
1112
"time"
1213

@@ -21,6 +22,7 @@ import (
2122
corepb "github.com/letsencrypt/boulder/core/proto"
2223
"github.com/letsencrypt/boulder/db"
2324
berrors "github.com/letsencrypt/boulder/errors"
25+
"github.com/letsencrypt/boulder/features"
2426
bgrpc "github.com/letsencrypt/boulder/grpc"
2527
"github.com/letsencrypt/boulder/identifier"
2628
blog "github.com/letsencrypt/boulder/log"
@@ -486,24 +488,37 @@ func (ssa *SQLStorageAuthority) NewOrderAndAuthzs(ctx context.Context, req *sapb
486488
newAuthzIDs = append(newAuthzIDs, am.ID)
487489
}
488490

491+
// Combine the already-existing and newly-created authzs.
492+
allAuthzIds := append(req.NewOrder.V2Authorizations, newAuthzIDs...)
493+
489494
// Second, insert the new order.
490495
created := ssa.clk.Now()
496+
var encodedAuthzs []byte
497+
var err error
498+
if features.Get().StoreAuthzsInOrders {
499+
encodedAuthzs, err = proto.Marshal(&sapb.Authzs{
500+
AuthzIDs: allAuthzIds,
501+
})
502+
if err != nil {
503+
return nil, err
504+
}
505+
}
506+
491507
om := orderModel{
492508
RegistrationID: req.NewOrder.RegistrationID,
493509
Expires: req.NewOrder.Expires.AsTime(),
494510
Created: created,
495511
CertificateProfileName: &req.NewOrder.CertificateProfileName,
496512
Replaces: &req.NewOrder.Replaces,
513+
Authzs: encodedAuthzs,
497514
}
498-
err := tx.Insert(ctx, &om)
515+
err = tx.Insert(ctx, &om)
499516
if err != nil {
500517
return nil, err
501518
}
502519
orderID := om.ID
503520

504521
// Third, insert all of the orderToAuthz relations.
505-
// Have to combine the already-associated and newly-created authzs.
506-
allAuthzIds := append(req.NewOrder.V2Authorizations, newAuthzIDs...)
507522
inserter, err := db.NewMultiInserter("orderToAuthz2", []string{"orderID", "authzID"})
508523
if err != nil {
509524
return nil, err
@@ -621,21 +636,22 @@ func (ssa *SQLStorageAuthority) SetOrderError(ctx context.Context, req *sapb.Set
621636
if req.Id == 0 || req.Error == nil {
622637
return nil, errIncompleteRequest
623638
}
624-
_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
625-
om, err := orderToModel(&corepb.Order{
626-
Id: req.Id,
627-
Error: req.Error,
628-
})
629-
if err != nil {
630-
return nil, err
631-
}
632639

640+
errJSON, err := json.Marshal(req.Error)
641+
if err != nil {
642+
return nil, err
643+
}
644+
if len(errJSON) > mediumBlobSize {
645+
return nil, fmt.Errorf("error object is too large to store in the database")
646+
}
647+
648+
_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
633649
result, err := tx.ExecContext(ctx, `
634650
UPDATE orders
635651
SET error = ?
636652
WHERE id = ?`,
637-
om.Error,
638-
om.ID)
653+
errJSON,
654+
req.Id)
639655
if err != nil {
640656
return nil, berrors.InternalServerError("error updating order error field")
641657
}

0 commit comments

Comments
 (0)