On 04/12/2018 04:00 PM, Brian Bouterse wrote:
On Thu, Apr 12, 2018 at 11:53 AM, Jeff Ortel <[email protected]
<mailto:[email protected]>> wrote:
On 04/12/2018 10:01 AM, Brian Bouterse wrote:
On Wed, Apr 11, 2018 at 6:07 PM, Jeff Ortel <[email protected]
<mailto:[email protected]>> wrote:
On 04/11/2018 03:29 PM, Brian Bouterse wrote:
I think we should look into this in the near-term. Changing
an interface on an object used by all plugins will be
significantly easier, earlier.
On Wed, Apr 11, 2018 at 12:25 PM, Jeff Ortel
<[email protected] <mailto:[email protected]>> wrote:
On 04/11/2018 10:59 AM, Brian Bouterse wrote:
On Tue, Apr 10, 2018 at 10:43 AM, Jeff Ortel
<[email protected] <mailto:[email protected]>> wrote:
On 04/06/2018 09:15 AM, Brian Bouterse wrote:
Several plugins have started using the Changesets
including pulp_ansible, pulp_python, pulp_file,
and perhaps others. The Changesets provide several
distinct points of value which are great, but
there are two challenges I want to bring up. I
want to focus only on the problem statements first.
1. There is redundant "differencing" code in all
plugins. The Changeset interface requires the
plugin writer to determine what units need to be
added and those to be removed. This requires all
plugin writers to write the same non-trivial
differencing code over and over. For example, you
can see the same non-trivial differencing code
present in pulp_ansible
<https://github.com/pulp/pulp_ansible/blob/d0eb9d125f9a6cdc82e2807bcad38749967a1245/pulp_ansible/app/tasks/synchronizing.py#L217-L306>,
pulp_file
<https://github.com/pulp/pulp_file/blob/30afa7cce667b57d8fe66d5fc1fe87fd77029210/pulp_file/app/tasks/synchronizing.py#L114-L193>,
and pulp_python
<https://github.com/pulp/pulp_python/blob/066d33990e64b5781c8419b96acaf2acf1982324/pulp_python/app/tasks/sync.py#L172-L223>.
Line-wise, this "differencing" code makes up a
large portion (maybe 50%) of the sync code itself
in each plugin.
Ten lines of trivial set logic hardly seems like a
big deal but any duplication is worth exploring.
It's more than ten lines. Take pulp_ansible for
example. By my count (the linked to section) it's 89
lines, which out of 306 lines of plugin code for sync
is 29% of extra redundant code. The other plugins have
similar numbers. So with those numbers in mind, what do
you think?
I was counting the lines (w/o comments) in find_delta()
based on the linked code. Which functions are you counting?
I was counting the find_delta, build_additions, and
build_removals methods. Regardless of how the lines are
counted, that differencing code is the duplication I'm
talking about. There isn't a way to use the changesets
without duplicating that differencing code in a plugin.
The differencing code is limited to find_delta() and perhaps
build_removals(). Agreed, the line count is less useful than
specifically identifying duplicate code. Outside of
find_delta(), I see similar code (in part because it got
copied from file plugin) but not seeing actual duplication.
Can you be more specific?
Very similar code or identical code, I think it begs the question
why are we having plugin writer's do this at all? What value are
they creating with it? I don't have a reasonable answer to that
question, so the requirement for plugin writer's to write that
code brings me back to the problem statement: "plugin writers
have redundant differencing code when using Changesets". More
info on why it is valuable for the plugin writer to do the
differencing code versus the Changesets would be helpful.
The ChangeSet abstraction (and API) is based on following division
of responsibility:
The plugin (with an understanding of the remote and its content):
- Download metadata.
- Parse metadata
- Based on the metadata:
- determine content to be added to the repository.
- define how artifacts are downloaded.
- construct content
- determine content to be removed to the repository.
Core (without understand of specific remote or its content):
- Provide low level API for plugin to affect the changes it has
determined need to be made to the repository. This is
downloaders, models etc.
- Provide high(er) level API for plugin to affect the changes it
has determined need to be made to the repository. This is the
ChangeSet.
Are you proposing that this is not the correct division?
Yes I believe these problem statements suggest we should adjust the
plugin writer's responsibilities when interacting with the Changesets
in two specific ways. It's not exactly the language you used, but I
believe the following two responsibilities could be moved into the
Changesets entirely:
- determining if any given Artifact or Content unit is already present
in Pulp (aka computing what needs tobe added)
Did you mean /added/ to the repository or /created/ in pulp. Currently,
the plugin determines the content that needs to be added to the
repository. This is modeled using a PendingContent which fully defines
the Content (unit) and its PendingArtifact(s) which are included in the
/additions/. The ChangeSet does determine whether or not any artifacts
need to be downloaded (and downloads them based on policy) and
determines which Content needs to be /created/ vs simply added to the
repository. The plugin blindly assumes that none of the /pending/
content has yet been created pulp. This accomplishes 2 things. 1)
reduces complexity and decision making by the plugin. 2) provides the
ChangeSet with all the information needed to /create/ and /download/ as
needed. The /additions/ represents what the plugin wants to be added to
the repository to synchronize it with the remote repository.
- determining which content units need to be removed (aka computing
the removals)
I don't see how the ChangeSet has enough information to do this. The
plugin can (most likely will) make the decision about what to remove
based on remote metadata, policy and configuration.
^ goals are a restating of the problem statement that plugin writers
are asked to do differencing calculations when the Changesets could
provide that to the plugin writer instead.
So a shorter, simpler problem statement is: "to use the
changesets plugin writers have to do extra work to compute
additions and removals parameters".
This statement ^ is better but still too vague to actually
solve. Can we elaborate on specifically what "to do extra
work" means?
Sure. Removing that vague language is one way to resolve its
vagueness. Here's a revised problem statement: "to use the
changesets plugin writers have to compute additions and removals
parameters". This problem statement would be resolved by a
solution that causes the plugin writer to never have to produce
these parameters and be replaced by an interface that would
require less effort from a plugin writer.
I think it's the plugin's responsibility to determine the
difference. Aside from that: without an understanding of the
metadata and content type, how could the ChangeSet do this? What
might that looks like?
If I'm understanding this correctly, the Changesets already do this
for additions right? Help check my understanding. If a plugin writer
delivers PendingContent and PendingArtifacts to the Changesets as
'additions', the Changesets will recognize them as already downloaded
and not download them right? If this is the case, what is the benefit
of having plugin writers also try to figure out if things should be
downloaded?
As you pointed out, the plugin writer does not need to figure out what
needs to be downloaded.
2. Plugins can't do end-to-end stream processing.
The Changesets themselves do stream processing,
but when you call into changeset.apply_and_drain()
you have to have fully parsed the metadata
already. Currently when fetching all metadata from
Galaxy, pulp_ansible takes about 380 seconds (6+
min). This means that the actual Changeset content
downloading starts 380 seconds later than it
could. At the heart of the problem, the
fetching+parsing of the metadata is not part of
the stream processing.
The additions/removals can be any interable (like
generator) and by using ChangeSet.apply() and
iterating the returned object, the pluign can "turn
the crank" while downloading and processing the
metadata. The ChangeSet.apply_and_drain() is just a
convenience method. I don't see how this is a
limitation of the ChangeSet.
That is new info for me (and maybe everyone). OK so
Changesets have two interfaces. apply() and
apply_and_drain(). Why do we have two interfaces when
apply() can support all existing use cases (that I know
of) and do end-to-end stream processing but
apply_and_drain() cannot? I see all of our examples
(and all of our new plugins) using apply_and_drain().
The ChangeSet.apply() was how I designed (and
documented) it. Not sure when/who added the
apply_and_drain(). +1 for removing it.
I read through the changeset docs. I think this stream
processing thing is still a problem but perhaps in how we're
presenting the Changeset with it's arguments. I don't think
apply() versus apply_and_drain() are at all related.
Regardless of if you are using apply() or apply_and_drain(),
the Changeset requires an 'additions' and 'removals'
arguments. This sends a clear message to the plugin writer
that they need to compute additions and removals. They will
fetch the metadata to compute these which is mostly how the
changeset documentation reads. To know that they could
present a generator that would correctly allow the metdata
from inside the Changeset is I feel as non-obvious. I want
the high-performing implementation to be the obvious one.
So what about a problem statement like this: "Changesets are
presented such that when you call into them you should
already have fetched the metadata"?
I'm not sure what is meant by "presented". If this means
that we should provide an example of how the ChangeSet can be
used by plugins (with large metadata) in such a way that does
not require downloading all the metadata first - that sounds
like a good idea.
Cool so this is transitioning to ideas for resolution. The
solution to add documentation on how to do this with the existing
interface is one option. My concern with adding additional docs
on how to use the current interface better is that if users
choose to follow the existing docs then they will have the stream
processing problem once again. To me, this suggests that this new
example should actually replace the existing documentation.
Seems like both example would be useful. I'm not convinced that
all plugins would benefit from this. For example: the File plugin
manifest is small and would likely not benefit from the extra
complexity. For complicated plugins (like RPM), can differencing
decision be made before analyzing the entire metadata (eg:
primary.xml)? Also, it's not clear to me how this would work
using the Downloader. Are you suggesting that the plugin would
parse/process metadata files while they're being downloaded?
Perhaps a better understanding of the flow to be supported would
help me understand this.
Yes I am suggesting just that: that the Changesets could facilitate
parse/processing metadata files while actual content named in those
files is also being downloaded. I have a straightforward idea on how
to achieve this. It's short and easy enough to write up (no code), but
I want to make sure I'm not moving beyond the problem statement
without others. Is there more we want to do on these problem
statements, or would answering a bit about one way it could work be
helpful?
The /additions/ can be (and usually is) a generator. The generator can
yield based on metadata as it is downloaded and digested. In this way,
the ChangeSet already facilitates this.
Just to state my expectations: Moving beyond the problem statement I
don't consider to be a commitment to solve it; just an agreement on
what we're solving as we discuss various resolutions. Problem
statements can also always be revisited. Either way forward is fine w/
me, just let me know how we should continue
So far, I'm not convinced that any specific problems/deficiencies have
been identified. That said, you seem to have a different abstraction in
mind. I would be interested in reviewing it and how it would be used by
plugin writers. It may help illustrate the gains that you are envisioning.
Do you see the same challenges I do? Are these the
right problem statements? I think with clear
problem statements a solution will be easy to see
and agree on.
I'm not convinced that these are actual
problems/challenges that need to be addressed in
the near term.
Thanks!
Brian
_______________________________________________
Pulp-dev mailing list
[email protected] <mailto:[email protected]>
https://www.redhat.com/mailman/listinfo/pulp-dev
<https://www.redhat.com/mailman/listinfo/pulp-dev>
_______________________________________________
Pulp-dev mailing list
[email protected] <mailto:[email protected]>
https://www.redhat.com/mailman/listinfo/pulp-dev
<https://www.redhat.com/mailman/listinfo/pulp-dev>
_______________________________________________
Pulp-dev mailing list
[email protected] <mailto:[email protected]>
https://www.redhat.com/mailman/listinfo/pulp-dev
<https://www.redhat.com/mailman/listinfo/pulp-dev>
_______________________________________________
Pulp-dev mailing list
[email protected]
https://www.redhat.com/mailman/listinfo/pulp-dev