Repository navigation
Migrate DB ledger read path to raw ledger bytes/view #945
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fcd868f
ce632d0
dc9b3ef
be6ee92
b514d1a
ce58de8
a8ac175
974fe30
a813f6b
0e62cc0
5e517cb
2cdc52b
c8a4520
97376c9
0bc19d8
62c442e
f9e2905
3e397d6
cb10f5f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,6 @@ | |
| package sqlitedb | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "context" | ||
| "database/sql" | ||
| "errors" | ||
|
|
@@ -74,28 +73,15 @@ func (l ledgerReaderTx) BatchGetLedgers( | |
|
|
||
| batch := make([]store.LedgerMetadataChunk, len(results)) | ||
| for i, meta := range results { | ||
| batch[i] = store.LedgerMetadataChunk{Lcm: meta} | ||
|
|
||
| var v xdr.Int32 | ||
| rd := bytes.NewReader(meta) | ||
| if _, err := xdr.Unmarshal(rd, &v); err != nil { | ||
| headerView, err := xdr.LedgerCloseMetaView(meta).LedgerHeader() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| if v > 0 { // V0 has no extension | ||
| var ext xdr.LedgerCloseMetaExt | ||
| if _, err := xdr.Unmarshal(rd, &ext); err != nil { // skipped | ||
| return nil, err | ||
| } | ||
| } | ||
|
|
||
| hdrStart := len(meta) - rd.Len() | ||
| var hdr xdr.LedgerHeaderHistoryEntry | ||
| n, err := xdr.Unmarshal(rd, &hdr) | ||
| headerRaw, err := headerView.Raw() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| batch[i].HeaderRaw = meta[hdrStart : hdrStart+n] | ||
| batch[i] = store.LedgerMetadataChunk{HeaderRaw: headerRaw, Lcm: meta} | ||
| } | ||
|
|
||
| return batch, nil | ||
|
|
@@ -163,6 +149,15 @@ func (r ledgerReader) GetLedger(ctx context.Context, sequence uint32) (xdr.Ledge | |
| return getLedgerFromDB(ctx, r.db, sequence) | ||
| } | ||
|
|
||
| // WithLedgerRaw lends the ledger's stored meta blob without decoding it. | ||
| func (r ledgerReader) WithLedgerRaw(ctx context.Context, sequence uint32, fn store.WithLedgerRawFn) (bool, error) { | ||
|
cjonas9 marked this conversation as resolved.
|
||
| meta, found, err := getLedgerRawFromDB(ctx, r.db, sequence) | ||
| if err != nil || !found { | ||
| return found, err | ||
| } | ||
| return true, fn(meta) | ||
| } | ||
|
|
||
| // GetLedgerRange pulls the min/max ledger sequence numbers from the meta table. | ||
| func (r ledgerReader) GetLedgerRange(ctx context.Context) (store.LedgerRange, error) { | ||
| r.db.cache.RLock() | ||
|
|
@@ -223,30 +218,60 @@ func (r ledgerReader) GetLatestLedgerSequence(ctx context.Context) (uint32, erro | |
| return getLatestLedgerSequence(ctx, r, r.db.cache) | ||
| } | ||
|
|
||
| // ledgerCloseTimePrefixBytes is the fast-path meta prefix fetched for range | ||
| // endpoints. Parsing falls back to the full blob if the header extends past it. | ||
| const ledgerCloseTimePrefixBytes = 1024 | ||
|
Comment on lines
+221
to
+223
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like a micro-optimization that isn't worth the add'l double-fetch complexity. The bulk of the time should be on encodes and decodes rather than the DB fetches themselves, which we mitigate via views already. Can you justify that this is worthwhile via some numbers?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, good catch, I'll try to get some profiling on this later. I'd intended to essentially eliminate the double-fetch path and just left that in for safety. I had figured that it was some wasted effort to fetch the entire DB when almost assuredly, only a small number of prefix bytes of the LCM were needed. But that can be discovered with numbers, too.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright, happy to say I got some results. I committed the bench test I used for this. I'm uncertain of whether or not this is exactly what you had in mind, but I hope it suffices. It checks this for the min/avg/max ledger sizes (512Kb/2Mb/4Mb), and it's neat as you can see how the results scale with ledger sizes.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh neat approach to do the side-by-side! In the future, it'd be sufficient to just post the before-and-after text as a GH comment by just running the benchmark on Also you might need to add this to the ignores for the release eval suite since there's no "before"?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was actually feeling strongly that this was a temp test, at least as I'd written it (especially because my release eval job does exactly this before/after automagically). But it did make for a theatric result :-). Absolutely I'll remove the comparison element and leave the rest, thanks for the look!
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this comment is still unanswered?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a little tricky, but I am pretty sure it is ok. Without getting into too much detail, the perf eval |
||
|
|
||
| // ledgerRangeRow is one endpoint of the stored ledger range. | ||
| type ledgerRangeRow struct { | ||
| Sequence uint32 `db:"sequence"` | ||
| MetaPrefix []byte `db:"meta_prefix"` | ||
| } | ||
|
|
||
| // ledgerInfoFromRow reads the close time out of the row's meta prefix, | ||
| // refetching the full blob for rare metas whose close time lies beyond it. | ||
| func ledgerInfoFromRow(ctx context.Context, db readDB, row ledgerRangeRow) (store.LedgerInfo, error) { | ||
| closeTime, err := xdr.LedgerCloseMetaView(row.MetaPrefix).LedgerCloseTime() | ||
|
cjonas9 marked this conversation as resolved.
|
||
| if err != nil { | ||
| meta, found, dbErr := getLedgerRawFromDB(ctx, db, row.Sequence) | ||
| if dbErr != nil { | ||
| return store.LedgerInfo{}, dbErr | ||
| } | ||
| if found { | ||
| closeTime, err = xdr.LedgerCloseMetaView(meta).LedgerCloseTime() | ||
| } | ||
| if err != nil { | ||
| return store.LedgerInfo{}, fmt.Errorf("couldn't get ledger %d close time: %w", row.Sequence, err) | ||
| } | ||
| } | ||
| return store.LedgerInfo{Sequence: row.Sequence, CloseTime: closeTime}, nil | ||
| } | ||
|
|
||
| // getLedgerRangeWithCache uses the latest ledger cache to optimize the query. | ||
| // It only needs to look up the first ledger since we have the latest cached. | ||
| func getLedgerRangeWithCache(ctx context.Context, db readDB, | ||
| latestSeq uint32, latestTime int64, | ||
| ) (store.LedgerRange, error) { | ||
| query := sq.Select("meta"). | ||
| query := sq.Select("sequence", fmt.Sprintf("substr(meta, 1, %d) AS meta_prefix", ledgerCloseTimePrefixBytes)). | ||
| From(ledgerCloseMetaTableName). | ||
| Where( | ||
| fmt.Sprintf("sequence = (SELECT MIN(sequence) FROM %s)", ledgerCloseMetaTableName), | ||
| ) | ||
| var lcm []xdr.LedgerCloseMeta | ||
| if err := db.Select(ctx, &lcm, query); err != nil { | ||
| var rows []ledgerRangeRow | ||
| if err := db.Select(ctx, &rows, query); err != nil { | ||
| return store.LedgerRange{}, fmt.Errorf("couldn't query ledger range: %w", err) | ||
| } | ||
|
|
||
| if len(lcm) == 0 { | ||
| if len(rows) == 0 { | ||
| return store.LedgerRange{}, store.ErrEmptyDB | ||
| } | ||
| firstLedger, err := ledgerInfoFromRow(ctx, db, rows[0]) | ||
| if err != nil { | ||
| return store.LedgerRange{}, err | ||
| } | ||
|
|
||
| return store.LedgerRange{ | ||
| FirstLedger: store.LedgerInfo{ | ||
| Sequence: lcm[0].LedgerSequence(), | ||
| CloseTime: lcm[0].LedgerCloseTime(), | ||
| }, | ||
| FirstLedger: firstLedger, | ||
| LastLedger: store.LedgerInfo{ | ||
| Sequence: latestSeq, | ||
| CloseTime: latestTime, | ||
|
|
@@ -256,31 +281,34 @@ func getLedgerRangeWithCache(ctx context.Context, db readDB, | |
|
|
||
| // getLedgerRangeWithoutCache queries both the first and last ledger when cache isn't available | ||
| func getLedgerRangeWithoutCache(ctx context.Context, db readDB) (store.LedgerRange, error) { | ||
| query := sq.Select("lcm.meta"). | ||
| query := sq.Select("lcm.sequence", fmt.Sprintf("substr(lcm.meta, 1, %d) AS meta_prefix", ledgerCloseTimePrefixBytes)). | ||
| From(ledgerCloseMetaTableName + " as lcm"). | ||
| Where(sq.Or{ | ||
| sq.Expr("lcm.sequence = (?)", sq.Select("MIN(sequence)").From(ledgerCloseMetaTableName)), | ||
| sq.Expr("lcm.sequence = (?)", sq.Select("MAX(sequence)").From(ledgerCloseMetaTableName)), | ||
| }).OrderBy("lcm.sequence ASC") | ||
|
|
||
| var lcms []xdr.LedgerCloseMeta | ||
| if err := db.Select(ctx, &lcms, query); err != nil { | ||
| var rows []ledgerRangeRow | ||
| if err := db.Select(ctx, &rows, query); err != nil { | ||
| return store.LedgerRange{}, fmt.Errorf("couldn't query ledger range: %w", err) | ||
| } | ||
|
|
||
| if len(lcms) == 0 { | ||
| if len(rows) == 0 { | ||
| return store.LedgerRange{}, store.ErrEmptyDB | ||
| } | ||
|
|
||
| firstLedger, err := ledgerInfoFromRow(ctx, db, rows[0]) | ||
| if err != nil { | ||
| return store.LedgerRange{}, err | ||
| } | ||
| lastLedger, err := ledgerInfoFromRow(ctx, db, rows[len(rows)-1]) | ||
| if err != nil { | ||
| return store.LedgerRange{}, err | ||
| } | ||
|
|
||
| return store.LedgerRange{ | ||
| FirstLedger: store.LedgerInfo{ | ||
| Sequence: lcms[0].LedgerSequence(), | ||
| CloseTime: lcms[0].LedgerCloseTime(), | ||
| }, | ||
| LastLedger: store.LedgerInfo{ | ||
| Sequence: lcms[len(lcms)-1].LedgerSequence(), | ||
| CloseTime: lcms[len(lcms)-1].LedgerCloseTime(), | ||
| }, | ||
| FirstLedger: firstLedger, | ||
| LastLedger: lastLedger, | ||
| }, nil | ||
| } | ||
|
|
||
|
|
@@ -326,21 +354,34 @@ func (l ledgerWriter) trimLedgers(latestLedgerSeq uint32, retentionWindow uint32 | |
| return err | ||
| } | ||
|
|
||
| // getLedgerFromDB is a helper function that encapsulates the common logic | ||
| // for fetching a single ledger from the database | ||
| // getLedgerFromDB fetches a single ledger from the database. | ||
| func getLedgerFromDB(ctx context.Context, db readDB, sequence uint32) (xdr.LedgerCloseMeta, bool, error) { | ||
| meta, found, err := getLedgerRawFromDB(ctx, db, sequence) | ||
| if err != nil || !found { | ||
| return xdr.LedgerCloseMeta{}, false, err | ||
| } | ||
| var lcm xdr.LedgerCloseMeta | ||
| if err := lcm.UnmarshalBinary(meta); err != nil { | ||
| return xdr.LedgerCloseMeta{}, false, err | ||
| } | ||
| return lcm, true, nil | ||
| } | ||
|
|
||
| // getLedgerRawFromDB is a helper function that encapsulates the common logic | ||
| // for fetching a single ledger's bytes from the database. | ||
| func getLedgerRawFromDB(ctx context.Context, db readDB, sequence uint32) ([]byte, bool, error) { | ||
| sql := sq.Select("meta").From(ledgerCloseMetaTableName).Where(sq.Eq{"sequence": sequence}) | ||
| var results []xdr.LedgerCloseMeta | ||
| var results [][]byte | ||
| if err := db.Select(ctx, &results, sql); err != nil { | ||
| return xdr.LedgerCloseMeta{}, false, err | ||
| return nil, false, err | ||
| } | ||
| switch len(results) { | ||
| case 0: | ||
| return xdr.LedgerCloseMeta{}, false, nil | ||
| return nil, false, nil | ||
| case 1: | ||
| return results[0], true, nil | ||
| default: | ||
| return xdr.LedgerCloseMeta{}, false, fmt.Errorf("multiple lcm entries (%d) for sequence %d in table %q", | ||
| return nil, false, fmt.Errorf("multiple lcm entries (%d) for sequence %d in table %q", | ||
| len(results), sequence, ledgerCloseMetaTableName) | ||
| } | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.