<div dir="ltr"><div>tldr; REST calls to Update or Delete Importers, Publishers, and Repositories will return *asynchronously*.</div><div><br></div>We met last week to discuss the question. The case of a Delete called during a sync demonstrates why this is necessary. If Deletes were synchronous, each plugin would have to handle the possibility that their models have been removed.<div><br></div><div>By deciding that Updates and Deletes are asynchronous, we are able to make a general promise to the plugin writers: The repository and its associated importer and publishers will not be changed by platform Pulp except during reserved calls. This means that when plugin code is running it has a sort of write lock on the relevant data.</div></div><div class="gmail_extra"><br><div class="gmail_quote">On Wed, Apr 26, 2017 at 9:55 AM, Jeff Ortel <span dir="ltr"><<a href="mailto:jortel@redhat.com" target="_blank">jortel@redhat.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span class=""><br>
<br>
On 04/25/2017 02:47 PM, Brian Bouterse wrote:<br>
> There are a few use cases and behaviors that I think only an asynchronous update/delete for importers,<br>
> publishers, and repositories will satisfy. Thanks to @asmacdo for covering some of these also.<br>
><br>
> * As a plugin writer, I am in control when my code is running. The data layer is not expected to change other<br>
> than what my code is doing or what I've asked platform to do for me. This same reasoning applies to unit<br>
> associations too (as a similar example of this principle). With unit associations for example, I don't think<br>
> we want new units showing up as part of a repo halfway through the publish operation.<br>
><br>
> * As a plugin writer, I can modify and save the importer without being concerned that I am overwriting data<br>
> the user just updated. This is a write-write race concern. We could say in documentation "don't do that", but<br>
> they may have good reasons to and I don't see a good reason why they can't call save() during a sync() for<br>
> example. Consider that they are defining fields on these subclassed importers and they want to write data into<br>
> them at sync-time.<br>
<br>
</span>This applies to previous use case as well.<br>
<br>
Even with update/delete being asynchronous, I don't think there is any guarantee as to what the data will be<br>
when the task actually runs in a multi-user application.  There is a race condition on the task queue.  Other<br>
users' updates and syncs can be queue in any order around what this user is trying to do.  If this is a<br>
requirement, we may need to include a snapshot of the data in the task.<br>
<span class=""><br>
><br>
> * A user should be able to submit a [sync], [modify the importer], [sync] and only have the update apply to<br>
> the second sync. Synchronous updates make it difficult for a user to determine the impact an update has when<br>
> applied while sync code is running for example.<br>
><br>
> * If users want the ability to apply an importer update apply immediately then can cancel any running syncs<br>
> and their update will apply immediately then, so we're not limiting them in any way.<br>
><br>
> * Deleting data while code is expected to use that data is running will likely create negative user<br>
> experiences. This is a rephrasing of the "don't change the data later while plugin code is running" point from<br>
> earlier but with a twist. If the publisher is deleted while the plugin writer's publish() is running, if<br>
> plugin code tries to read or save, the task will fail at some random point with a traceback, which is not a<br>
> good experience. Contrast that against what you would get with an asynchronous design which would allow a<br>
> clean PulpException to be emitted by the publish() task just before calling the publish() code. Having the<br>
> PulpException which checks in exactly one place will be a better user experience with a nice message instead<br>
> of a deep traceback and easier to maintain (in one place).<br>
<br>
</span>Seems this can be handled cleanly/gracefully for synchronous delete as well.  The plugin should protect<br>
updates with try/catch and raise a PulpException when the DB exception is raised.  The sync would terminate<br>
immediately and the task would report that the importer had been deleted which seems reasonable.<br>
<span class=""><br>
><br>
> Thank you to everyone who is participating in this convo. We don't have agreement, but we do have a good<br>
> dialogue going! What are you thoughts about ^?<br>
><br>
> -Brian<br>
><br>
</span><span class="">> On Fri, Apr 21, 2017 at 2:16 PM, Austin Macdonald <<a href="mailto:austin@redhat.com">austin@redhat.com</a> <mailto:<a href="mailto:austin@redhat.com">austin@redhat.com</a>>> wrote:<br>
><br>
>     I think we have arrived at a consensus around these points:<br>
><br>
>     * Django's ModelForm validations that are used during `full_clean` is an anti-pattern in<br>
>     DjangoRestFramework and should not be used<br>
>     * Validation, by our definition, will be done by the serializers in the API layer<br>
>     * The Task layer should run business logic checks at the time of use<br>
><br>
>     We have not arrived at a consensus on whether Updates/Deletes for Importers/Publishers will be synchronous<br>
>     or asynchronous. I've tried to think through each way, and what it would need to work.<br>
><br>
</span><span class="">>     *Synchronous Update and Delete:<br>
>     *<br>
>     _Validation Heuristic_<br>
</span><span class="">>     The serializers validate in the API layer only, and it is desirable to have that validation occur at the<br>
>     time of the write.<br>
</span><span class="">>     *<br>
>     *<br>
>     _Concurrent Writes:_<br>
</span><span class="">>     Let's assume synchronous Updates and Deletes. There is a possibility of concurrent writes because<br>
>     sync/publish tasks write to the importer/publisher tables respectively and these tables can also be<br>
>     written by the Update API at any time. If there is a collision the last write wins.<br>
><br>
>     Workaround:<br>
>     Concurrent writes may not be a big deal because they should be not affect each other. In platform, sync<br>
>     and publish only write to the last_sync/last_publish fields, which should never be written by the update<br>
>     API. If the writes are restricted to only the affected fields, then there would be no clobbering. As long<br>
>     as plugin writers' implementations of sync and publish do not write to the same fields allowed in an API<br>
>     update, this concurrency is safe. We would obviously need to be clearly documented for plugin writers.<br>
><br>
</span>>     _Dealing With Broken Configurations_<br>
<span class="">>     For example, while a sync task is WAITING, the user updates an importer in a way that breaks sync. The<br>
>     importer configuration should be read only once at the start of a sync. If this configuration has been<br>
>     deleted or is not syncable, business logic checks catch this and fail immediately. Even if the<br>
>     configuration is changed during the sync task, the sync task will continue with the original<br>
>     configuration. The sync task will update last_sync, but will not write to other fields. I don't see a<br>
>     problem here, as long as this is documented for the plugin writer.<br>
><br>
</span>>     _Summary_:<br>
<span class="">>     I don't see any concurrency problems here. The downside of this design is that sync and publish,<br>
>     (implemented by plugin writers), should not write to the same fields as API updates.<br>
><br>
</span><span class="">>     *<br>
>     *<br>
>     *Asynchronous Update and Delete*<br>
><br>
>     _Validation Heuristic_<br>
</span><span class="">>     Assuming async updates, the data comes into the API layer where it is validated by the serializers. The<br>
>     update reserves a task (reservation is based on repository) and waits to be performed. It is usually<br>
>     undesirable to separate validation from use. There could be concern that data validated in the API layer<br>
>     could become unacceptable by the time the Task layer actually performs the write.<br>
><br>
>     Practically, this separation may not be an issue. Valid data becoming invalid involves validations that<br>
>     are dependent on state. We can split these validations into two categories, uniqueness validations and<br>
>     multi-field validations. Fortunately, the Django ORM will enforce uniqueness validation, so the database<br>
>     is protected, but we might have to handle write failures. Multi-field validations are probably not<br>
>     validations by our definition, but are business logic, which could be checked in the Task layer.<br>
</span><span class="">>     _<br>
>     _<br>
>     _Concurrent write prevention_<br>
</span><span class="">>     Leveraging the reserved resource system, concurrent writes could not be a problem because writes could<br>
>     never be concurrent at all. Syncs, Updates, Deletes, Publishes all make reservations based on the<br>
>     repository (this is an assumption based on what we do in Pulp 2), so each would run in the order that they<br>
>     were called.<br>
><br>
</span>>     _User Controlled Order_<br>
<span class="">>     A user can issue a [sync][update][sync] and be confident that the first sync will use the original<br>
>     importer configuration, and the second sync will use the updated configuration.<br>
><br>
</span>>     _Uncertain State_<br>
<span class="">>     The configuration of an importer is unpredictable if there is a waiting update task from another user.<br>
><br>
><br>
><br>
><br>
><br>
><br>
>     ______________________________<wbr>_________________<br>
>     Pulp-dev mailing list<br>
</span>>     <a href="mailto:Pulp-dev@redhat.com">Pulp-dev@redhat.com</a> <mailto:<a href="mailto:Pulp-dev@redhat.com">Pulp-dev@redhat.com</a>><br>
>     <a href="https://www.redhat.com/mailman/listinfo/pulp-dev" rel="noreferrer" target="_blank">https://www.redhat.com/<wbr>mailman/listinfo/pulp-dev</a> <<a href="https://www.redhat.com/mailman/listinfo/pulp-dev" rel="noreferrer" target="_blank">https://www.redhat.com/<wbr>mailman/listinfo/pulp-dev</a>><br>
><br>
><br>
><br>
><br>
<div class="HOEnZb"><div class="h5">> ______________________________<wbr>_________________<br>
> Pulp-dev mailing list<br>
> <a href="mailto:Pulp-dev@redhat.com">Pulp-dev@redhat.com</a><br>
> <a href="https://www.redhat.com/mailman/listinfo/pulp-dev" rel="noreferrer" target="_blank">https://www.redhat.com/<wbr>mailman/listinfo/pulp-dev</a><br>
><br>
<br>
</div></div><br>______________________________<wbr>_________________<br>
Pulp-dev mailing list<br>
<a href="mailto:Pulp-dev@redhat.com">Pulp-dev@redhat.com</a><br>
<a href="https://www.redhat.com/mailman/listinfo/pulp-dev" rel="noreferrer" target="_blank">https://www.redhat.com/<wbr>mailman/listinfo/pulp-dev</a><br>
<br></blockquote></div><br></div>