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

[com_fields] Fetch all custom field values in one query #14558

Merged
merged 12 commits into from
Mar 27, 2017

Conversation

laoneo
Copy link
Member

@laoneo laoneo commented Mar 13, 2017

Pull Request for Issue #14473.

Summary of Changes

Right now when the values of the fields are fetched, for every field a query is done. This pr changes the behavior to one query for all fields.

Integer indexes are faster to lookup than string indexes, the context column got removed from the field_values table, we gain a bit of performance here as well.

Ping @Bakual and @mbabker for review.
Ping @alikon and @csthomas did I change the installer and upgrade scripts correctly?

Testing Instructions

  • Create some fields.
  • Edit an article.
  • Set some values for the fields.
  • Save the article.
  • View the article on the front.

Expected result

All of these should work as before, but with less queries. When the debugging is on, then you should see less queries logged.

Actual result

It works, but with more queries.

Documentation Changes Required

None.

$fieldsQuery = $this->getDbo()->getQuery(true);
$fieldsQuery->select($fieldsQuery->qn('id'))
->from($fieldsQuery->qn('#__fields'))
->where($fieldsQuery->qn('context') . ' = ' . $fieldsQuery->q($context));
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks wrong to me as you have removed context from the db table ;)

Copy link
Member Author

@laoneo laoneo Mar 13, 2017

Choose a reason for hiding this comment

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

That query is against #__fields and not #__fields_values. It is correct.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah thanks i got confused. 👍

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you know if we use in the core a delete with an inner join as subselects are slower? I couldn't find a way to do it.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know that we use it. But that does not mean that we don't use it. 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not that concerned about performance for deleting fields. It's not done multiple times per second usually. Much more important that it works reliably 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

I saw that, but also doesn't use an inner join.

}

if ($field->value === '' || $field->value === null)
if (!isset($field->value) || $field->value === '' || $field->value === null)
Copy link
Contributor

Choose a reason for hiding this comment

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

$field->value === null this will never happen. isset() will check that.

@laoneo
Copy link
Member Author

laoneo commented Mar 13, 2017

Thanks @csthomas removed the double null check.

@@ -66,11 +66,9 @@ CREATE TABLE IF NOT EXISTS `#__fields_groups` (

CREATE TABLE IF NOT EXISTS `#__fields_values` (
`field_id` int(10) unsigned NOT NULL,
`context` varchar(255) NOT NULL,
`item_id` varchar(255) NOT NULL COMMENT 'Allow references to items which have strings as ids, eg. none db systems.',
`value` text NOT NULL DEFAULT '',
Copy link
Contributor

Choose a reason for hiding this comment

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

You are not changed it now, but database field with type TEXT NOT NULL can not have default value on mysql.
https://dev.mysql.com/doc/refman/5.7/en/blob.html

There is a lot of such mistakes in joomla but please do not repeat it.
I see 2 solutions. Allow to NULL or always set default value in php code.

Copy link
Member Author

Choose a reason for hiding this comment

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

That sort of stuff should be done in a separate pr, basically on thing in one pr, otherwise it gets harder to review.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I want only mention about it.

Copy link
Member Author

Choose a reason for hiding this comment

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

No problem.

@@ -109,7 +109,7 @@ protected function getStoreId($id = '')
$id .= ':' . serialize($this->getState('filter.assigned_cat_ids'));
$id .= ':' . $this->getState('filter.state');
$id .= ':' . $this->getState('filter.group_id');
$id .= ':' . print_r($this->getState('filter.language'), true);
$id .= ':' . serialize($this->getState('filter.language'));
Copy link
Member

Choose a reason for hiding this comment

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

serialize? :-)

Copy link
Member Author

Choose a reason for hiding this comment

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

Why not, it is an array and serialize is also used for the assigned cats.


if (!key_exists($key, $this->valueCache))
{
$this->valueCache[$key] = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

If I read the code correctly, this line is not needed. You set $this->valueCache[$key] = $data; later already

Copy link
Member Author

Choose a reason for hiding this comment

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

True


$query->select(array($query->qn('field_id'), $query->qn('value')))
->from($query->qn('#__fields_values'))
->where($query->qn('field_id') . ' in (' . implode(',', ArrayHelper::toInteger($fieldIds)) . ')')
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be IN instead of in. Am I correct that we always use upper case string for SQL keywords?

@Bakual
Copy link
Contributor

Bakual commented Mar 14, 2017

After thinking about this a bit, the only question I have is if we really need both the "getFieldValue" and "getFieldValues" methods. They do quite the same just instead of using a "->where field_id IN (...)" instead of "->where field_id = ...".
That could also be done using the same method with a conditional based on the field_id parameter (multiple ids vs single integer).

@mbabker
Copy link
Contributor

mbabker commented Mar 14, 2017

Please don't combine the two. That violates SRP.

@Bakual
Copy link
Contributor

Bakual commented Mar 14, 2017

Stupid question, what is SRP?

@rdeutz
Copy link
Contributor

rdeutz commented Mar 14, 2017

single responsibility prinzip

@Bakual
Copy link
Contributor

Bakual commented Mar 14, 2017

Makes sense 😄

So better to have a method to fetch the value(s) for a single field and one for fetching values for multiple fields allthough they do basically the same thing?
If that's preferred, then it is fine as it is. It was just something to consider 👍

@mbabker
Copy link
Contributor

mbabker commented Mar 14, 2017

Right. Basically you want your methods to be responsible for one task and one task only. Fetching the data for a single field and fetching the data for multiple fields are two different tasks, will presumably have two different return types (one might be a value object and the other may be a collection of value objects), and may be post-processed by two different code paths (even if you're doing a foreach loop and it just calls the path for the single item, it still has to have that extra logic). So it's better from a structural standpoint to separate those out.

@laoneo
Copy link
Member Author

laoneo commented Mar 15, 2017

Changed the getValue function to use getValues and removed the not needed code as pointed out by @joomdonation. Additionally added some inline comments to explain what the function is doing.

@rdeutz
Copy link
Contributor

rdeutz commented Mar 15, 2017

@laoneo when you on it could you fix the code style issues

@laoneo
Copy link
Member Author

laoneo commented Mar 15, 2017

@rdeutz which ones? Travis is running trough.

@laoneo
Copy link
Member Author

laoneo commented Mar 15, 2017

Ah sorry, didn't realize that drone is now doing the CS stuff. Give me a minute.

@zero-24
Copy link
Contributor

zero-24 commented Mar 15, 2017

done @laoneo

@laoneo
Copy link
Member Author

laoneo commented Mar 15, 2017

@rdeutz all good now.

@laoneo
Copy link
Member Author

laoneo commented Mar 20, 2017

Should we also add here a release blocker label as it reduces the amount of SQL queries?

@mbabker
Copy link
Contributor

mbabker commented Mar 20, 2017

Added due to the major performance issues this PR aims to address.

@joomdonation
Copy link
Contributor

I have tested this item 🔴 unsuccessfully on c2af37b

There are SQL error causes by this PR:

  1. I created two text fields

  2. I created to articles

  3. I created a menu item to link to category blog layout menu option

Access to that menu item, see this error

1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ') AND item_id = '2'' at line 3

The actual SQL command is

SELECT field_id,value FROM jos_fields_values WHERE field_id IN () AND item_id = '2'

Look like in some condition, field_id doesn't have any data for some reasons


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/14558.

@zero-24 zero-24 added this to the Joomla 3.7.0 milestone Mar 24, 2017
@joomdonation
Copy link
Contributor

BTW, in my test, look like the custom fields are not being displayed for Category Blog menu option. So in that case, could we stop querying the database to get data? (I haven't tested this feature much so far, so maybe I just mis-understand how it works)

@mbabker
Copy link
Contributor

mbabker commented Mar 24, 2017

I don't think I would hardcode the plugin context to exclude category views. In theory someone could do a category layout using fields if desired.

@joomdonation
Copy link
Contributor

joomdonation commented Mar 24, 2017

OK. That makes sense (although with default usage like this, it is a waste of resource to query data without using it - It might has performance impact). Also, one thing about category view which I cannot test right now because above error. Before patch, for each item, an SQL query is is generated to get data for a field, so with 2 articles, 2 fields, 4 query total. I guess if this patch works, there will only one query for one article, so still 2 query. Do we want to merge it to one query to get data for all custom fields of all articles (not sure if it is possible)

@Bakual
Copy link
Contributor

Bakual commented Mar 24, 2017

Do we want to merge it to one query to get data for all custom fields of all articles (not sure if it is possible)

Not possible with the current plugin events. You would need a new event for the whole list.

@laoneo
Copy link
Member Author

laoneo commented Mar 25, 2017

@joomdonation I'v added a test if the field ids are empty. Now the error should be gone.

@joomdonation
Copy link
Contributor

I have tested this item ✅ successfully on 23fac40


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/14558.

@joomdonation
Copy link
Contributor

OK. It works now. Unrelated to this PR, on category blog page, still one query per article if you want to improve (in case it can be improved)

@laoneo
Copy link
Member Author

laoneo commented Mar 25, 2017

Open an issue for it. But as @Bakual said, it needs a new event for it, probably not a bad idea, but I'm not sure if it is doable in the 3 series.

@klas
Copy link
Contributor

klas commented Mar 26, 2017

I have tested this item ✅ successfully on 23fac40

with 100 items in list number of queries is reduced from 1000 to 100, there is also jsut a single query on single item.


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/14558.

@klas
Copy link
Contributor

klas commented Mar 26, 2017

This is much better but still not quite there (not the fault of this PR), we have the same problem with tags.


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/14558.

@laoneo
Copy link
Member Author

laoneo commented Mar 27, 2017

Guess RTC?

@sbouey
Copy link

sbouey commented Mar 27, 2017

work now with Falang

@laoneo laoneo deleted the cf/performance branch March 27, 2017 11:50
@PhilETaylor

This comment was marked as abuse.

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.