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

refactor(i): Rewrite document fetcher #3279

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from

Conversation

AndrewSisley
Copy link
Contributor

Relevant issue(s)

Resolves #3277

Description

Rewrites the document fetcher.

Motivation

The old document fetcher had grown over the last few years and had IMO turned into a bit of a mess with a large amount of mutable state, and a lot of different concepts blurred into the same type where they were interleaved amongst each other that made maintaining the file pretty horrible.

Picking up #3275 motivated me to make this change now, so I can integrate the new package into something that I (hopefully we) can read.

What has not been done

The Fetcher interface has not changed (much, see first commit) - it would involve touching more parts of the codebase and would be a lot more hassle, it would have minimal impact on introducing CoreKV.

What has been done

The old code has been deleted and replaced with something written from scratch. Please review each line as if it is brand new code, complain about anything you like. Please be quite critical, especially if you actually liked the old fetcher code.

Behavioural changes

Only one significant change has been made to the behaviour.

Previously, filtering would be done as soon as all the fields for the filter had been read into memory. Now filtering is done once all selected fields have been read.

The old behaviour makes very little sense to me, as best case scenario, only fields that had names lexicographically larger than the last filter field for the last document in the prefix would have been avoided being scanned. This is quite tiny, and the code required to do so is almost certainly more computationally expensive than the saving (across the database's lifetime). Note: the primary side of relations gained the most from this, until we add automatic indexing of relations.

Even once CoreKV is introduced, with its seek functionality, skipping past those key-values would require the iterator to seek backwards and forwards (or have two iterators, with extra seeking), and the savings would likely be minimal at best.

Medium-long term this means that doing any field-based filtering makes little sense, and we might as well do it outside of the fetcher. This is more hassle than it is worth to change in this PR, so, in order to keep a manageable scope to this PR a filtered fetcher has been implemented in order to host fetcher-level filtering. Hopefully we can remove that too soon.

It hasn't been used in 3 years, it can die and come back if/when needed.
@AndrewSisley AndrewSisley added area/query Related to the query component refactor This issue specific to or requires *notable* refactoring of existing codebases and components code quality Related to improving code quality labels Nov 28, 2024
@AndrewSisley AndrewSisley added this to the DefraDB v0.15 milestone Nov 28, 2024
@AndrewSisley AndrewSisley requested a review from a team November 28, 2024 20:27
@AndrewSisley AndrewSisley self-assigned this Nov 28, 2024
Copy link

codecov bot commented Nov 28, 2024

Codecov Report

Attention: Patch coverage is 78.57143% with 81 lines in your changes missing coverage. Please review.

Project coverage is 78.04%. Comparing base (8509211) to head (f03a867).

Files with missing lines Patch % Lines
internal/db/fetcher/wrapper.go 78.95% 13 Missing and 7 partials ⚠️
internal/db/fetcher/deleted.go 65.91% 11 Missing and 4 partials ⚠️
internal/db/fetcher/document.go 85.58% 10 Missing and 5 partials ⚠️
internal/db/fetcher/prefix.go 75.81% 10 Missing and 5 partials ⚠️
internal/db/fetcher/filtered.go 70.00% 6 Missing and 3 partials ⚠️
internal/db/fetcher/permissioned.go 82.86% 4 Missing and 2 partials ⚠️
internal/db/fetcher/versioned.go 80.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #3279      +/-   ##
===========================================
+ Coverage    77.95%   78.04%   +0.09%     
===========================================
  Files          382      388       +6     
  Lines        35364    35298      -66     
===========================================
- Hits         27568    27548      -20     
+ Misses        6148     6120      -28     
+ Partials      1648     1630      -18     
Flag Coverage Δ
all-tests 78.04% <78.57%> (+0.09%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
internal/db/collection.go 69.87% <100.00%> (ø)
internal/db/collection_get.go 74.14% <100.00%> (ø)
internal/db/collection_index.go 87.43% <ø> (-0.03%) ⬇️
internal/db/fetcher/fetcher.go 100.00% <ø> (+22.78%) ⬆️
internal/db/fetcher/indexer.go 83.69% <ø> (-0.11%) ⬇️
internal/lens/fetcher.go 70.09% <ø> (-0.14%) ⬇️
internal/planner/scan.go 89.88% <100.00%> (-0.04%) ⬇️
internal/db/fetcher/versioned.go 81.77% <80.00%> (+1.55%) ⬆️
internal/db/fetcher/permissioned.go 82.86% <82.86%> (ø)
internal/db/fetcher/filtered.go 70.00% <70.00%> (ø)
... and 4 more

... and 19 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8509211...f03a867. Read the comment docs.

Comment on lines +19 to +20
// deleted is a fetcher that orchastrates the fetching of deleted and active documents.
type deleted struct {
Copy link
Member

@shahzadlone shahzadlone Nov 29, 2024

Choose a reason for hiding this comment

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

suggestion: Why not rename to deletedFetcher, I know the package name is fetcher but still prefer the more descriptive name especially as you also have newDeletedFetcher function.

Suggested change
// deleted is a fetcher that orchastrates the fetching of deleted and active documents.
type deleted struct {
// deleted is a fetcher that orchastrates the fetching of deleted and active documents.
type deletedFetcher struct {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I dislike fetcher.deletedFetcher now, and I would have expected @fredcarle to complain if I had named it so. Happy to rename if the majority want to though, otherwise leaving as-is.

Copy link
Member

Choose a reason for hiding this comment

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

Fred and I have rather similar views on this kind of naming stuff and although I'm not speaking for him, I do prefer the explicitness of deletedFetcher here. deleted is just too generic of a term, same with document which given the context of the package could just as easily refer to an actual document.

As for the import/package full naming (ie: fetcher.deletedFetcher) this is also OK and not against any language idioms or best practises, because the alternative (fetcher.deleted) is not a clear enough naming scheme (ignoring the fact that this isnt a public type).

Copy link
Member

@shahzadlone shahzadlone Dec 2, 2024

Choose a reason for hiding this comment

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

I dislike fetcher.deletedFetcher now, and I would have expected @fredcarle to complain if I had named it so. Happy to rename if the majority want to though, otherwise leaving as-is.

Why newDeletedFetcher over newDeleted then also haha? The only reason deleted is easy to understand for us is because we have context. A new developer just stumbling on this file even though the package is called fetcher might not grasp what a general term like deleted is referring to here, let alone what a "deleted fetcher" is.

I do understand that this is subjective so happy to follow consensus. I do think I am bias to longer and descriptive names haha my ideal would definitely be deletedAndActiveDocumentFetcher or activeDocumentAndDeletedFetcher for this and activeDocumentFetecher for the other one that is currently document

@@ -362,7 +362,7 @@ func TestQueryWithIndexOnOneToOnePrimaryRelation_IfFilterOnIndexedFieldOfRelatio
// we make 3 index fetch to get the 3 address with city == "Montreal"
// then we scan all 10 users to find one with matching "address_id" for each address
// after this we fetch the name of each user
Asserter: testUtils.NewExplainAsserter().WithFieldFetches(33).WithIndexFetches(3),
Asserter: testUtils.NewExplainAsserter().WithFieldFetches(60).WithIndexFetches(3),
Copy link
Member

Choose a reason for hiding this comment

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

question: Whats the context on field fetches increases, is it because all fields are read now?

Copy link
Contributor Author

@AndrewSisley AndrewSisley Nov 29, 2024

Choose a reason for hiding this comment

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

Noted in the description. It is a large increase due to the lack of indexing (and other potential optimisations), so we end up doing a full scan for every parent record - field count here has gone from (3*10*1+(1*3)) to (3*10*2) (note if you rename address to baddress on develop branch you get the same result).

// document is the type responsible for fetching documents from the datastore.
//
// It does not filter the data in any way.
type document struct {
Copy link
Member

Choose a reason for hiding this comment

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

suggestion: Similar renaming suggestion as above.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same answer as above

Copy link
Member

Choose a reason for hiding this comment

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

same comment as above, I don't think document is the best naming here, too generic/general and could be interpreted in a myriad of ways (which I initially did).

In addition to my above comment, from a purely gramatical POV, the fetcher is a verb indicating action of some kind, which makes sense since the fetchers are a system of pulling and processing key values into a consistent output form. Where as document is a noun and indicates a static object. (This grammar argument isn't my primary concern with the naming, just an observation)

@jsimnz
Copy link
Member

jsimnz commented Dec 2, 2024

reviewing now

"github.com/sourcenetwork/defradb/internal/keys"
)

// document is the type responsible for fetching documents from the datastore.
Copy link
Member

Choose a reason for hiding this comment

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

question/suggestion: Is this only for active documents? or can work for both?

Perhaps this suggestion if only for active:

Suggested change
// document is the type responsible for fetching documents from the datastore.
// document is the type responsible for fetching only active documents from the datastore.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/query Related to the query component code quality Related to improving code quality refactor This issue specific to or requires *notable* refactoring of existing codebases and components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Cleanup fetcher
3 participants