[Persistence] PrePersistence deprecation & Block lifecycle improvements (Issue #128 && Issue #105)#140
[Persistence] PrePersistence deprecation & Block lifecycle improvements (Issue #128 && Issue #105)#140andrewnguyen22 merged 15 commits intomainfrom
Conversation
#73) ## Objective Foundational iteration of PostgreSQL based persistence module implementation. ## Origin Document #68 ## Type of change New major module implementation. ### Persistence-related core changes: - List of actors / interfaces with MVP implementation: - Applications - Fisherman - ServiceNode - Accounts - Pools - List of actors / interfaces with partial MVP implementation: - Validator - Gov params - List of actors / interfaces with minorimplementation: - Block - SQL Schema definition of the actors above - SQL Query implementation for common actor persistence functionality - PostgresContext implementation of the actors actors above - Base infrastructure for fuzz testing Non-persistence “fly-by” changes - Updates to the PrePersistence module and utility module with breaking changes - A few minor improvements/additions to the Makefile - TODOs & comment cleanups throughout the codebase ## How Has This Been Tested? ### Unit Tests ``` make test_persistence make test_all ``` ### LocalNet Ran a basic LocalNet following the instructions in the [development README](docs/development/README.md). Co-authored-by: Daniel Olshansky <olshansky.daniel@gmail.com> Co-authored-by: Andrew Nguyen <andrewnguyen@Andrews-MacBook-Pro-2.local> Co-authored-by: Andrew Nguyen <andrewnguyen@Andrews-MBP-2.lan> Co-authored-by: Daniel Olshansky <olshansky@pokt.network>
…tence-foundation-interim-merge' into issue-#111
… to read / write separation
|
A ton of conflicts here. Will work on this in parallel - but wanted to get the PR up to get some eyes on immediately |
@andrewnguyen22 It's a bit hard to review the changes in isolation because of all the conflicts with master so I think you'll need to do "merge with master" (i.e. I don't suggest a rebase here) and likely spend an hour or two resolvings. I acknowledge this isn't fun, but I think it's worth it and we'll have smaller PRs as the MVP takes shape make these less painful.I looked at the changelogs and the interface changes and my high level understanding is this: Instead of writing to the postgres DB, we read/write to a transaction and commit it at the very end. If that's the case, at a high level, it looks good to me and will review in depth once ready. |
Understood and agree, working on it now |
Olshansk
left a comment
There was a problem hiding this comment.
I took a first pass at this PR, but will do a more detailed one after we address some of the comments and discuss some of the open questions I left inline.
@andrewnguyen22 I also left a couple of open-ended refactoring comments but didn't have time to tackle the changes myself. If you find that it's too much of a waste of time or too out of scope, feel free just to leave a TODO or comment and I can address it in a follow-up PR.
8e5c20f to
6853095
Compare
|
@andrewnguyen22 I've spent most of the day going over this PR and the changes have become big enough that I've moved it into a separate branch for now. I think there are some foundational business logic issues, but wanted to know your preference on:
I would personally prefer (2), but am okay with (1) in order to unblock other work in parallel |
Let's sync offline to make sure everything goes smoothly. Not closed off to either solution, but 1) does seem attractive given you've already split the PR into two |
Olshansk
left a comment
There was a problem hiding this comment.
@andrewnguyen22 As discussed offline, I'm working on the follow-ups to this in EDIT: Moving this to #165.
Hoping to approve this by EOD, but just reviewing to make sure I didn't lose any context.
I'm posting some of my previous comments, only one of which I believe needs an answer.
| return | ||
| } | ||
| if err = conn.QueryRow(ctx, schema.GetAccountAmountQuery(address, height)).Scan(&amount); err != nil { | ||
| amount = "0" |
There was a problem hiding this comment.
I played around with this and cannot understand the intended behaviour.
We only return this default value if err != nil, but if err != nil, then the result should not be used because we should handle the error.
If we want to keep this in place, what we need to do is check the err and do something like (pseudocode):
amount = 0
if err = QueryRow.scan(&amount); err != RowNotFound {
return "", err
}
return amount, nil
There was a problem hiding this comment.
We only return this default value if err != nil, but if err != nil, then the result should not be used because we should handle the error.
The error returned can be row_not_found (pseudo) which should be translated to the default value
There was a problem hiding this comment.
We can handle this higher in the stack if you prefer
| func SelectAccounts(height int64, tableName string) string { | ||
| return fmt.Sprintf(` | ||
| SELECT address, balance, height | ||
| FROM %s WHERE height<=%d AND (height,address) IN (SELECT MAX(height),address from %s GROUP BY address) |
There was a problem hiding this comment.
EDIT: Moving this to #165.
I'm updating this to use DISTINCT and avoiding the IN query. Even though this probably yields some performance gains (did not benchmark), I think it's more readable / understandable to see what query we're making.
Also going to add some unit tests here too.
| func SelectAccounts(height int64, tableName string) string { | ||
| return fmt.Sprintf(` | ||
| SELECT address, balance, height | ||
| FROM %s WHERE height<=%d AND (height,address) IN (SELECT MAX(height),address from %s GROUP BY address) |
There was a problem hiding this comment.
EDIT: Moving this to #165.
I added a few unit tests and there seemed to be a bug in the previous approach but I didn't dive deep enough to understand the reasoning. Here is the new business logic:
func SelectAccounts(height int64, tableName string) string {
return fmt.Sprintf(`
SELECT DISTINCT ON (address) address, balance, height
FROM %s
WHERE height<=%d
ORDER BY address, height DESC
`, tableName, height)
}|
Continued conversation here #165 (comment) |
| BlockStore: m.blockStore, | ||
| ContextStore: kvstore.NewMemKVStore(), | ||
| func (m *persistenceModule) NewRWContext(height int64) (modules.PersistenceRWContext, error) { | ||
| db, err := ConnectAndInitializeDatabase(m.postgresURL, m.nodeSchema) |
There was a problem hiding this comment.
I think this might be the source of the issues I'm running into in the #128-fixes/improvements.
Can we reuse the same DB connection from Create here?
|
@andrewnguyen22 I'm getting close to finishing #165, so what are your thoughts on merging this in so #139 can start resolving merging conflicts while reviewing #165 in parallel? Can approve the PR if that sounds good per out offline discussion. |
#165) # Objective Follow up on both critical and non-critical fixes to #140. # Origin Document #140 deprecated PrePersistence and implemented multiple contexts. This PR is a follow with some stylistic changes, improvements in the readability of the tests, the addition of unit tests, bug fixes in some SQL business logic, and implementation of the read context. Followup work is being tracked in #149. ## Learnings to streamline workflow in the future - Do not conflate non-critical stylistic changes as it leads to more conflicts down the road - Implemented an automated "LocalNet" for easier testing - Try to plan work ahead of time so we can work in smaller PRs / iterations ## Changelog / changes **Main persistence module changes:** - Split `ConnectAndInitializeDatabase` into `connectToDatabase` and `initializeDatabase` - This enables creating multiple contexts in parallel without re-initializing the DB connection - Fix the SQL query used in `SelectActors`, `SelectAccounts` & `SelectPools` - Add a generalized unit test for all actors - Remove `NewPersistenceModule` and an injected `Config` + `Create` - This improves isolation a a “injection-like” paradigm for unit testing - Change `SetupPostgresDocker` to `SetupPostgresDockerPersistenceMod` - This enables more “functional” like testing by returning a persistence module and avoiding global testing variables - Only return once a connection to the DB has been initialized reducing the likelihood of test race conditions - Implemented `NewReadContext` with a proper read-only context **Secondary persistence module changes** - Improve return values in `Commit` and `Release` (return error, add logging, etc…) - Add `pgx.Conn` pointer to `PostgresDB` - `s/db/conn/g` and `s/conn/tx/g` in some (not all) places where appropriate - Make some exported variables / functions unexported for readability & access purposes - Add a few helpers for persistence related unit testing - Added unit tests and TODOs for handling multiple read/write contexts ## General milestone checklist - [x] Update all the relevant CHANGELOGs - [ ] Update all the relevant READMEs - [ ] Update the source code tree explanation - [ ] Add or update a state, sequence or flowchart diagram using [mermaid](https://mermaid-js.github.io/mermaid/) - [x] Create followup milestones + issues - [x] Document small TODO along the way ## Follow-up Work All follow-up work is being tracked in #149 --- ## Checklist - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have tested my changes using the available tooling - [ ] If applicable, I have made corresponding changes to related or global README - [ ] If applicable, I have added added new diagrams using [memaid.js](https://mermaid-js.github.io) - [ ] If applicable, I have added tests that prove my fix is effective or that my feature works --- Co-authored-by: Daniel Olshansky <olshansky.daniel@gmail.com> Co-authored-by: Andrew Nguyen <andrewnguyen@Andrews-MacBook-Pro-2.local> Co-authored-by: Andrew Nguyen <andrewnguyen@Andrews-MBP-2.lan> Co-authored-by: Daniel Olshansky <olshansky@pokt.network> Co-authored-by: Andrew Nguyen <amnguyen1@mail.usf.edu>
Objective
closes #128
blockprocessing and production by creating a transactional architecture around persistence contexts instead of writing directly to the database on every operation.closes #105 and closes #104
PrePersistencerelated code.Origin Document
Background
#128
Multiple persistence contexts enable a proper lifecycle for validators and full nodes as they maintain 'ephemeral' states in situations like block validation/processing and block production.
Pretty early on - it was discovered that though ephemeral states are needed, there seems to be little validity in having multiple
writecontexts in parallel - rather enable write contexts sequentially (only one at a time) and enable multiple read contexts. This is currently not strictly enforced, rather needs discussion on where to delegate this responsibility (Consensus, Utility, or Persistence module).#105
[Persistence] V1 Persistence Foundation Integration Issue
Goals / Deliverables
General milestone checklist
Follow-up Work
All follow-up work is being tracked in #149
Checklist
Co-authored-by: Daniel Olshansky olshansky.daniel@gmail.com
Co-authored-by: Andrew Nguyen andrewnguyen@Andrews-MacBook-Pro-2.local
Co-authored-by: Andrew Nguyen andrewnguyen@Andrews-MBP-2.lan
Co-authored-by: Daniel Olshansky olshansky@pokt.network