{"action":"created","issue":{"url":"https://api.github.com/repos/openaddresses/machine/issues/15","labels_url":"https://api.github.com/repos/openaddresses/machine/issues/15/labels{/name}","comments_url":"https://api.github.com/repos/openaddresses/machine/issues/15/comments","events_url":"https://api.github.com/repos/openaddresses/machine/issues/15/events","html_url":"https://github.com/openaddresses/machine/issues/15","id":51903445,"number":15,"title":"Machine task queue architecture","user":{"login":"NelsonMinar","id":382136,"avatar_url":"https://avatars.githubusercontent.com/u/382136?v=3","gravatar_id":"","url":"https://api.github.com/users/NelsonMinar","html_url":"https://github.com/NelsonMinar","followers_url":"https://api.github.com/users/NelsonMinar/followers","following_url":"https://api.github.com/users/NelsonMinar/following{/other_user}","gists_url":"https://api.github.com/users/NelsonMinar/gists{/gist_id}","starred_url":"https://api.github.com/users/NelsonMinar/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NelsonMinar/subscriptions","organizations_url":"https://api.github.com/users/NelsonMinar/orgs","repos_url":"https://api.github.com/users/NelsonMinar/repos","events_url":"https://api.github.com/users/NelsonMinar/events{/privacy}","received_events_url":"https://api.github.com/users/NelsonMinar/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":{"login":"migurski","id":58730,"avatar_url":"https://avatars.githubusercontent.com/u/58730?v=3","gravatar_id":"","url":"https://api.github.com/users/migurski","html_url":"https://github.com/migurski","followers_url":"https://api.github.com/users/migurski/followers","following_url":"https://api.github.com/users/migurski/following{/other_user}","gists_url":"https://api.github.com/users/migurski/gists{/gist_id}","starred_url":"https://api.github.com/users/migurski/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/migurski/subscriptions","organizations_url":"https://api.github.com/users/migurski/orgs","repos_url":"https://api.github.com/users/migurski/repos","events_url":"https://api.github.com/users/migurski/events{/privacy}","received_events_url":"https://api.github.com/users/migurski/received_events","type":"User","site_admin":false},"milestone":{"url":"https://api.github.com/repos/openaddresses/machine/milestones/1","labels_url":"https://api.github.com/repos/openaddresses/machine/milestones/1/labels","id":916208,"number":1,"title":"Ditch Node","description":"Excise Node.js dependencies from normal processing use.","creator":{"login":"migurski","id":58730,"avatar_url":"https://avatars.githubusercontent.com/u/58730?v=3","gravatar_id":"","url":"https://api.github.com/users/migurski","html_url":"https://github.com/migurski","followers_url":"https://api.github.com/users/migurski/followers","following_url":"https://api.github.com/users/migurski/following{/other_user}","gists_url":"https://api.github.com/users/migurski/gists{/gist_id}","starred_url":"https://api.github.com/users/migurski/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/migurski/subscriptions","organizations_url":"https://api.github.com/users/migurski/orgs","repos_url":"https://api.github.com/users/migurski/repos","events_url":"https://api.github.com/users/migurski/events{/privacy}","received_events_url":"https://api.github.com/users/migurski/received_events","type":"User","site_admin":false},"open_issues":0,"closed_issues":22,"state":"open","created_at":"2014-12-28T18:03:27Z","updated_at":"2015-01-30T06:58:59Z","due_on":null,"closed_at":null},"comments":27,"created_at":"2014-12-13T23:46:15Z","updated_at":"2015-01-30T06:58:59Z","closed_at":"2015-01-30T06:58:59Z","body":"Proposal: redesign machine with a task queue abstraction where a single task is processing one source. Cache, download, conform, extract a single source at a time. Doesn't require a fundamental rewrite, mostly refactoring existing code. Assumes all code running in Python, although shelling out to Node or ogr2ogr should be possible.\r\n\r\nThe current system runs all sources in sequential stages in a single Python process. `process.py` invokes code in `run.py` to first `run_all_caches`, then `run_all_conforms`, then `run_all_excerpts`. It then generates an HTML report from all the status objects. This works fine but has some drawbacks. The whole run currently takes 10 hours and will only take longer as more sources are added. Work is lost if the job fails, particularly awkward with EC2 spot instances. The code mixes application logic with thread and process logic.\r\n\r\nI think it makes more sense to slice the work the other way, process each source as an independent job. The sources are themselves independent (different servers, different schemas, etc). It will make it easier to do test runs on a single source. It will make it easier to only re-run a conform if the source changed. It also presents a nice atomic unit of work to a task scheduling abstraction.\r\n\r\nDetails below.\r\n\r\n##### Task\r\n\r\nA single task processes a single user-contributed data source file. A task consists of several subtasks to perform sequentially:\r\n\r\n* Download from source URL / ESRI store. (Optionally cache it; see below.)\r\n* Conform the data, transforming the source to the OpenAddress CSV schema.\r\n* Extract the data, presenting a few lines from the source for user inspection.\r\n* Communicate task execution stats to the reporting system.\r\n\r\nI propose each subtask be written as a Python module. Subtasks should also be able to run standalone on a local machine for development. Each subtask should have tests from a synthetic data source.\r\n\r\n##### Source caching\r\n\r\nThe current Node code downloads data from the official source and caches it to S3, and is intelligent about only downloading if the data changed. Mike indicated that S3 dependence makes development challenging. But it's also useful in the (frequent) case the source goes down; see issue #9. I propose retaining an S3 cache in the system, but make the subtask functions able to work from S3 or local files.\r\n\r\n##### Task Scheduling\r\n\r\nThe simplest scheduler is none at all, just run a Python loop over all sources with some sort of basic threading or multiprocessing. That will be functionally equivalent to where we are today, a single monolithic Python process.\r\n\r\nWe can then migrate to a task queue written to a persistent store. A job wakes up every minute to check if any sources have changed and if so, posts a processing tasks to the queue. Another job wakes up every minute to run tasks on the queue. Is there some AWS-friendly task abstraction we can reuse rather than writing our own?\r\n\r\n##### Reporting\r\n\r\nReporting re-centralizes all the tasks when they are complete. The current system is a batch job sort of report, \"I ran all the sources and the result on Dec 13 is X.\" I think it'd be better to move to a rolling report where each task updates its own little task stats record, then the reporter just has to group all task statuses together and publish an HTML report of the latest status of everything. But that is a product change.\r\n\r\n##### Migration Plan\r\n\r\nI'd start with a simple Python loop scheduler. This means rewriting `process.py:process()` to loop over sources. Most of `jobs.py` would be removed. I think even at this first stage we need some parallelism; perhaps process.py can simply fork a subprocess for each task? The current code for tasks and subtasks mostly exists, just needs to be refactored a bit. (`conform.py` is not yet complete.) The task status reporting requires some thinking.\r\n\r\nOnce that redesign is working as well as the current setup we can then move on to some more ambitious job scheduling system. I think that choice should be driven by what works best with AWS.\r\n\r\nI also think it'd be worth re-examining the logic around caching data in S3 to make sure it's doing the right thing.\r\n\r\n*It's hubris of me to propose a redesign; I wrote this up after some conversations with Mike about the direction he was moving in.*\r\n"},"comment":{"url":"https://api.github.com/repos/openaddresses/machine/issues/comments/72161973","html_url":"https://github.com/openaddresses/machine/issues/15#issuecomment-72161973","issue_url":"https://api.github.com/repos/openaddresses/machine/issues/15","id":72161973,"user":{"login":"migurski","id":58730,"avatar_url":"https://avatars.githubusercontent.com/u/58730?v=3","gravatar_id":"","url":"https://api.github.com/users/migurski","html_url":"https://github.com/migurski","followers_url":"https://api.github.com/users/migurski/followers","following_url":"https://api.github.com/users/migurski/following{/other_user}","gists_url":"https://api.github.com/users/migurski/gists{/gist_id}","starred_url":"https://api.github.com/users/migurski/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/migurski/subscriptions","organizations_url":"https://api.github.com/users/migurski/orgs","repos_url":"https://api.github.com/users/migurski/repos","events_url":"https://api.github.com/users/migurski/events{/privacy}","received_events_url":"https://api.github.com/users/migurski/received_events","type":"User","site_admin":false},"created_at":"2015-01-30T06:58:59Z","updated_at":"2015-01-30T06:58:59Z","body":"BOOM."}}