Skip to content
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

/operation_fee_stats endpoint #586

Merged
merged 2 commits into from
Sep 10, 2018
Merged

/operation_fee_stats endpoint #586

merged 2 commits into from
Sep 10, 2018

Conversation

robertDurst
Copy link
Contributor

@robertDurst robertDurst commented Aug 15, 2018

This a rough first draft for this endpoint. Below I have noted the things I am looking for feedback on.

Things that need improvement:

  • currently my query is not great - this performs three queries and gets a lot more data from horizon db than needed

Things up for discussion:

  • data to return -- currently returning:
    • latest_ledger_base_fee: as per @stanford-scs
    • mode: the most occurring, accepted transaction over the past five ledgers
    • min: the smallest tx fee accepted transaction over the past five ledgers
  • hard coded limit - currently I hardcode the endpoint to get data from the last five ledgers. Is five a good number (based on how core/horizon deals with pending tx's)? Should this be an endpoint parameter?

The test schema build script generated a lot of files I have not included in this PR.

Closes: #516

@bartekn
Copy link
Contributor

bartekn commented Aug 16, 2018

I need to check this with real data (I'm on mobile tethering so will do it once on a stable wifi) but doesn't a query like:

select
    min(fee_paid/operation_count),
    mode() within group (order by fee_paid/operation_count)
from history_transactions
where ledger_sequence > {current - x};

return the same data without doing it all manually?

Also because this won't change between ledgers we can keep the results in a cache for ~5 seconds so consecutive requests do not recalculate it over and over again.

@robertDurst
Copy link
Contributor Author

robertDurst commented Aug 16, 2018

As per discussion with @bartekn , next steps are:

  1. Consolidating to only two queries (one for latest ledger info and one for x ledgers worth of tx's)
  2. Moving calculation logic to SQL query
  3. Implementing a cache for op_fee_stats modeled after https://github.com/stellar/go/blob/master/services/horizon/internal/ledger/main.go

Will update PR with this by Saturday night. Today and Friday I will be at RustConf

@robertDurst
Copy link
Contributor Author

@bartekn switched to cache like we described. I made a few design decisions in this process:

  • Inserted op fee cache update into current horizon tick, I can easily make this every 5 ticks or create a separate tick that goes every 5 sec (my reasoning here was that ledgers too should only be updated every 5 sec, but the update lives within the 1 sec tick)
  • Hardcoded query to last 5 ledgers worth of tx's. This seems good for now (and also wasn't sure how a 5/10/15 approach would work, would the endpoint then return data for each of these queries). If you think this is something we need, this is also easy to add.

@robertDurst
Copy link
Contributor Author

Also, tests do pass, but I think travis won't since I didn't add any of the generated test files beyond the scc recipes

type OperationFeeStatsAction struct {
Action
Records []history.Transaction
Ledgers history.LedgerCache
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Records and Ledgers not used.


err = a.HistoryQ().
TransactionsForLastXLedgers(latest.Sequence).
Select(&resp)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move Select inside TransactionsForLastXLedgers and change it to something like:

TransactionsForLastXLedgers(currentSeq int32, min, max *null.Int) error

Then FeeStats is not needed anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I am mistaken (still not a golang pro yet), I believe I need some sort of intermediary data structure to capture the response from the sql query -- hence FeeStats and LatestLedger, both of which I moved to github.com/stellar/go/services/horizon/internal/db2/history.

I could accept and min and mode as input params and write to them as part of this method, however I would still need something like FeeStats right?


if len(resp) < 1 {
return
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This query will always return at least 1 row.

type LatestLedger struct {
BaseFee int32 `db:"base_fee"`
Sequence int32 `db:"sequence"`
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this to github.com/stellar/go/services/horizon/internal/db2/history package.

Select("min(fee_paid/operation_count), mode() within group (order by fee_paid/operation_count)").
From("history_transactions").
Where("ledger_sequence > ?", currentSeq-5),
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not GetRaw as in LatestLedgerBaseFeeAndSequence? This looks complicated.

err = a.HistoryQ().LatestLedgerBaseFeeAndSequence(&latest)
if err != nil {
goto Failed
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inserted op fee cache update into current horizon tick, I can easily make this every 5 ticks or create a separate tick that goes every 5 sec (my reasoning here was that ledgers too should only be updated every 5 sec, but the update lives within the 1 sec tick)

We can do it even better. We can leave the tick to be every second but then we can check if there is a new ledger (by comparing the result of LatestLedgerBaseFeeAndSequence and current state in operationfeestats). If there is a new ledger we can continue calculations, if not we can return early.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call.

@robertDurst
Copy link
Contributor Author

@bartekn updated for latest review. One part I have questions about (responded in the comment thread above) is where you said Then FeeStats is not needed anymore.

var next operationfeestats.State

var latest history.LatestLedger
var resp history.FeeStats
Copy link
Contributor

@bartekn bartekn Aug 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename resp to feeStats? Easy to mistake.

@@ -16,6 +16,17 @@ func (q *Q) TransactionByHash(dest interface{}, hash string) error {
return q.Get(dest, sql)
}

// TransactionsForLastXLedgers filters the query to only the last X ledgers worth of transactions.
// Currently, we hard code the query to return the last 5 ledgers worth of transactions. In the
// future this mya be configurable.
Copy link
Contributor

@bartekn bartekn Aug 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mya -> may

func SetState(next State) {
lock.Lock()
current = next
lock.Unlock()
Copy link
Contributor

@bartekn bartekn Aug 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need one more thing here to prevent sync issues (like in #603 but probably less dangerous). UpdateOperationFeeStatsState is called every second so if one of the queries inside take a long time, the old state can overwrite the newer one:

Time Tick1 Tick2
1 UpdateOperationFeeStatsState start
2 UpdateOperationFeeStatsState start
3 Save result: operationfeestats.SetState
4 Save result: operationfeestats.SetState

To prevent this we should:

  1. Check if the next.LastLedger > current.LastLedger in SetState above.
  2. Change TransactionsForLastXLedgers to something like TransactionFeesBetweenLedgers(first int32, last int32, dest interface{}). So we will always check the correct transactions even if one or more new ledgers appeared in a meantime.

Copy link
Contributor

@bartekn bartekn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Can you add test schema files?

func (t *T) UpdateOperationFeeStatsState() {
var err error
var next operationfeestats.State
var latest history.LatestLedger
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All Update* methods that are duplicated from App should be removed eventually. For now, let's just update them (ex. history db min/mode query is wrong).

@robertDurst
Copy link
Contributor Author

robertDurst commented Aug 24, 2018

Looks like a I have a couple things to fix -- some tests fail and a sort of cyclical import (no sure where that came from). Just rebased and will see if that fixes anything.

@robertDurst
Copy link
Contributor Author

robertDurst commented Aug 24, 2018

Looks like recent offer liabilities changes broke scc test scenarios

@robertDurst
Copy link
Contributor Author

Tests pass, but needs one more round of cleanup/refactor.

@robertDurst
Copy link
Contributor Author

@bartekn ok so test schema included and everything passes. Only questionable thing left I know of is a ResetState method I added to operationfeestats cache. I did this because the cache checks that new_ledger > cur_ledger. However since empty struct has 0 ledger seq as default, it cannot reset the cache.

@bartekn bartekn added this to the Horizon v0.15.0 milestone Sep 4, 2018
@bartekn bartekn changed the title [WIP] /operation_fee_stats endpoint /operation_fee_stats endpoint Sep 4, 2018
@bartekn bartekn added the horizon label Sep 5, 2018
@bartekn bartekn changed the base branch from master to horizon-0.15.0 September 10, 2018 16:38
@bartekn bartekn merged commit 2e1fe77 into stellar:horizon-0.15.0 Sep 10, 2018
@robertDurst
Copy link
Contributor Author

Yeah 🍾

bartekn pushed a commit that referenced this pull request Sep 24, 2018
bartekn pushed a commit that referenced this pull request Sep 25, 2018
bartekn pushed a commit that referenced this pull request Oct 10, 2018
bartekn pushed a commit that referenced this pull request Oct 25, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants