Skip to content

Commit

Permalink
Undo breaking schema changes
Browse files Browse the repository at this point in the history
The same schema is used for both revisioned and unrevisioned subtrees. The difference is that we always write a revision of 0 in the unrevisioned case, which still means that there will only be a single entry per subtree. This version uses some magic bytes '🙃norevs🙃' as the prefix of the tree description field to determine whether to disable subtree revision writing. This mechanism isn't necessarily final, but does have some benefits over the previous version: a) the same instance of Trillian can use different modes for different logs, b) no DDL updates required on the schema.
  • Loading branch information
mhutchinson committed Nov 16, 2023
1 parent 2e0ce9e commit 09bc384
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 18 deletions.
2 changes: 1 addition & 1 deletion storage/mysql/log_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ type logTreeTX struct {
func (t *logTreeTX) GetMerkleNodes(ctx context.Context, ids []compact.NodeID) ([]tree.Node, error) {
t.treeTX.mu.Lock()
defer t.treeTX.mu.Unlock()
return t.subtreeCache.GetNodes(ids, t.getSubtreesFunc(ctx))
return t.subtreeCache.GetNodes(ids, t.getSubtreesAtRev(ctx, t.readRev))
}

func (t *logTreeTX) DequeueLeaves(ctx context.Context, limit int, cutoffTime time.Time) ([]*trillian.LogLeaf, error) {
Expand Down
3 changes: 2 additions & 1 deletion storage/mysql/schema/storage.sql
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ CREATE TABLE IF NOT EXISTS Subtree(
TreeId BIGINT NOT NULL,
SubtreeId VARBINARY(255) NOT NULL,
Nodes MEDIUMBLOB NOT NULL,
SubtreeRevision INTEGER NOT NULL,
-- Key columns must be in ASC order in order to benefit from group-by/min-max
-- optimization in MySQL.
PRIMARY KEY(TreeId, SubtreeId),
PRIMARY KEY(TreeId, SubtreeId, SubtreeRevision),
FOREIGN KEY(TreeId) REFERENCES Trees(TreeId) ON DELETE CASCADE
);

Expand Down
76 changes: 61 additions & 15 deletions storage/mysql/tree_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,26 @@ import (

// These statements are fixed
const (
insertSubtreeMultiSQL = `REPLACE INTO Subtree(TreeId, SubtreeId, Nodes) ` + placeholderSQL
insertSubtreeMultiSQL = `REPLACE INTO Subtree(TreeId, SubtreeId, Nodes, SubtreeRevision) ` + placeholderSQL
insertTreeHeadSQL = `INSERT INTO TreeHead(TreeId,TreeHeadTimestamp,TreeSize,RootHash,TreeRevision,RootSignature)
VALUES(?,?,?,?,?,?)`

selectSubtreeSQL = `
SELECT x.SubtreeId, Subtree.Nodes
FROM (
SELECT n.TreeId, n.SubtreeId, max(n.SubtreeRevision) AS MaxRevision
FROM Subtree n
WHERE n.SubtreeId IN (` + placeholderSQL + `) AND
n.TreeId = ? AND n.SubtreeRevision <= ?
GROUP BY n.TreeId, n.SubtreeId
) AS x
INNER JOIN Subtree
ON Subtree.SubtreeId = x.SubtreeId
AND Subtree.SubtreeRevision = x.MaxRevision
AND Subtree.TreeId = x.TreeId
AND Subtree.TreeId = ?`

selectSubtreeSQLNoRev = `
SELECT SubtreeId, Subtree.Nodes
FROM Subtree
WHERE Subtree.TreeId = ?
Expand Down Expand Up @@ -124,12 +139,16 @@ func (m *mySQLTreeStorage) getStmt(ctx context.Context, statement string, num in
return s, nil
}

func (m *mySQLTreeStorage) getSubtreeStmt(ctx context.Context, num int) (*sql.Stmt, error) {
return m.getStmt(ctx, selectSubtreeSQL, num, "?", "?")
func (m *mySQLTreeStorage) getSubtreeStmt(ctx context.Context, subtreeRevs bool, num int) (*sql.Stmt, error) {
if subtreeRevs {
return m.getStmt(ctx, selectSubtreeSQL, num, "?", "?")
} else {
return m.getStmt(ctx, selectSubtreeSQLNoRev, num, "?", "?")
}
}

func (m *mySQLTreeStorage) setSubtreeStmt(ctx context.Context, num int) (*sql.Stmt, error) {
return m.getStmt(ctx, insertSubtreeMultiSQL, num, "VALUES(?, ?, ?)", "(?, ?, ?)")
return m.getStmt(ctx, insertSubtreeMultiSQL, num, "VALUES(?, ?, ?, ?)", "(?, ?, ?, ?)")
}

func (m *mySQLTreeStorage) beginTreeTx(ctx context.Context, tree *trillian.Tree, hashSizeBytes int, subtreeCache *cache.SubtreeCache) (treeTX, error) {
Expand All @@ -138,6 +157,9 @@ func (m *mySQLTreeStorage) beginTreeTx(ctx context.Context, tree *trillian.Tree,
klog.Warningf("Could not start tree TX: %s", err)
return treeTX{}, err
}
// If the tree description starts with magic constant then we will only write a single
// revision for each subtree tile, which simplifies the reading query.
subtreeRevs := !strings.HasPrefix(tree.Description, "🙃norevs🙃")
return treeTX{
tx: t,
mu: &sync.Mutex{},
Expand All @@ -146,6 +168,7 @@ func (m *mySQLTreeStorage) beginTreeTx(ctx context.Context, tree *trillian.Tree,
treeType: tree.TreeType,
hashSizeBytes: hashSizeBytes,
subtreeCache: subtreeCache,
subtreeRevs: subtreeRevs,
writeRevision: -1,
}, nil
}
Expand All @@ -161,16 +184,17 @@ type treeTX struct {
hashSizeBytes int
subtreeCache *cache.SubtreeCache
writeRevision int64
subtreeRevs bool
}

func (t *treeTX) getSubtrees(ctx context.Context, ids [][]byte) ([]*storagepb.SubtreeProto, error) {
func (t *treeTX) getSubtrees(ctx context.Context, treeRevision int64, ids [][]byte) ([]*storagepb.SubtreeProto, error) {
klog.V(2).Infof("getSubtrees(len(ids)=%d)", len(ids))
klog.V(4).Infof("getSubtrees(")
if len(ids) == 0 {
return nil, nil
}

tmpl, err := t.ts.getSubtreeStmt(ctx, len(ids))
tmpl, err := t.ts.getSubtreeStmt(ctx, t.subtreeRevs, len(ids))
if err != nil {
return nil, err
}
Expand All @@ -181,13 +205,26 @@ func (t *treeTX) getSubtrees(ctx context.Context, ids [][]byte) ([]*storagepb.Su
}
}()

args := make([]interface{}, 0, len(ids)+1)
args = append(args, t.treeID)
var args []interface{}
if t.subtreeRevs {
args = make([]interface{}, 0, len(ids)+3)
// populate args with ids.
for _, id := range ids {
klog.V(4).Infof(" id: %x", id)
args = append(args, id)
}
args = append(args, t.treeID)
args = append(args, treeRevision)
args = append(args, t.treeID)
} else {
args = make([]interface{}, 0, len(ids)+1)
args = append(args, t.treeID)

// populate args with ids.
for _, id := range ids {
klog.V(4).Infof(" id: %x", id)
args = append(args, id)
// populate args with ids.
for _, id := range ids {
klog.V(4).Infof(" id: %x", id)
args = append(args, id)
}
}

rows, err := stx.QueryContext(ctx, args...)
Expand Down Expand Up @@ -271,6 +308,12 @@ func (t *treeTX) storeSubtrees(ctx context.Context, subtrees []*storagepb.Subtre
// a really large number of subtrees to store.
args := make([]interface{}, 0, len(subtrees))

subtreeRev := t.writeRevision
if !t.subtreeRevs {
// If we aren't doing subtree revisions then always fill in the subtree
// revision with a constant, such as 0.
subtreeRev = 0
}
for _, s := range subtrees {
s := s
if s.Prefix == nil {
Expand All @@ -283,6 +326,7 @@ func (t *treeTX) storeSubtrees(ctx context.Context, subtrees []*storagepb.Subtre
args = append(args, t.treeID)
args = append(args, s.Prefix)
args = append(args, subtreeBytes)
args = append(args, subtreeRev)
}

tmpl, err := t.ts.setSubtreeStmt(ctx, len(subtrees))
Expand Down Expand Up @@ -326,16 +370,18 @@ func checkResultOkAndRowCountIs(res sql.Result, err error, count int64) error {
return nil
}

func (t *treeTX) getSubtreesFunc(ctx context.Context) cache.GetSubtreesFunc {
// getSubtreesAtRev returns a GetSubtreesFunc which reads at the passed in rev.
func (t *treeTX) getSubtreesAtRev(ctx context.Context, rev int64) cache.GetSubtreesFunc {
return func(ids [][]byte) ([]*storagepb.SubtreeProto, error) {
return t.getSubtrees(ctx, ids)
return t.getSubtrees(ctx, rev, ids)
}
}

func (t *treeTX) SetMerkleNodes(ctx context.Context, nodes []tree.Node) error {
t.mu.Lock()
defer t.mu.Unlock()
return t.subtreeCache.SetNodes(nodes, t.getSubtreesFunc(ctx))
rev := t.writeRevision - 1
return t.subtreeCache.SetNodes(nodes, t.getSubtreesAtRev(ctx, rev))
}

func (t *treeTX) Commit(ctx context.Context) error {
Expand Down
2 changes: 1 addition & 1 deletion storage/testonly/admin_storage_tester.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ var (
TreeState: trillian.TreeState_ACTIVE,
TreeType: trillian.TreeType_LOG,
DisplayName: "Llamas Log",
Description: "Registry of publicly-owned llamas",
Description: "🙃norevs🙃 Registry of publicly-owned llamas",
MaxRootDuration: durationpb.New(0 * time.Millisecond),
}

Expand Down

0 comments on commit 09bc384

Please sign in to comment.