원인

MongoDB의 shard key size 제한이 512 바이트인데, insert 하려는 document에서 shard key에 해당하는 부분이 512 바이트를 넘어서기 때문에 발생하는 에러



해결

사이즈 기준이 각 키/값별 데이터 사이즈의 합계인지, 아니면 JSON 문자열의 길이인지 뭔지 몰라서 헤맸는데,

BSON size가 기준이었다.


MongoDB 소스코드에서 shard key size를 체크하는 함수의 매개변수 부분을 보면 BSONObj가 넘어가는 걸 확인할 수 있다.

https://github.com/mongodb/mongo/blob/v3.0/src/mongo/s/shard_key_pattern.cpp#L59

(내가 운용하는 MongoDB 버전이 3.0이라 3.0 버전 소스코드 기준임)

const int ShardKeyPattern::kMaxShardKeySizeBytes = 512;
const unsigned int ShardKeyPattern::kMaxFlattenedInCombinations = 4000000;
 
Status ShardKeyPattern::checkShardKeySize(const BSONObj& shardKey) {
    if (shardKey.objsize() <= kMaxShardKeySizeBytes)
        return Status::OK();
 
    return Status(ErrorCodes::ShardKeyTooBig,
                  stream() << "shard keys must be less than " << kMaxShardKeySizeBytes
                           << " bytes, but key " << shardKey << " is " << shardKey.objsize()
                           << " bytes");
}



PHP에서 BSON size를 어떻게 구할 수 있을까 찾아보다가,

php-mongo driver의 함수 중 bson_encode() 가 PHP의 변수를 받아 BSON으로 변환해주는 구현체라는 걸 발견했고,

https://github.com/mongodb/mongo-php-driver-legacy/blob/v1.6/bson.c#L1412

/* {{{ proto string bson_encode(mixed document)
   Takes any type of PHP var and turns it into BSON */
PHP_FUNCTION(bson_encode)
{
    zval *z;
    mongo_buffer buf;
 
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &z) == FAILURE) {
        return;
    }
     
    /** 이하 생략 **/



bson_encode() 함수와 strlen() 함수를 이용해서 MongoDB shell 명령어인 Object.bsonsize() 와 동일한 결과를 얻을 수 있다는 사실을 알아냈다.

아래와 같은 방식으로 shard key의 size를 미리 체크해서 insert 하기 전에 사고를 방지할 수 있다.

// shard key가 이런 모양이고 key_X가 얼마나 늘어날지 모르는 상황에서 에러가 발생한다면
$shard_key = array(
    'key_A' => 'AA',
    'key_B' => 11,
    'key_C' => array(1, 2, 3),
    'key_X' => 'this string can exceed 512 bytes...'
);
 
 
// BSON size를 계산해보고 512를 넘으면 적절한 조치를 취해주자
if (strlen(bson_encode($shard_key)) > 512) {
    $shard_key['key_X'] = 'INVALID_STRING';
}



bson_encode() 함수는 deprecated된 php-mongo driver(legacy)에 포함된 함수임.

현역인 php-mongodb driver를 이용한다면 bson_encode() 대신, MongoDB\BSON\fromPHP() 함수를 이용하면 될 듯.


BSON spec을 보니, 맨 앞의 4바이트(int32)가 document의 길이를 나타내므로 bin2hex, hexdec 등을 이용하는 방법을 먼저 해봤는데, 그냥 strlen이 성능상 빠르고 편함.




참고

MongoDB shard key size 제한: https://docs.mongodb.com/manual/reference/limits/#Shard-Key-Size

php-mongo drvier(legacy): http://php.net/mongo

bson_encode(): http://php.net/manual/en/function.bson-encode.php

MongoDB\BSON\fromPHP(): http://php.net/manual/en/function.mongodb.bson-fromphp.php

BSON spec: http://bsonspec.org/spec.html





.






Posted by bloodguy
,