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

Execute Queries in RQ #4413

Merged
merged 28 commits into from
Dec 30, 2019
Merged

Execute Queries in RQ #4413

merged 28 commits into from
Dec 30, 2019

Conversation

rauchy
Copy link
Contributor

@rauchy rauchy commented Nov 28, 2019

What type of PR is this? (check all applicable)

  • Refactor

Description

Moves query executions from Celery to RQ.

  • Fix tests
  • Remove all Celery monitoring stuff
  • Try to stick to the term "job" wherever possible (remove "task"s)
  • Test on different data sources
  • Perform load tests

I was really tempted to clean up the code as I went along with this refactoring, but I decided to move things as they are and get them working on RQ, then simplify.

Related Tickets & Documents

#4307

'scheduled': scheduled_query_id is not None,
'query_id': metadata.get('Query ID'),
'user_id': user_id
})
Copy link
Member

Choose a reason for hiding this comment

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

👋 bye, bye.

Copy link
Member

Choose a reason for hiding this comment

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

Btw, does the RQ API let us segment tasks by the same dimensions (org_id, data_source_id, query_id, user_id)?

else:
self._async_result = AsyncResult(job_id, app=celery)
def __init__(self, job):
self._job = job if isinstance(job, Job) else CancellableJob.fetch(job, connection=rq_redis_connection)
Copy link
Member

Choose a reason for hiding this comment

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

Maybe class method to create them to avoid confusion and weird issues? (from_job_id and from_job)


if isinstance(result, (TimeLimitExceeded, SoftTimeLimitExceeded)):
result = self._job.result
if isinstance(result, JobTimeoutException):
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure we still return this one.

@@ -111,10 +109,10 @@ def enqueue_query(query, data_source, user_id, is_api_key=False, scheduled_query
if job_id:
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 know I said I’ll port enqueue_query from Celery to RQ as is and deal with refactoring later, but I’m really tempted to use RQ’s ability to set custom IDs to jobs and get rid of the whole lock mechanism using it (i.e. job IDs will be rq:job:<ds.id>:<query_hash> ). WDYT @arikfr?

Copy link
Member

Choose a reason for hiding this comment

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

This is indeed tempting! But --

  1. We lose ability to track down specific invocation (in logs and such), because every invocation has the same id.
  2. Current hash implementation needs fixing (Case insensitive parameters in Redash query result cache #2137).
  3. Are we sure job IDs were meant to be used this way?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. That could be easily solved by a custom description or meta attribute holding an invocation counter.
  2. That needs fixing regardless, but does that affect whether we should drop locks?
  3. It's not a hack, if that's what you mean. RQ docs specify that you can use a predetermined ID instead of the auto-generated one.

Copy link
Member

Choose a reason for hiding this comment

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

RQ docs specify that you can use a predetermined ID instead of the auto-generated one.

But do they expect the ID to be non unique/reusable? An execution's id feels like something that supposed to be unique, I'm just concerned about the can of worms we're opening if we assign a non unique one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

All jobs scheduled from rq-scheduler reuse the same identifier :)

Copy link
Member

Choose a reason for hiding this comment

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

You might want to check out: rq/rq#793.

Copy link
Member

Choose a reason for hiding this comment

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

TL;DR:

What sirex said, the job will be executed twice (if the job is still in Redis).

Copy link
Contributor Author

@rauchy rauchy Dec 2, 2019

Choose a reason for hiding this comment

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

I fail to see the issue. We still check for the job's existence in Redis. We get the same effect of a lock by checking if the job is queued. We just don't have to worry about maintaining another key and expiring it.

Copy link
Member

Choose a reason for hiding this comment

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

I thought you were suggesting reusing job ids to avoid having to implement our own lock.

While we don't need to worry about another key, we do need to worry about lots of other things and fully understand how rq works. Considering that the current implementation works and we have reasonable processes for it, I don't think we will gain much here at this stage, until we're more familiar with RQ. The risk vs. gain is just not worth it.

@@ -26,75 +27,6 @@ def _unlock(query_hash, data_source_id):
redis_connection.delete(_job_lock_id(query_hash, data_source_id))


class QueryTask(object):
Copy link
Member

Choose a reason for hiding this comment

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

👋 good bye old friend. This class survived since the very first iterations of Redash (it was actually named Job back then), when we had my half-assed background job implementation and Tornado as the web server.

@rauchy
Copy link
Contributor Author

rauchy commented Dec 8, 2019

Also, can't we just call push_connection when starting the app or something?

After too much frolicking, I realized that setting this at the start of the app doesn't play nice with Flask's threads and RQ's LocalStack. I'll keep the pre-request & post-request for now unless you can see any fundamental issues with it.

@arikfr
Copy link
Member

arikfr commented Dec 8, 2019

I'll keep the pre-request & post-request for now unless you can see any fundamental issues with it.

Do we really push a request context when starting the app and/or executing a job?

@rauchy
Copy link
Contributor Author

rauchy commented Dec 8, 2019

@arikfr which query runners do you think I should try this on?

@rauchy rauchy changed the base branch from master to python-3 December 9, 2019 08:01
@rauchy rauchy changed the base branch from python-3 to master December 9, 2019 08:01
@rauchy rauchy marked this pull request as ready for review December 11, 2019 21:34
@rauchy rauchy mentioned this pull request Dec 26, 2019
1 task
@rauchy rauchy merged commit 329e859 into master Dec 30, 2019
@rauchy rauchy mentioned this pull request Jan 5, 2020
1 task
@guidopetri guidopetri deleted the execute-query-in-rq branch November 4, 2023 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants