Вопрос: Подобрать архитектуру распределённого Yii-приложения?

Лучший ответ:
Первое что Вам нужно сделать это разобраться с guid.
Я не пробовал этого в Yii, но насколько я помню навскидку там не должно быть серьезных препятствий к его использованию.
Если есть какие-то наработки, модули модели и т.п., то надо будет их приучить к новой структуре. Поменять типы полей в базе (включая связанные), поменять автоинкремент на генерацию гуид по вашей логике и т.п.

Создаете себе базовый класс наследник от CActiveRecord, скажем CDistributedActiveRecord.
Этот класс у вас будет отвечать как за guid так и за репликацию.
Собственно все АР наследуют от него.

Проектирование архитектуры нужно начать с четкого планирования базы.
Очевидно что часть таблиц у вас будут уместны во всех базах, без фильтрации. К примеру таблица содержащая список организаций в вашей иерархии нет смысла. Вообще стандартная логика обычно такая — «справочники» мы имеем общие, «документы» у каждого свои. К примеру номенклатуру (товары, виды услуг и т.п.) лучше отнести к справочникам, и тиражировать централизованно, а вот к примеру прайс из этой номенклатуры лучше считать документом (но опять де от специфики зависит). «Документы» и «справочники» нам не обязательно делать так строго как в 1С, мы ведь свою архитектуру делаем. К примеру сотрудники/пользователи по логике это справочник, но мы можем отнести его к документу, и вообще сделать два базовых класса для них. Это опять таки сильно зависит от вашей задачи.

Далее вам необходимо будет создать критерий, по которому вы будете определять кому принадлежит тот или иной документ, и нужно ли его передавать. В простейшем случае это будет идентификатор предприятия/подразделения. Очень хорошо будет, если он будет у вас простой, и универсальный. Т.е. чтобы он во всех таблицах которые реплицируются был одинаков. Пусть он будет у нас называться ПредприятиеВладелец.
Обратите внимание, что если вышестоящие подразделения будут не только просматривать данные нижестоящих, но и вносить в них изменения, или еще хуже — создавать связанные с ними новые документы, то вы должны указывать этим документам правильное ПредприятиеВладелец. Если документ подразумевает, что его должны прочитать в подчиненном предприятии (к примеру это документ содержащий резолюцию по заявке полученной из подчиненного подразделения), то необходимо указывать у такого документа ПредприятиеВладелец равное тому, которое было указано в документе связанном с этим (т.е. в нашем примере Владелец из «заявки»). Если вы укажете Владельца равного предприятию в котором был создан документ, то потом придется городить сложные критерии того какой документ передавать при репликации и кому передавать.

Совет — сильно подумайте о внешних ключах. Как это отразится на ваших ручных репликациях.

Теперь перейдем собственно к репликации. Тут я сделаю несколько допущений. Если они неверны, то озвучьте. Для начала я предположу, что вам не нужна реалтайм репликация. Т.е. было бы хорошо, чтобы все документы появлялись в ту же милисекунду в другой базе, но если будет задержка даже в несколько минут, то никто не умрет, не будет нарушена целостность данных и т.п. Второе допущение — скорее всего у вас будет не много маленьких изменений больших записей. Т.е. если вы не создаете запись, а вносите в нее изменения, то вы все равно можете передать всю измененную запись, а не только изменения, и при этом излишние накладные расходы будут не очень высокими. К примеру если вы даже передаете в базе файлик, который весит несколько мегабайт, то вы либо изменяете сам файл и размер изменения большой, либо его описание, которое занимает небольшую часть общего объема. В идеале если у вас ожидается большое количество изменений этого самого описания, без изменения «файла», то лучше разделить это на две связанные таблицы, чем городить сложную репликацию.

Если мои ограничения вам подходят, или вы готовы под них подстроится, то можем переходить к репликации.
Для репликации мы переопределяем методы создания и изменения записей таким образом, чтобы они сохраняли наши изменения в журналы.
Журналы могут быть как у каждой реплицируемой таблицы, так и общие для всех. Зависит от задачи. Журналы у нас содержат в себе только:
инкрементарный номер записи, который представляет из себя «скрытое время», который является сквозным для всех таблиц, возможно название таблицы, и guid изменяемой записи. Также мы указываем что произошло с записью. для целей репликации у нас есть два действия — сохранение и удаление. Нам не интересно была ли запись создана, или редактируется старая — все равно нам ее передавать.

Далее мы создаем еще одну таблицу, в которой у нас будут указаны узлы с которыми у нас предусмотрена связь. Здесь может быть что угодно, включая реквизиты для связи с узлом, но обязательными полями должны быть — признак того, какие именно записи передавать (поскольку некоторые данные могут передаваться в несколько узлов сразу, то нет смысла выносить это в журналы репликации) и два номера обработанных записей: входящий и исходящий (не путать с номерами сообщений в 1с). В этих номерах мы будем отражать какие записи из журналов репликации мы получили или отправили (или если у нас будет оффлайн репликация, то какие изменения мы отправили и получили о них подтверждение о получении). Далее при передаче инфы соседям, мы будем делать запрос в журнал репликации, который будет отбирать только данные с нужным ПредприятиеВладелец (или без ограничения если у нас отправка «наверх») и с номером записи больше чем номер уже обработанный. Имея список записей которые нам надо отдать мы ищем их в соответствующих таблицах, формируем пакет для синхронизации, и отправляем его. При получении пакета мы проверяем записи на факт того, не являются ли эти записи уже отработанными до этого (проверяем по номеру, особенно критично для оффлайн синхронизации).

Обратите внимание — мы не храним все изменения, а храним только номера измененных записей. При получении мы или изменяем запись, или создаем новую если таковой нет, или удаляем если указано удалить.

Как передавать информацию? да как угодно. Хоть по почте кидайте в виде json.

Не забывайте чистить «журналы репликации» от записей которые уже не нужны никаким из узлов с которыми есть связь (т.е. чьи номера меньше чем ВСЕ исходящие номера у всех узлов). Также хочу обратить ваше внимание на размерность счетчика-идентификатора в этом журнале. Делайте его побольше :)

Что еще важно в большой, распределенной системе? Важны тщательно продуманные права пользователей, и важны ЛОГИ. Много больших и разных логов. Еще больше логов. Логи для разбора полетов лучше всего вести отдельно от журналов репликаций. В них требуется большая детализация чем там, с другой стороны они читаются редко, а пишутся часто, так что индексация нужна в меньшей степени. обязательно указывать кто вносил изменения, когда вносил и т.п. В идеале такие журналы держать вообще в отдельной базе или даже в текстовом файле.

Вроде пока всё что вспомнил.

Худший ответ:
У меня похожая ситуация. Разрабатываю SaaS приложение, где много пользователей, при этом каждый сам себе король, у каждого своя нумерация (id) и т.д. и т.п. Тоже думал про несколько баз, но в результате решил использовать одну базу

В БД к таблицам добавляю поля (int) personalID и projectID, а дальше наследуюсь вот от этого класса
abstract class PersonalActiveRecord extends CActiveRecord
{
  public $usePersonalID = true; // Использовать ли персональную нумерацию
  public $personalID = null;
  public $projectID = null;

  protected function personalIdExpression(){
    $sql =
      'COALESCE((SELECT personalID FROM
        (SELECT (MAX(personalID)+1) as personalID FROM '.$this->tableName().' WHERE projectID = '.$this->projectID.')
      as personalID ),1)';
    return new CDbExpression($sql);
  }

  protected function beforeSave(){
    if (!parent::beforeSave()) return false;
      if ($this->isNewRecord) {
        $this->projectID = (int)Y::param('projectID');
        if ($this->usePersonalID)
          if ($this->personalID===null) $this->personalID = $this->personalIdExpression();
      }
    return true;
  }

  public function defaultScope() {
    return array(
      'condition'=>"projectID='".(int)Y::param('projectID')."'",
    );
  }

}