Просмотр полной версии : Оптимизация взаимодействия с БД
Fartoviy_Spec
13.06.2013, 17:38
Мир всем. Создаю приложение, которое выполняет ряд операций с данными, поступающими из БД MySql через PHP скрипт в качестве одного XML файла. Дело в том, что в БД уже 2500 строк и когда я в цикле из 2500 итераций произвожу действия, то происходит ошибка, типо превышено время выполнения. Соответственно данных в БД будет становиться все больше и больше.
Подскажите как можно решить данную проблему, чтобы можно было работать с большой БД.
maincode
13.06.2013, 18:50
Оптимизировать код который обрабатывает ваши строки.
Если оптимизировать больше не куда - делайте обработку пачками(по 500 строк например) в одном кадре ENTER_FRAME. Т.е. создаете класс например ParserDB, этот класс запускает событие ENTER_FRAME (или Timer), в каждой итерации обрабатывайте по 500 строк, когда все строки обработаны, класс ParserDB отправляет событие Event.COMPLETE
Например нагрузить этими задачами сервер? Наверняка можно еще на уровне майскл часть задач решить.
Fartoviy_Spec
14.06.2013, 00:24
Спасибо. Буду пробовать все таки разбивать на пачки, так как все задачи необходимо решать в приложении.
caseyryan
14.06.2013, 08:29
так как все задачи необходимо решать в приложении.
Какая-то неправильная архитектура.
Что за данные запрашиваются из mysql? Каким образом?
Эти базы данных уже не первый год разрабатываются, и могут, как уже сказал GBee, очень многое делать самостоятельно, если составить грамотный запрос.
Fartoviy_Spec
14.06.2013, 20:41
Дело в том, что запросов то никаких у меня и нет. Я запрашиваю один xml, который содержит всю БД и в приложении уже с ним работаю.
Просто время обработки на клиенте часто превышает время запроса и выполнения операций на сервере.
Добавлено через 2 минуты
Трудно понять зачем отображать сразу 2500 визуальных объектов, траверсить или сортировать.
AlexCooper
14.06.2013, 21:02
Я могу ошибаться но я так понял что Вы храните в базе запись с текстовым типом данных, текст которого является xml-структурой и при работе с ним Вы его получаете от базы, обрабатываете и перезаписываете?
Уверен AlexCooper, что вы ошибайтесь:)
Автор, переделывайте архитектуру, засасывать всю БД для обработки - это особое извращение.
Astraport
14.06.2013, 22:17
2500 в цикле приложения? Тогда понятно. А для PHP 2500 иттераций не так уж и много. Хотя смотря какая база, какой хостинг и т. п.
AlexCooper
14.06.2013, 22:47
Уверен AlexCooper, что вы ошибайтесь:)
Возможно. Взглянуть бы на структуру, уверен там косяк в архитектуре.
2500 в цикле приложения? Тогда понятно.
2500 это вообще ни о чем.
Fartoviy_Spec
14.06.2013, 23:11
Вот так выглядит кусок БД:
http://www.flasher.ru/forum/attachment.php?attachmentid=29700&stc=1&d=1371232000
Я формирую xml, через php:
<?php
...
$sql=@mysql_query('SELECT * FROM `'.$mysql_table.'`') or die(mysql_error());
echo "<?xml version='1.0' encoding='UTF-8' ?>";
echo "<response>";
for ($i=0;$i<mysql_num_rows($sql);$i++)
{
$res=mysql_fetch_array($sql);
echo "<block>";
echo "<turnir>".$res['turnir']."</turnir>";
echo "<nomer_kruga>".$res['nomer_kruga']."</nomer_kruga>";
echo "<pobeditel>".$res['pobeditel']."</pobeditel>";
echo "<proigravshiy>".$res['proigravshiy']."</proigravshiy>";
echo "<shet>".$res['shet']."</shet>";
echo "<total>".$res['total']."</total>";
echo "<ochki_pobeditel>".$res['ochki_pobeditel']."</ochki_pobeditel>";
echo "<ochki_proigravshiy>".$res['ochki_proigravshiy']."</ochki_proigravshiy>";
echo "</block>";
}
echo "</response>";
?>И получившийся xml полностью загружаю в приложение. Приложение я делаю для себя, то есть денвер использую просто для того, чтобы была возможность работать с БД.
Дальше происходит множество различных операций, одна из которых получение из всей БД списка игроков. Для этого и необходим цикл из 2500 итераций, так как я заношу первое имя в список, а последующие уже заносятся, если такого имени еще не было.
В качестве примера вот эта функция:
function ARR_IGROKOV():void
{
var win_zanos = false;
var lose_zanos = false;
arr_igrokov.push(String(xmllist[0].pobeditel));
for (var i=0; i<xmllist.length(); i++)
{
mass_matchey.push(String(xmllist[i].pobeditel),String(xmllist[i].proigravshiy));
win_zanos = false;
lose_zanos = false;
for (var t=0; t<arr_igrokov.length; t++)
{
if (String(xmllist[i].pobeditel)==arr_igrokov[t])
{
win_zanos = true;
}
if (String(xmllist[i].proigravshiy)==arr_igrokov[t])
{
lose_zanos = true;
}
if ((win_zanos==true)&&(lose_zanos==true))
{
break;
}
}
if (win_zanos==false)
{
arr_igrokov.push(String(xmllist[i].pobeditel));
}
if (lose_zanos==false)
{
arr_igrokov.push(String(xmllist[i].proigravshiy));
}
}
arr_igrokov.sort();
}
Эта операция работает, но на других более громоздких вычислениях происходит ошибка "превышено время выполнения".
Я понимаю, что как то я не правильно делаю, что гружу всю БД, но я не понимаю как быть по другому.
Решение разбивать пачками конечно есть, то ест цикл брать не 2500, а 5 циклов по 500 строк например, как говорил maincode, но это все таки как то не оптимально, да и наверно не везде получится применить.
Что надо отображать за один раз то и надо дергать с сервера.
Добавлено через 3 минуты
То есть можно определить интервал строк, выходя за который, идет запрос к серверу за порцией данных.
Как же все запущено... scan по массиву вместо использования прямого отображения. И в результате вы имеете 2500*2500/2 операций вместо 2500. Используйте обычный map (оно же Object) с нужными ключами. Или два object'а (я особо не разбирался), оно на пару порядков быстрее работать будет. Может быть, оно и из базы правильно выбирается одним запросом, нужно понять что именно в результате вы хотите.
2 Fartoviy_Spec
А зачем вам тогда база данных, с таким подходом? Храните все данные в текстовом файлике. Результат будет абсолютно тот же самый. Но это сарказм, сорри. :)
Сделайте несколько php-скриптов. Каждый из которых будет возвращать вам из вашей базы данных то, что нужно в определенный момент. Выборку же регулируйте через sql. Это мощнейший механизм, а вы его напрочь игнорируете.
Например, для получения уникального списка игроков по двум колонкам, приведенным в примере:
"select distinct * from (select pobeditel from ".$mysql_table." union select proigravshi from ".$mysql_table.") as p_table"
такой запрос на сервере для б.д. в 2500 записей выполняется за сотые доли секунды и вы получаете список, который уже можете преобразовать в xml и отослать флеш-клиенту.
Точно так же и с другими запросами. Не нужно нагружать клиента тем, что база сама сделает гораздо быстрее.
А при том подходе, что у вас, подумайте - когда база дорастет до пары тройки гигабайт (а это вполне нормальный размер для базы), сколько времени будет тратиться на ее полную пересылку и обработку клиентом?
caseyryan
15.06.2013, 11:44
Korchy, а зачем конкатенация в запросах?
Лично мне составление запросов в php тем и нравится, что переменные можно прямо в строки добавлять, без конкатенаций и лишних заморочек.
"select distinct * from (select pobeditel from `$mysql_table` union select proigravshi from `$mysql_table`) as p_table"
а зачем конкатенация в запросах?
Не все версии PHP поддерживают такой синтаксис. Рекомендуется использовать конкатенацию, чтобы было наверняка. Меня за такое побили по рукам наши кодеры )
caseyryan
15.06.2013, 11:57
Понятно) значит я уже на тех, что не поддерживают не кодил
Сколько себя помню пхп всегда так мог. Главное кавычки не перепутать :D . Когда тока начинал, бесило, что иногда не срабатывает :)
AlexCooper
15.06.2013, 12:39
Ой Я вообще перешел на NoSQL (объектно-ориентированые) хранилища и очень доволен. Гибкость структуры как по мне просто колосальная. Но не будем заниматься лирикой.
- На самом деле на сколько я понимаю каждый из пользователей запускает этот скрипт, а результаты не всегда меняются. Да и необходимость в выборе всех данных я думаю тоже отсутствует, так как никто сразу не в силе просмотреть 2500 строк, все должно быть разделено на части (страницы) в случае просмотра информации.
- Проверять если ли пользователь в базе нужно однозначно на уровне базы с правильно выставленным ключем.
- Если у Вас запись вида id1, id2 ( то-есть 1 запись равно игра между двумя пользователями ) я бы сделал ключ по типу Math.min(id1,id2)+'.'+Math.max(id1,id2) таким способом генерируя валидный ключ, ну и можно после обернуть в md5 ( это в случае если Вам нужна выборка конкретной записи ).
- Так же я бы заменил тестовое поле "nomer_kryga" на числовое и записывал бы только число (текст уже подставлял во флеше).
- поля победитель и проигравший представлял в виде его ID, имя подтягивал бы вторым запросом. ( но предварительно выбрал все нужные мне айди и одним запросом выбрал их всех)
- Плюс ко всему я бы еще не использовал xml. На пхп клиенту отдавал бы данные
ecgo json_encode(mysql_fetch_array($sql)); Что тоже бы немного освободило ресурсов как сервера так и интернет-канала.
- Да и передавал данные по amf-протоколу.
XML составлен избыточно неправильно и что мешает делать запросы к самому xml файлу?
caseyryan
16.06.2013, 10:28
- Плюс ко всему я бы еще не использовал xml. На пхп клиенту отдавал бы данные
ecgo json_encode(mysql_fetch_array($sql)); Что тоже бы немного освободило ресурсов как сервера так и интернет-канала.
Этот код диспатчит ошибку ;)
плюс, незачем в джейсон отдавать индексы
echo json_encode(mysql_fetch_array($sql, MYSQL_ASSOC));
ну и желательно конечно сначала проверять, а будут ли вообще какие-то данные по запросу/
Или можно вообще url переменные отдавать вместо джейсона
echo http_build_query(mysql_fetch_array($sql, MYSQL_ASSOC));
Хех... ну, для начала, самое очевидное: возможно вам и нужно передать все 2500 записей из базы на клиент, но показать одновременно вы их не сможете никак. Даже на самых больших пользовательских мониторах у вас бы получилось по пикселу на строку, это если вертикально развернуть.
Отсюда вывод - показывайте понемногу. Более того, ни для MySQL, ни для PHP, ни для AS 2500 итераций в цикле - вообще не заметны. Но нарисовать на экране таблицу с таким количеством полей - тяжело.
Несколько замечаний по поводу связки PHP <-> AS. Во-первых, не используйте mysql_* группу функций ни для чего другого кроме какого-нибудь домашнего архива, который никому кроме вас не нужен. Эти функции передают строки серверу как есть, и это располагает к тому, чтобы злоумышленик передал с ними SQL код, который навредит базе данных. Посмотрите документацию по PDO, как альтернатива - mysqli - но PDO лучше, т.как это промышленный стандарт (более-менее), позволяет проще сменить базу данных, абстрагирует некоторые другие моменты в работе с БД. Не генрируйте XML склеиванием строк. В PHP есть сразу несколько классов которые это делают. Но, еще лучше - поищите библиотеку, которая реализует AMF формат. Это более комактный формат, тем не менее содержащий больше метаданных, чем XML, но он еще удобен тем, что он "родной" для Флеша.
Что касается SQL - пользоваться астерисками в запросах - лучше не нужно. Опять же, если задача одноразовая, то не страшно, но поддерживать код в котором поля таблицы не указаны явно очень сложно, уж поверьте... А еще лучше, возьмите книжку по базам данных, или недавно Станфордский университет проводил он-лайн курсы по базам данных. Там был базисный курс, который хорошо это объяснял. https://class2go.stanford.edu/db/Winter2013/preview/
Psycho Tiger
23.06.2013, 20:28
Посмотрите документацию по PDO, как альтернатива - mysqli
Я не пишу на PHP, но гугл предложил достаточно много полноценных ORM, с has_many из коробки и прочим.
ORM сам по себе не добродетель. PDO однозначно облегчает работу, его недостаток - нет возможности легально использовать какие-нибудь супер нестандартные фичи конкретной DBMS, например в MSSQL есть куча типов данных, но PDO должен поддерживать только те, которые есть и в MySQL и т.д.
ORM - это уже претензия на архитектуру. Предполагает специфическую структуру базы данных, обязывает к специфическому подходу к построению запросов. Кроме всего прочего, SQL - более выразительный язык, чем то, что можно представить через объекты и их свойства. Имитация сложных реляционных отношений в ОО может оказаться непосильным трудом / запутать.
Я видел как обычная авторизация сделаная наивно используя ORM делала более 50 запросов. Конечно, этот механизм в умелых руках может быть очень полезным, но если за него браться без опыта, то можно здорово попасть.
Psycho Tiger
23.06.2013, 22:22
Честно, я не знаю, что такое PDO, но при беглом просмотре мне показалось, что это как раз ORM =)
Ладно, тогда просто объясню свою позицию в 3 словах: MDD – это хорошо.
Претензия на архитектуру... Отчасти соглашусь. По факту, он занимается тем что удобно для программиста делает запросы к БД и складывает результаты в сущности. Почти всегда это именно то, что пришлось бы сделать если этот запрос к БД происходит (конечно, речь о выборках). Сложно ли это в сложных ситуациях? Не настолько, чтобы отказываться от ORM, реализация которого обязана иметь возможность исполнения произвольного SQL-кода. Но с всеми защитами от хакера из коробки.
Я видел как обычная авторизация сделаная наивно используя ORM делала более 50 запросов.
Никто не говорил о том, что владея какой-нибудь либой ORM сразу будут крутые приложения. Можно где угодно выполнять по запросу в цикле. Кстати, хорошие ORM'ы оснащены lazy-load'ом.
Я настаиваю на том, что ORM – абстракция от SQL у приложения, но не у программиста. Всегда нужно проверять что именно запросило БД у приложения просмотром сгенерированного SQL-кода. Зато если сначала сидели на РСУБД, потом перешли, например, на mongo, а потом решили сделать какие-то коллекции как "вшитые" (embed), то я на коне. И при этом я уверен, что у меня нет потенциальных дырок.
К слову, нормальный ORM должен поддерживать те типы, которые есть в языке.
carrotoff
23.06.2013, 22:28
Имитация сложных реляционных отношений в ОО может оказаться непосильным трудом / запутать.
Теоретически, да. Но на практике вряд ли, точнее не было критических ситуаций, хотя сложные выборки под хорошей нагрузкой присутствовали.
Предполагает специфическую структуру базы данных, обязывает к специфическому подходу к построению запросов
Скажите это тем, кто использует рельсы или джангу. Зато (назовем все это ORM) на себя берет из коробки львиную долю модельной логики, и не нужно париться о безопасности запросов.
гугл предложил достаточно много полноценных ORM, с has_many из коробки и прочим.
для php уже придумано не хуже чем на ROR. Гуглите Yii ActiveRecord, Kohana ORM, symfony2 doctrine.
Сами используем для Python Джангу и для php Yii. Это очень хорошие штуки.
bifidokk
24.06.2013, 09:23
зачем вы подсовываете человеку ORM, если ему нужно просто разбить запрос? задан вопрос по одному sql запросу, а тут уже ORM в предложениях.
ORM - это уже претензия на архитектуру. и не стоит юзать ее для простых задач. только хуже сделаете.
Зато (назовем все это ORM) на себя берет из коробки львиную долю модельной логики, и не нужно париться о безопасности запросов. зато придется долго париться с производительностью.
по теме (хотя уже выше отписались)
1. вам же не нужно показывать все 2500 строк. делайте лимит в запросе по количеству показанного и отдавайте только то, что нужно юзеру. например, по 50 строк.
2. откажитесь от xml. посмотрите хотя бы в сторону json
carrotoff
24.06.2013, 10:39
зато придется долго париться с производительностью.
Я тоже долго был сторонником этой мысли. Но практика показала, что возможное падение производительности компенсируется приятными бонусами при разработке и, самое главное, поддержке.
Ну и производительность падает не фатально, а, фактически в пределах допустимой погрешности, да и то, только в каких-то действительно сложных ситуациях.
Честно, я не знаю, что такое PDO, но при беглом просмотре мне показалось, что это как раз ORM =)
PDO - уровень абстракции базы данных. Тут тоже сложная история, т.как исторически задумывалось, что ODBC должно хватить всем, а на более высоком уровне все DBMS будут поддерживать стандары SQL. Но на практике выяснилось, что за несколько десятилетий существования, стандарт так полностью не реализовал практически никто.
Так вот PDO это прослойка, которая помогает учесть несовпадения между диалектами SQL, и при этом создает более общие абстракции. Например, в MySQL есть limit ключевое слово, а в MSSQL его нет, зато есть top для тех же целей. Используя PDO вам не нужно будет переписывать значительную часть кода с одного диалекта SQL на другой, т.как это будет сделано автоматически.
Но с точки зрения ОП, PDO полезен в первую очередь тем, что предлагает механизм для подстановки значений в запросы (не нужно изобретать механизмы защиты от сиквел-инйекций), а так же убирает необходимость самому перерабатывать полученные данные в нужные типы / делает эту процедуру более универсальной. В менее тривиальных ситуациях, он может помочь организовать транзакции / выборки в которых есть несколько результатов одновременно.
Что до ORM, есть задачи где объектный подход ничего хорошего не сулит. Например, обработка статистических данных, таких как, моделирование моллекул белков / извлечение информации из текста на есстесственном языке. Т.е. ситуации, когда таблицы не хранят в себе состояние, которое можно представить как объект, а наборы отношений, которые лучше представляются типами данных типа графов.
Очень походит на разговор глухого со слепым.
Пока автор топика не разберется как взаимодействовать с БД (PHP, а потом уже mysqli или PDO)
и что это такое на ощупь, данная дискуссия останется в статусе прикола.
Psycho Tiger
24.06.2013, 13:00
Но на практике выяснилось, что за несколько десятилетий существования, стандарт так полностью не реализовал практически никто.
Насколько мне известно, наоборот: стандарт реализовали все, только каждый накидал сверху ещё разных "киллерфич".
Так вот PDO это прослойка, которая помогает учесть несовпадения между диалектами SQL, и при этом создает более общие абстракции. Например, в MySQL есть limit ключевое слово, а в MSSQL его нет, зато есть top для тех же целей. Используя PDO вам не нужно будет переписывать значительную часть кода с одного диалекта SQL на другой, т.как это будет сделано автоматически.
Ок. А чем по твоему занимается ORM? =)
Но с точки зрения ОП, PDO полезен в первую очередь тем, что предлагает механизм для подстановки значений в запросы (не нужно изобретать механизмы защиты от сиквел-инйекций)
Кстати, этим тоже занимается ORM. Я об этом писал.
а так же убирает необходимость самому перерабатывать полученные данные в нужные типы / делает эту процедуру более универсальной.
И об этом. Это диалог со мной или просто? Это всё делает как раз ORM.
Сравнивать ODBS и ORM вообще не стоит. ODBC – драйвер, который умеет делать разные операции с таблицами. ORM уже работает с отношениями. Это абстракция выше. Вероятно, что многие реализации ORM являются надстройками над ODBC.
Раз такая пьянка, то я посмотрел что есть PDO и оказалось, что это обёртка над ODBC (или выглядит так, как обёртка). И честное слово, у меня нету идей по которым ты рекоммендуешь его как альтернативу ОРМу. Да, они делают похожие вещи – только ORM навяжет делать красиво и отDRYено.
Тут звучал такой аргумент, что мол, это медленно. Вообще, стоило подумать о производительности выбирая PHP, но речь сейчас не об этом.
Если этот проект мелкий, то зачем тратить время и силы на "ручную" поддержку фич, которые уже есть? Мелкие проекты априори не хайлоад, а красивый код и быстрая разработка всегда актуальны.
Хайлоад же проекты обязаны горизонтально масштабироваться. Это, конечно, вообще другая тема, но я предвижу боль шардинга без ORM. А с ним можно просто сказать моделькам, где они хранятся.
Что мало где / почти нигде не реализовано в полном объеме из стандарта:
- домейны.
- триггеры.
Частично это есть, местами даже почти все есть, но как правило все-таки не все. Триггеры особенно в контексте виртуальных таблиц - это очень заморочливая сама по себе вещь, с которой так просто не разобраться. MySQL в этом плане самый простой и наивный, мало чего умеет, и может иногда гадость сделать. Postgres - ближе всего к стандарту, наверное.
ОРМ - это связывание таблиц с объектами в прикладной программе. Для этих целей придуман специальный вид UML, который позволяет выяснить отношения между таблицами, такие как наследование / компизиция, и перевести это в описания классов программы, которая работает с базой данных.
ПДО ничем таким не занимается, он только обеспечивает совместимость / устраняет наиболее часто встречающиеся неудобства.
Использовать ПДО - ничуть не сложнее / не накладнее, чем использование библиотек привязаных к конкретной базе данных. Это вас ни к чему на уровне проектирования программы не обязывает. Используя ПДО вы можете вообще объекты не создавать, если ван не нравится.
Не нужно сначала разбираться с mysql_* группой функций, а потом переходить на использовние ПДО - ничего хорошего вы из mysql_* не почерпнете / ничего не потеряете если будете всегда использовать ПДО.
Psycho Tiger
25.06.2013, 09:43
Используя ПДО вы можете вообще объекты не создавать, если ван не нравится.
Именно. Это и есть ключевой момент: хорошей практикой является их как раз создавать. Лучше – модели, на крайняк – VO. Реальные запросы оборачивать в другие методы (скоупы) для последующего реиспользования. Создавать виртуальные атрибуты, калькулировать нужные значения. Список плюсов у создания модели гораздо больше, чем у не создания :)
ПДО ничем таким не занимается, он только обеспечивает совместимость / устраняет наиболее часто встречающиеся неудобства.
С моей стороны речь про то, что ORM тоже обеспечивает совместимость и тоже устраняет неудобства.
хорошей практикой является их как раз создавать.
С чего бы это? А если в языке их нет? Или, если это совсем плохо подходит к задаче? Например, в базе у вас хранится несколько миллионов молекул белков (расписаных как последовательности аминокислот), заводить на каждый белок по классу? Может еще наследовать их как-то? :) Есть много задачь, где обьекты не особо в тему...
Опять же, используете вы ПДО или нет - использовать или не использовать объекты - сугобо лично ваше дело. ПДО нет дела до того, что вы будете делать дальше с данными.
ОРМ - это привязка объектов к таблицам (отношениям). Это не техническое действие, а теоретическое, как, например, умножение. Продолжая аналогию с умножением: ПДО - это конвенция о том, какой последовательностью битовых операций на процессорах архитектуры x86-64 выполнять умножение, в какие регистры помещать множители, а в какой результат и т.д.
Можно ли использовать ПДО для реализации ОРМ? - да.
Можно ли использовать ОРМ для реализации ПДО? - нет.
Может ли существовать ОРМ без ПДО? - да.
Может ли существовать ПДО без ОРМ? - да.
Psycho Tiger
25.06.2013, 14:33
Например, в базе у вас хранится несколько миллионов молекул белков (расписаных как последовательности аминокислот), заводить на каждый белок по классу?
А как они в базе представлены? Отображение запись в БД <-> объект в языке можно сделать всегда.
Я уже хорошо понял, чем ПДО от ОРМа отличается, не стоит повторять. Но вот примеры... Часто ли приходится на пхп решать задачи о белках или задачи, в которых полученные с таблиц данные противопоказано вязать с моделями? :)
Да, ОРМ вполне себе может быть без адаптеров к базам данных. Но мне доводилось использовать только те, которые были с ним.
Можно, но иногда бессмысленно: зачем вам миллион разных уникальных объектов-синглотонов?
Представлены? Ну, я видел разные варианты, более специализорованое ПО использует не реляционные базы, а графовые. Но можно и просто создать таблицу с несколько тысяч колонок и считать колонки порядковыми номерами аминокислот.
На ПХП - ну бывает на самом деле, в лаборатории, где работала моя знакомая большей частью использовали Перл и Питон, но ПХП от них в этом смысле не сильно отличается. У Питона просто гораздо лучше с математикой, поэтому он предпочтительнее, но если бы, например, драйвер для какой-то базы был только для ПХП, то и его бы использовали.
Работает на vBulletin ® версия 3.7.3. Copyright ©2000-2026, Jelsoft Enterprises Ltd. Перевод: zCarot
Copyright © 1999-2008 Flasher.ru. All rights reserved.