Symfony 2.1 Session Handler MongoDB MongoTimestamp Bug
В этом посте я расскажу, почему нельзя использовать MongoDbSessionHandler в Symfony 2.1 и о том, к чему это привело. А привело это к довольно долгому поиску и анализу бага, который изрядно подпортил нам нервы и заставит нецензурно ругаться в адрес одного француза.
Проблема
Сидел я себе спокойно в своей веточке в git-е, кодил помаленьку, никому не мешал. И вот наконец фича готова, протестирована, и надо проверить её в продакшен окружении. Выкатываем код на несколько минут, сначала всё идёт нормально, а потом бац, сервер отваливается (ошибка по бд MongoDB). Откатываем код – веб-сервер лежит, чистим сессии – он восстанавливается. Wtf?
Анализ
Мы понимали, что косяк как-то связан с сессиями, но конкретно проблему воспроизвести не могли. На девелоперских машинах всё отрабатывало нормально, на stage – тоже, и баг никак не ловился. Но разобраться с ним надо было. Стоит заметить, что в данный момент мы используем параллельно два механизма доступа к сессиям (Zend Framework 1.x Session и Symfony 2 Session). Это конечно не по фен-шую и создаёт ряд неудобств, но явление, как-таковое – временное. При этом Sf2 сессии были только в моей ветке, а Zf – в апстриме. Следовательно надо копаться именно в реализациях сессий в симфони. И я начал копаться. Ошибка на продакшене была такая:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Wed Apr 3 16:52:57.733 [repl writer worker 1] ERROR: exception: wrong type for field () 17 != 9 on: { ts: Timestamp 1364997093000|1, h: 8651743189839434320, v: 2, op: "i", ns: "site.system.indexes", o: { _id: ObjectId('515c33e50d42e2030300000b'), ns: "site.sessions", key: { expiration_time: true }, name: "expiration_time_1" } } Wed Apr 3 16:52:57.733 [repl writer worker 1] Fatal Assertion 16361 0xdc7f71 0xd87cf3 0xc1be26 0xd95821 0xe10879 0x7ff847facd90 0x7ff84733619d /usr/bin/mongod(_ZN5mongo15printStackTraceERSo+0x21) [0xdc7f71] /usr/bin/mongod(_ZN5mongo13fassertFailedEi+0xa3) [0xd87cf3] /usr/bin/mongod(_ZN5mongo7replset21multiInitialSyncApplyERKSt6vectorINS_7BSONObjESaIS2_EEPNS0_8SyncTailE+0x166) [0xc1be26] /usr/bin/mongod(_ZN5mongo10threadpool6Worker4loopEv+0x281) [0xd95821] /usr/bin/mongod() [0xe10879] /lib64/libpthread.so.0(+0x7d90) [0x7ff847facd90] /lib64/libc.so.6(clone+0x6d) [0x7ff84733619d] Wed Apr 3 16:52:57.735 [repl writer worker 1] ***aborting after fassert() failure |
Это значит, что произошло несовпадение типа в поле документа и типа в поле индекса. После этого репликация становится невозможной и база падает. А за ней падает и PHP, и веб-сервер.
Я начал смотреть MongoDbSessionHandler и наш админ обратил моё внимание вот на этот кусок кода.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public function write($sessionId, $data) { $data = array( $this->options['id_field'] => $sessionId, $this->options['data_field'] => new \MongoBinData($data, \MongoBinData::BYTE_ARRAY), $this->options['time_field'] => new \MongoTimestamp() ); $this->getCollection()->update( array($this->options['id_field'] => $sessionId), array('$set' => $data), array('upsert' => true) ); return true; } |
Поле time_field использует тип MongoTimestamp. Смотрим доку по нему (http://php.net/manual/en/class.mongotimestamp.php). А там написано следующее:
1 |
This class is not for measuring time, creating a timestamp on a document or automatically adding or updating a timestamp on a document. Unless you are writing something that interacts with the sharding internals, stop, go directly to MongoDate, do not pass go, do not collect 200 dollars. This is not the class you are looking for. |
Данный класс используется только для шардинга, в других случаях надо использовать вместо него MongoDate. Что кстати и сделано в Symfony 2.2.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public function write($sessionId, $data) { $this->getCollection()->update( array($this->options['id_field'] => $sessionId), array('$set' => array( $this->options['data_field'] => new \MongoBinData($data, \MongoBinData::BYTE_ARRAY), $this->options['time_field'] => new \MongoDate(), )), array('upsert' => true, 'multiple' => false) ); return true; } |
Решение