diff --git a/doctrine-mongo-mapping.xsd b/doctrine-mongo-mapping.xsd
index b5d4119b8c..76869f83fd 100644
--- a/doctrine-mongo-mapping.xsd
+++ b/doctrine-mongo-mapping.xsd
@@ -43,6 +43,7 @@
       <xs:element name="lifecycle-callbacks" type="odm:lifecycle-callbacks" minOccurs="0"/>
       <xs:element name="also-load-methods" type="odm:also-load-methods" minOccurs="0"/>
       <xs:element name="indexes" type="odm:indexes" minOccurs="0"/>
+      <xs:element name="shard-key" type="odm:shard-key" minOccurs="0"/>
     </xs:sequence>
     <xs:attribute name="db" type="xs:NMTOKEN" />
     <xs:attribute name="name" type="xs:string" />
@@ -60,7 +61,7 @@
   <xs:complexType name="field">
     <xs:sequence>
       <xs:element name="id-generator-option" type="odm:id-generator-option" minOccurs="0" maxOccurs="unbounded" />
-    </xs:sequence>  
+    </xs:sequence>
     <xs:attribute name="id" type="xs:boolean" default="false" />
     <xs:attribute name="name" type="xs:NMTOKEN" />
     <xs:attribute name="type" type="xs:NMTOKEN" default="string" />
@@ -82,7 +83,7 @@
     <xs:attribute name="sparse" type="xs:boolean" />
     <xs:attribute name="unique" type="xs:boolean" />
   </xs:complexType>
-  
+
   <xs:complexType name="id-generator-option">
     <xs:attribute name="name" type="xs:NMTOKEN" use="required"/>
     <xs:attribute name="value" type="xs:string" use="required"/>
@@ -275,6 +276,24 @@
     </xs:sequence>
   </xs:complexType>
 
+  <xs:complexType name="shard-key">
+    <xs:sequence>
+      <xs:element name="key" type="odm:shard-key-key" minOccurs="1" maxOccurs="unbounded"/>
+      <xs:element name="option" type="odm:shard-key-option" minOccurs="0" maxOccurs="unbounded"/>
+    </xs:sequence>
+    <xs:attribute name="unique" type="xs:boolean"/>
+    <xs:attribute name="numInitialChunks" type="xs:integer"/>
+  </xs:complexType>
+
+  <xs:complexType name="shard-key-key">
+    <xs:attribute name="name" type="xs:NMTOKEN" use="required"/>
+    <xs:attribute name="order" type="xs:NMTOKEN" default="asc" />
+  </xs:complexType>
+
+  <xs:complexType name="shard-key-option">
+    <xs:attribute name="name" type="xs:NMTOKEN" use="required"/>
+    <xs:attribute name="value" type="xs:NMTOKEN" use="required" />
+  </xs:complexType>
 
   <xs:complexType name="also-load-method">
     <xs:attribute name="method" type="xs:NMTOKEN" use="required"/>
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/DoctrineAnnotations.php b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/DoctrineAnnotations.php
index 8f2fe92f86..61d4a24945 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/DoctrineAnnotations.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/DoctrineAnnotations.php
@@ -80,3 +80,4 @@
 require_once __DIR__ . '/PostLoad.php';
 require_once __DIR__ . '/PreFlush.php';
 require_once __DIR__ . '/HasLifecycleCallbacks.php';
+require_once __DIR__ . '/ShardKey.php';
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/Document.php b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/Document.php
index cf536475cc..da5929010d 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/Document.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/Document.php
@@ -27,5 +27,6 @@ final class Document extends AbstractDocument
     public $repositoryClass;
     public $indexes = array();
     public $requireIndexes = false;
+    public $shardKey;
     public $slaveOkay;
 }
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/ShardKey.php b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/ShardKey.php
new file mode 100644
index 0000000000..f707c975b3
--- /dev/null
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/ShardKey.php
@@ -0,0 +1,30 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ODM\MongoDB\Mapping\Annotations;
+
+use Doctrine\Common\Annotations\Annotation;
+
+/** @Annotation */
+final class ShardKey extends Annotation
+{
+    public $keys = array();
+    public $unique;
+    public $numInitialChunks;
+}
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php
index a850328089..6aa9826da5 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php
@@ -20,7 +20,6 @@
 namespace Doctrine\ODM\MongoDB\Mapping;
 
 use Doctrine\Instantiator\Instantiator;
-use Doctrine\ODM\MongoDB\LockException;
 
 /**
  * A <tt>ClassMetadata</tt> instance holds all the object-document mapping metadata
@@ -115,6 +114,7 @@ public function __sleep()
             'generatorOptions',
             'idGenerator',
             'indexes',
+            'shardKey',
         );
 
         // The rest of the metadata is only serialized if necessary.
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php
index bd80ac4293..e3f570bdcd 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataFactory.php
@@ -138,6 +138,7 @@ protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonS
             $this->addInheritedFields($class, $parent);
             $this->addInheritedRelations($class, $parent);
             $this->addInheritedIndexes($class, $parent);
+            $this->setInheritedShardKey($class, $parent);
             $class->setIdentifier($parent->identifier);
             $class->setVersioned($parent->isVersioned);
             $class->setVersionField($parent->versionField);
@@ -336,4 +337,20 @@ private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $par
             $subClass->addIndex($index['keys'], $index['options']);
         }
     }
+
+    /**
+     * Adds inherited shard key to the subclass mapping.
+     *
+     * @param ClassMetadata $subClass
+     * @param ClassMetadata $parentClass
+     */
+    private function setInheritedShardKey(ClassMetadata $subClass, ClassMetadata $parentClass)
+    {
+        if ($parentClass->isSharded()) {
+            $subClass->setShardKey(
+                $parentClass->shardKey['keys'],
+                $parentClass->shardKey['options']
+            );
+        }
+    }
 }
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataInfo.php
index 7aad04e283..8ba727e31a 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataInfo.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataInfo.php
@@ -19,11 +19,10 @@
 
 namespace Doctrine\ODM\MongoDB\Mapping;
 
-use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
 use Doctrine\ODM\MongoDB\LockException;
-use Doctrine\ODM\MongoDB\Mapping\MappingException;
 use Doctrine\ODM\MongoDB\Proxy\Proxy;
 use Doctrine\ODM\MongoDB\Types\Type;
+use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
 use InvalidArgumentException;
 
 /**
@@ -194,6 +193,11 @@ class ClassMetadataInfo implements \Doctrine\Common\Persistence\Mapping\ClassMet
      */
     public $indexes = array();
 
+    /**
+     * READ-ONLY: Keys and options describing shard key. Only for sharded collections.
+     */
+    public $shardKey;
+
     /**
      * READ-ONLY: Whether or not queries on this document should require indexes.
      */
@@ -518,7 +522,7 @@ public function setCustomRepositoryClass($repositoryClassName)
         if ($this->isEmbeddedDocument) {
             return;
         }
-        
+
         if ($repositoryClassName && strpos($repositoryClassName, '\\') === false && strlen($this->namespace)) {
             $repositoryClassName = $this->namespace . '\\' . $repositoryClassName;
         }
@@ -798,6 +802,67 @@ public function hasIndexes()
         return $this->indexes ? true : false;
     }
 
+    /**
+     * Set shard key for this Document.
+     *
+     * @param array $keys Array of document keys.
+     * @param array $options Array of sharding options.
+     *
+     * @throws MappingException
+     */
+    public function setShardKey(array $keys, array $options = array())
+    {
+        if ($this->inheritanceType == self::INHERITANCE_TYPE_SINGLE_COLLECTION && !is_null($this->shardKey)) {
+            throw MappingException::shardKeyInSingleCollInheritanceSubclass($this->getName());
+        }
+
+        if ($this->isEmbeddedDocument) {
+            throw MappingException::embeddedDocumentCantHaveShardKey($this->getName());
+        }
+
+        foreach ($keys as $field) {
+            if ($this->getTypeOfField($field) == 'increment') {
+                throw MappingException::noIncrementFieldsAllowedInShardKey($this->getName());
+            }
+        }
+
+        $this->shardKey = array(
+            'keys' => array_map(function($value) {
+                if ($value == 1 || $value == -1) {
+                    return (int) $value;
+                }
+                if (is_string($value)) {
+                    $lower = strtolower($value);
+                    if ($lower === 'asc') {
+                        return 1;
+                    } elseif ($lower === 'desc') {
+                        return -1;
+                    }
+                }
+                return $value;
+            }, $keys),
+            'options' => $options
+        );
+    }
+
+    /**
+     * @return array
+     */
+    public function getShardKey()
+    {
+        return $this->shardKey;
+    }
+
+    /**
+     * Checks whether this document has shard key or not.
+     *
+     * @return bool
+     */
+    public function isSharded()
+    {
+        return $this->shardKey ? true : false;
+    }
+
     /**
      * Sets the change tracking policy used by this class.
      *
@@ -1112,7 +1177,7 @@ public function mapField(array $mapping)
         $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
         $mapping['isCascadeMerge'] = in_array('merge', $cascades);
         $mapping['isCascadeDetach'] = in_array('detach', $cascades);
-        
+
         if (isset($mapping['type']) && $mapping['type'] === 'file') {
             $mapping['file'] = true;
         }
@@ -1160,7 +1225,7 @@ public function mapField(array $mapping)
             && ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {
             throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);
         }
-        
+
         if ($this->isEmbeddedDocument && $mapping['type'] === 'many' && CollectionHelper::isAtomic($mapping['strategy'])) {
             throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);
         }
@@ -1514,7 +1579,7 @@ public function setFieldValue($document, $field, $value)
             //so the proxy needs to be loaded first.
             $document->__load();
         }
-        
+
         $this->reflFields[$field]->setValue($document, $value);
     }
 
@@ -1531,7 +1596,7 @@ public function getFieldValue($document, $field)
         if ($document instanceof Proxy && $field !== $this->identifier && ! $document->__isInitialized()) {
             $document->__load();
         }
-        
+
         return $this->reflFields[$field]->getValue($document);
     }
 
@@ -1543,8 +1608,6 @@ public function getFieldValue($document, $field)
      * @return array  The field mapping.
      *
      * @throws MappingException if the $fieldName is not found in the fieldMappings array
-     *
-     * @throws MappingException
      */
     public function getFieldMapping($fieldName)
     {
@@ -1554,6 +1617,26 @@ public function getFieldMapping($fieldName)
         return $this->fieldMappings[$fieldName];
     }
 
+    /**
+     * Gets the field mapping by its DB name.
+     * E.g. it returns identifier's mapping when called with _id.
+     *
+     * @param string $dbFieldName
+     *
+     * @return array
+     * @throws MappingException
+     */
+    public function getFieldMappingByDbFieldName($dbFieldName)
+    {
+        foreach ($this->fieldMappings as $mapping) {
+            if ($mapping['name'] == $dbFieldName) {
+                return $mapping;
+            }
+        }
+
+        throw MappingException::mappingNotFoundByDbName($this->name, $dbFieldName);
+    }
+
     /**
      * Check if the field is not null.
      *
@@ -1701,7 +1784,7 @@ public function isIdGeneratorNone()
      * value to use depending on the column type.
      *
      * @param array $mapping   The version field mapping array
-     * 
+     *
      * @throws LockException
      */
     public function setVersionMapping(array &$mapping)
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php
index e946c7bdbe..5f90fa8391 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/AnnotationDriver.php
@@ -185,6 +185,11 @@ public function loadMetadataForClass($className, ClassMetadata $class)
             }
         }
 
+        // Set shard key after all fields to ensure we mapped all its keys
+        if (isset($classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey'])) {
+            $this->setShardKey($class, $classAnnotations['Doctrine\ODM\MongoDB\Mapping\Annotations\ShardKey']);
+        }
+
         /** @var $method \ReflectionMethod */
         foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
             /* Filter for the declaring class only. Callbacks from parent
@@ -240,6 +245,25 @@ private function addIndex(ClassMetadataInfo $class, $index, array $keys = array(
         $class->addIndex($keys, $options);
     }
 
+    /**
+     * @param ClassMetadataInfo $class
+     * @param ODM\ShardKey      $shardKey
+     *
+     * @throws MappingException
+     */
+    private function setShardKey(ClassMetadataInfo $class, ODM\ShardKey $shardKey)
+    {
+        $options = array();
+        $allowed = array('unique', 'numInitialChunks');
+        foreach ($allowed as $name) {
+            if (isset($shardKey->$name)) {
+                $options[$name] = $shardKey->$name;
+            }
+        }
+
+        $class->setShardKey($shardKey->keys, $options);
+    }
+
     /**
      * Factory method for the Annotation Driver
      *
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php
index 45b78765c5..02ec95f40a 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php
@@ -116,6 +116,9 @@ public function loadMetadataForClass($className, ClassMetadata $class)
                 $this->addIndex($class, $index);
             }
         }
+        if (isset($xmlRoot->{'shard-key'})) {
+            $this->setShardKey($class, $xmlRoot->{'shard-key'}[0]);
+        }
         if (isset($xmlRoot['require-indexes'])) {
             $class->setRequireIndexes('true' === (string) $xmlRoot['require-indexes']);
         }
@@ -381,6 +384,41 @@ private function addIndex(ClassMetadataInfo $class, \SimpleXmlElement $xmlIndex)
         $class->addIndex($keys, $options);
     }
 
+    private function setShardKey(ClassMetadataInfo $class, \SimpleXmlElement $xmlShardkey)
+    {
+        $attributes = $xmlShardkey->attributes();
+
+        $keys = array();
+        $options = array();
+        foreach ($xmlShardkey->{'key'} as $key) {
+            $keys[(string) $key['name']] = isset($key['order']) ? (string)$key['order'] : 'asc';
+        }
+
+        if (isset($attributes['unique'])) {
+            $options['unique'] = ('true' === (string) $attributes['unique']);
+        }
+
+        if (isset($attributes['numInitialChunks'])) {
+            $options['numInitialChunks'] = (int) $attributes['numInitialChunks'];
+        }
+
+        if (isset($xmlShardkey->{'option'})) {
+            foreach ($xmlShardkey->{'option'} as $option) {
+                $value = (string) $option['value'];
+                if ($value === 'true') {
+                    $value = true;
+                } elseif ($value === 'false') {
+                    $value = false;
+                } elseif (is_numeric($value)) {
+                    $value = preg_match('/^[-]?\d+$/', $value) ? (integer) $value : (float) $value;
+                }
+                $options[(string) $option['name']] = $value;
+            }
+        }
+
+        $class->setShardKey($keys, $options);
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/YamlDriver.php
index e9bd476e3b..a4fb2a0721 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/Driver/YamlDriver.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/Driver/YamlDriver.php
@@ -79,6 +79,9 @@ public function loadMetadataForClass($className, ClassMetadata $class)
                 $class->addIndex($index['keys'], isset($index['options']) ? $index['options'] : array());
             }
         }
+        if (isset($element['shardKey'])) {
+            $this->setShardKey($class, $element['shardKey']);
+        }
         if (isset($element['inheritanceType'])) {
             $class->setInheritanceType(constant('Doctrine\ODM\MongoDB\Mapping\ClassMetadata::INHERITANCE_TYPE_' . strtoupper($element['inheritanceType'])));
         }
@@ -335,4 +338,22 @@ protected function loadMappingFile($file)
     {
         return Yaml::parse(file_get_contents($file));
     }
+
+    private function setShardKey(ClassMetadataInfo $class, array $shardKey)
+    {
+        $keys = $shardKey['keys'];
+        $options = array();
+
+        if (isset($shardKey['options'])) {
+            $allowed = array('unique', 'numInitialChunks');
+            foreach ($shardKey['options'] as $name => $value) {
+                if ( ! in_array($name, $allowed, true)) {
+                    continue;
+                }
+                $options[$name] = $value;
+            }
+        }
+
+        $class->setShardKey($keys, $options);
+    }
 }
diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php b/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php
index f847acb72c..96d37c6fcc 100644
--- a/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php
+++ b/lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php
@@ -57,6 +57,16 @@ public static function mappingNotFound($className, $fieldName)
         return new self("No mapping found for field '$fieldName' in class '$className'.");
     }
 
+    /**
+     * @param string $className
+     * @param string $dbFieldName
+     * @return MappingException
+     */
+    public static function mappingNotFoundByDbName($className, $dbFieldName)
+    {
+        return new self("No mapping found for field by DB name '$dbFieldName' in class '$className'.");
+    }
+
     /**
      * @param string $document
      * @param string $fieldName
@@ -255,4 +265,31 @@ public static function referenceManySortMustNotBeUsedWithNonSetCollectionStrateg
     {
         return new self("ReferenceMany's sort can not be used with addToSet and pushAll strategies, $strategy used in $className::$fieldName");
     }
+
+    /**
+     * @param $subclassName
+     * @return MappingException
+     */
+    public static function shardKeyInSingleCollInheritanceSubclass($subclassName)
+    {
+        return new self("Shard key overriding in subclass is forbidden for single collection inheritance: $subclassName");
+    }
+
+    /**
+     * @param $className
+     * @return MappingException
+     */
+    public static function embeddedDocumentCantHaveShardKey($className)
+    {
+        return new self("Embedded document can't have shard key: $className");
+    }
+
+    /**
+     * @param string $className
+     * @return MappingException
+     */
+    public static function noIncrementFieldsAllowedInShardKey($className)
+    {
+        return new self("No increment fields allowed in the shard key: $className");
+    }
 }
diff --git a/lib/Doctrine/ODM/MongoDB/MongoDBException.php b/lib/Doctrine/ODM/MongoDB/MongoDBException.php
index b1ffc8cd46..e8ca840ecb 100644
--- a/lib/Doctrine/ODM/MongoDB/MongoDBException.php
+++ b/lib/Doctrine/ODM/MongoDB/MongoDBException.php
@@ -144,4 +144,50 @@ public static function invalidValueForType($type, $expected, $got)
         }
         return new self(sprintf('%s type requires value of type %s, %s given', $type, $expected, $gotType));
     }
+
+    /**
+     * @param string $field
+     * @param string $className
+     * @return MongoDBException
+     */
+    public static function shardKeyFieldCannotBeChanged($field, $className)
+    {
+        return new self(sprintf('Shard key field "%s" cannot be changed. Class: %s', $field, $className));
+    }
+
+    /**
+     * @param string $field
+     * @param string $className
+     * @return MongoDBException
+     */
+    public static function shardKeyFieldMissing($field, $className)
+    {
+        return new self(sprintf('Shard key field "%s" is missing. Class: %s.', $field, $className));
+    }
+
+    /**
+     * @param string $dbName
+     * @param string $errorMessage
+     * @return MongoDBException
+     */
+    public static function failedToEnableSharding($dbName, $errorMessage)
+    {
+        return new self(sprintf('Failed to enable sharding for database "%s". Error from MongoDB: %s',
+            $dbName,
+            $errorMessage
+        ));
+    }
+
+    /**
+     * @param string $className
+     * @param string $errorMessage
+     * @return MongoDBException
+     */
+    public static function failedToEnsureDocumentSharding($className, $errorMessage)
+    {
+        return new self(sprintf('Failed to ensure sharding for document "%s". Error from MongoDB: %s',
+            $className,
+            $errorMessage
+        ));
+    }
 }
diff --git a/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php b/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php
index d5aefc65aa..3ebf81be96 100644
--- a/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php
+++ b/lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php
@@ -23,17 +23,18 @@
 use Doctrine\MongoDB\CursorInterface;
 use Doctrine\ODM\MongoDB\Cursor;
 use Doctrine\ODM\MongoDB\DocumentManager;
-use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
 use Doctrine\ODM\MongoDB\Hydrator\HydratorFactory;
 use Doctrine\ODM\MongoDB\LockException;
 use Doctrine\ODM\MongoDB\LockMode;
 use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
+use Doctrine\ODM\MongoDB\MongoDBException;
 use Doctrine\ODM\MongoDB\PersistentCollection;
 use Doctrine\ODM\MongoDB\Proxy\Proxy;
 use Doctrine\ODM\MongoDB\Query\CriteriaMerger;
 use Doctrine\ODM\MongoDB\Query\Query;
 use Doctrine\ODM\MongoDB\Types\Type;
 use Doctrine\ODM\MongoDB\UnitOfWork;
+use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
 
 /**
  * The DocumentPersister is responsible for persisting documents.
@@ -276,24 +277,8 @@ public function executeUpserts(array $options = array())
         }
 
         foreach ($this->queuedUpserts as $oid => $document) {
-            $data = $this->pb->prepareUpsertData($document);
-
-            // Set the initial version for each upsert
-            if ($this->class->isVersioned) {
-                $versionMapping = $this->class->fieldMappings[$this->class->versionField];
-                if ($versionMapping['type'] === 'int') {
-                    $nextVersion = max(1, (int) $this->class->reflFields[$this->class->versionField]->getValue($document));
-                    $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersion);
-                } elseif ($versionMapping['type'] === 'date') {
-                    $nextVersionDateTime = new \DateTime();
-                    $nextVersion = new \MongoDate($nextVersionDateTime->getTimestamp());
-                    $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersionDateTime);
-                }
-                $data['$set'][$versionMapping['name']] = $nextVersion;
-            }
-            
             try {
-                $this->executeUpsert($data, $options);
+                $this->executeUpsert($document, $options);
                 $this->handleCollections($document, $options);
                 unset($this->queuedUpserts[$oid]);
             } catch (\MongoException $e) {
@@ -304,23 +289,42 @@ public function executeUpserts(array $options = array())
     }
 
     /**
-     * Executes a single upsert in {@link executeInserts}
+     * Executes a single upsert in {@link executeUpserts}
      *
-     * @param array $data
-     * @param array $options
+     * @param object $document
+     * @param array  $options
      */
-    private function executeUpsert(array $data, array $options)
+    private function executeUpsert($document, array $options)
     {
         $options['upsert'] = true;
-        $criteria = array('_id' => $data['$set']['_id']);
-        unset($data['$set']['_id']);
+        $criteria = $this->getQueryForDocument($document);
+
+        $data = $this->pb->prepareUpsertData($document);
+
+        // Set the initial version for each upsert
+        if ($this->class->isVersioned) {
+            $versionMapping = $this->class->fieldMappings[$this->class->versionField];
+            if ($versionMapping['type'] === 'int') {
+                $nextVersion = max(1, (int) $this->class->reflFields[$this->class->versionField]->getValue($document));
+                $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersion);
+            } elseif ($versionMapping['type'] === 'date') {
+                $nextVersionDateTime = new \DateTime();
+                $nextVersion = new \MongoDate($nextVersionDateTime->getTimestamp());
+                $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersionDateTime);
+            }
+            $data['$set'][$versionMapping['name']] = $nextVersion;
+        }
+
+        foreach (array_keys($criteria) as $field) {
+            unset($data['$set'][$field]);
+        }
 
         // Do not send an empty $set modifier
         if (empty($data['$set'])) {
             unset($data['$set']);
         }
 
-        /* If there are no modifiers remaining, we're upserting a document with 
+        /* If there are no modifiers remaining, we're upserting a document with
          * an identifier as its only field. Since a document with the identifier
          * may already exist, the desired behavior is "insert if not exists" and
          * NOOP otherwise. MongoDB 2.6+ does not allow empty modifiers, so $set
@@ -359,11 +363,18 @@ private function executeUpsert(array $data, array $options)
      */
     public function update($document, array $options = array())
     {
-        $id = $this->uow->getDocumentIdentifier($document);
         $update = $this->pb->prepareUpdateData($document);
 
-        $id = $this->class->getDatabaseIdentifierValue($id);
-        $query = array('_id' => $id);
+        $query = $this->getQueryForDocument($document);
+
+        foreach (array_keys($query) as $field) {
+            unset($update['$set'][$field]);
+        }
+
+        if (empty($update['$set'])) {
+            unset($update['$set']);
+        }
+
 
         // Include versioning logic to set the new version value in the database
         // and to ensure the version has not changed since this document object instance
@@ -416,8 +427,7 @@ public function update($document, array $options = array())
      */
     public function delete($document, array $options = array())
     {
-        $id = $this->uow->getDocumentIdentifier($document);
-        $query = array('_id' => $this->class->getDatabaseIdentifierValue($id));
+        $query = $this->getQueryForDocument($document);
 
         if ($this->class->isLockable) {
             $query[$this->class->lockField] = array('$exists' => false);
@@ -433,13 +443,15 @@ public function delete($document, array $options = array())
     /**
      * Refreshes a managed document.
      *
-     * @param array $id The identifier of the document.
+     * @param string $id
      * @param object $document The document to refresh.
+     *
+     * @deprecated The first argument is deprecated.
      */
     public function refresh($id, $document)
     {
-        $class = $this->dm->getClassMetadata(get_class($document));
-        $data = $this->collection->findOne(array('_id' => $id));
+        $query = $this->getQueryForDocument($document);
+        $data = $this->collection->findOne($query);
         $data = $this->hydratorFactory->hydrate($document, $data);
         $this->uow->setOriginalDocumentData($document, $data);
     }
@@ -521,6 +533,33 @@ public function loadAll(array $criteria = array(), array $sort = null, $limit =
         return $cursor;
     }
 
+    /**
+     * @param object $document
+     *
+     * @return array
+     * @throws MongoDBException
+     */
+    public function getShardKeyQuery($document)
+    {
+        if ( ! $this->class->isSharded()) {
+            return array();
+        }
+
+        $shardKey = $this->class->getShardKey();
+        $keys = array_keys($shardKey['keys']);
+        $data = $this->uow->getDocumentActualData($document);
+
+        $shardKeyQueryPart = array();
+        foreach ($keys as $key) {
+            $mapping = $this->class->getFieldMappingByDbFieldName($key);
+            $this->guardMissingShardKey($document, $key, $data);
+            $value = Type::getType($mapping['type'])->convertToDatabaseValue($data[$mapping['fieldName']]);
+            $shardKeyQueryPart[$key] = $value;
+        }
+
+        return $shardKeyQueryPart;
+    }
+
     /**
      * Wraps the supplied base cursor in the corresponding ODM class.
      *
@@ -778,7 +817,7 @@ public function createReferenceManyInverseSideQuery(PersistentCollection $collec
     private function loadReferenceManyWithRepositoryMethod(PersistentCollection $collection)
     {
         $cursor = $this->createReferenceManyWithRepositoryMethodCursor($collection);
-        $mapping = $collection->getMapping();        
+        $mapping = $collection->getMapping();
         $documents = $cursor->toArray(false);
         foreach ($documents as $key => $obj) {
             if (CollectionHelper::isHash($mapping['strategy'])) {
@@ -1257,4 +1296,49 @@ private function handleCollections($document, $options)
             $coll->takeSnapshot();
         }
     }
+
+    /**
+     * If the document is new, ignore shard key field value, otherwise throw an exception.
+     * Also, shard key field should be presented in actual document data.
+     *
+     * @param object $document
+     * @param string $shardKeyField
+     * @param array  $actualDocumentData
+     *
+     * @throws MongoDBException
+     */
+    private function guardMissingShardKey($document, $shardKeyField, $actualDocumentData)
+    {
+        $dcs = $this->uow->getDocumentChangeSet($document);
+        $isUpdate = $this->uow->isScheduledForUpdate($document);
+
+        $fieldMapping = $this->class->getFieldMappingByDbFieldName($shardKeyField);
+        $fieldName = $fieldMapping['fieldName'];
+
+        if ($isUpdate && isset($dcs[$fieldName]) && $dcs[$fieldName][0] != $dcs[$fieldName][1]) {
+            throw MongoDBException::shardKeyFieldCannotBeChanged($shardKeyField, $this->class->getName());
+        }
+
+        if (!isset($actualDocumentData[$fieldName])) {
+            throw MongoDBException::shardKeyFieldMissing($shardKeyField, $this->class->getName());
+        }
+    }
+
+    /**
+     * Get shard key aware query for single document.
+     *
+     * @param object $document
+     *
+     * @return array
+     */
+    private function getQueryForDocument($document)
+    {
+        $id = $this->uow->getDocumentIdentifier($document);
+        $id = $this->class->getDatabaseIdentifierValue($id);
+
+        $shardKeyQueryPart = $this->getShardKeyQuery($document);
+        $query = array_merge(array('_id' => $id), $shardKeyQueryPart);
+
+        return $query;
+    }
 }
diff --git a/lib/Doctrine/ODM/MongoDB/SchemaManager.php b/lib/Doctrine/ODM/MongoDB/SchemaManager.php
index 0685391bf3..6cda2fe4d4 100644
--- a/lib/Doctrine/ODM/MongoDB/SchemaManager.php
+++ b/lib/Doctrine/ODM/MongoDB/SchemaManager.php
@@ -487,4 +487,98 @@ public function isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentInde
 
         return true;
     }
+
+    /**
+     * Ensure collections are sharded for all documents that can be loaded with the
+     * metadata factory.
+     *
+     * @param array $indexOptions Options for `ensureIndex` command. It's performed on an existing collections
+     *
+     * @throws MongoDBException
+     */
+    public function ensureSharding(array $indexOptions = array())
+    {
+        foreach ($this->metadataFactory->getAllMetadata() as $class) {
+            if ($class->isMappedSuperclass || !$class->isSharded()) {
+                continue;
+            }
+
+            $this->ensureDocumentSharding($class->name, $indexOptions);
+        }
+    }
+
+    /**
+     * Ensure sharding for collection by document name.
+     *
+     * @param string $documentName
+     * @param array  $indexOptions Options for `ensureIndex` command. It's performed on an existing collections.
+     *
+     * @throws MongoDBException
+     */
+    public function ensureDocumentSharding($documentName, array $indexOptions = array())
+    {
+        $class = $this->dm->getClassMetadata($documentName);
+        if ( ! $class->isSharded()) {
+            return;
+        }
+
+        $this->enableShardingForDbByDocumentName($documentName);
+
+        do {
+            $result = $this->runShardCollectionCommand($documentName);
+            $done = true;
+            $try = 0;
+
+            if ($result['ok'] != 1 && isset($result['proposedKey'])) {
+                $this->dm->getDocumentCollection($documentName)->ensureIndex($result['proposedKey'], $indexOptions);
+                $done = false;
+                $try++;
+            }
+        } while (!$done && $try < 2);
+
+        if ($result['ok'] != 1 && $result['errmsg'] !== 'already sharded') {
+            throw MongoDBException::failedToEnsureDocumentSharding($documentName, $result['errmsg']);
+        }
+    }
+
+    /**
+     * Enable sharding for database which contains documents with given name.
+     *
+     * @param string $documentName
+     *
+     * @throws MongoDBException
+     */
+    public function enableShardingForDbByDocumentName($documentName)
+    {
+        $dbName = $this->dm->getDocumentDatabase($documentName)->getName();
+        $adminDb = $this->dm->getConnection()->selectDatabase('admin');
+        $result = $adminDb->command(array('enableSharding' => $dbName));
+
+        if ($result['ok'] != 1 && $result['errmsg'] !== 'already enabled') {
+            throw MongoDBException::failedToEnableSharding($dbName, $result['errmsg']);
+        }
+    }
+
+    /**
+     * @param $documentName
+     *
+     * @return array
+     */
+    private function runShardCollectionCommand($documentName)
+    {
+        $class = $this->dm->getClassMetadata($documentName);
+        $dbName = $this->dm->getDocumentDatabase($documentName)->getName();
+        $shardKey = $class->getShardKey();
+        $adminDb = $this->dm->getConnection()->selectDatabase('admin');
+
+        $result = $adminDb->command(
+            array(
+                'shardCollection' => $dbName . '.' . $class->getCollection(),
+                'key'             => $shardKey['keys']
+            ),
+            $shardKey['options']
+        );
+
+        return $result;
+    }
 }
diff --git a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php
index 40dc282f81..7d9502149d 100644
--- a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php
+++ b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php
@@ -175,10 +175,10 @@ class UnitOfWork implements PropertyChangedListener
      * @var array
      */
     private $collectionUpdates = array();
-    
+
     /**
      * A list of documents related to collections scheduled for update or deletion
-     * 
+     *
      * @var array
      */
     private $hasScheduledCollections = array();
@@ -446,7 +446,7 @@ public function commit($document = null, array $options = array())
         $this->collectionDeletions =
         $this->visitedCollections =
         $this->scheduledForDirtyCheck =
-        $this->orphanRemovals = 
+        $this->orphanRemovals =
         $this->hasScheduledCollections = array();
     }
 
@@ -2448,7 +2448,7 @@ public function clear($documentName = null)
             $this->collectionUpdates =
             $this->collectionDeletions =
             $this->parentAssociations =
-            $this->orphanRemovals = 
+            $this->orphanRemovals =
             $this->hasScheduledCollections = array();
         } else {
             $visited = array();
@@ -2523,11 +2523,11 @@ public function isCollectionScheduledForDeletion(PersistentCollection $coll)
     {
         return isset($this->collectionDeletions[spl_object_hash($coll)]);
     }
-    
+
     /**
      * INTERNAL:
      * Unschedules a collection from being deleted when this UnitOfWork commits.
-     * 
+     *
      * @param \Doctrine\ODM\MongoDB\PersistentCollection $coll
      */
     public function unscheduleCollectionDeletion(PersistentCollection $coll)
@@ -2561,11 +2561,11 @@ public function scheduleCollectionUpdate(PersistentCollection $coll)
             $this->scheduleCollectionOwner($coll);
         }
     }
-    
+
     /**
      * INTERNAL:
      * Unschedules a collection from being updated when this UnitOfWork commits.
-     * 
+     *
      * @param \Doctrine\ODM\MongoDB\PersistentCollection $coll
      */
     public function unscheduleCollectionUpdate(PersistentCollection $coll)
@@ -2577,7 +2577,7 @@ public function unscheduleCollectionUpdate(PersistentCollection $coll)
             unset($this->hasScheduledCollections[spl_object_hash($topmostOwner)][$oid]);
         }
     }
-    
+
     /**
      * Checks whether a PersistentCollection is scheduled for update.
      *
@@ -2604,22 +2604,22 @@ public function getVisitedCollections($document)
                 ? $this->visitedCollections[$oid]
                 : array();
     }
-    
+
     /**
      * INTERNAL:
      * Gets PersistentCollections that are scheduled to update and related to $document
-     * 
+     *
      * @param object $document
      * @return array
      */
     public function getScheduledCollections($document)
     {
         $oid = spl_object_hash($document);
-        return isset($this->hasScheduledCollections[$oid]) 
+        return isset($this->hasScheduledCollections[$oid])
                 ? $this->hasScheduledCollections[$oid]
                 : array();
     }
-    
+
     /**
      * Checks whether the document is related to a PersistentCollection
      * scheduled for update or deletion.
@@ -2631,7 +2631,7 @@ public function hasScheduledCollections($document)
     {
         return isset($this->hasScheduledCollections[spl_object_hash($document)]);
     }
-    
+
     /**
      * Marks the PersistentCollection's top-level owner as having a relation to
      * a collection scheduled for update or deletion.
@@ -2642,7 +2642,7 @@ public function hasScheduledCollections($document)
      * If the collection is nested within atomic collection, it is immediately
      * unscheduled and atomic one is scheduled for update instead. This makes
      * calculating update data way easier.
-     * 
+     *
      * @param PersistentCollection $coll
      */
     private function scheduleCollectionOwner(PersistentCollection $coll)
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 06aca8fe1d..52ea03e0aa 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -25,6 +25,7 @@
     <groups>
         <exclude>
             <group>performance</group>
+            <group>sharding</group>
         </exclude>
     </groups>
 
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/BaseTest.php b/tests/Doctrine/ODM/MongoDB/Tests/BaseTest.php
index bb2b5a449f..7951cd5501 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/BaseTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/BaseTest.php
@@ -6,17 +6,13 @@
 use Doctrine\ODM\MongoDB\DocumentManager;
 use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver;
 use Doctrine\MongoDB\Connection;
+use Doctrine\ODM\MongoDB\UnitOfWork;
 
 abstract class BaseTest extends \PHPUnit_Framework_TestCase
 {
-    /**
-     * @var \Doctrine\ODM\MongoDB\DocumentManager
-     */
+    /** @var DocumentManager */
     protected $dm;
-    
-    /**
-     * @var \Doctrine\ODM\MongoDB\UnitOfWork
-     */
+    /** @var UnitOfWork */
     protected $uow;
 
     public function setUp()
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/EnsureShardingTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/EnsureShardingTest.php
new file mode 100644
index 0000000000..87c5d97689
--- /dev/null
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/EnsureShardingTest.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Doctrine\ODM\MongoDB\Tests\Functional;
+
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+use Doctrine\ODM\MongoDB\Tests\BaseTest;
+
+class EnsureShardingTest extends BaseTest
+{
+    /**
+     * @group sharding
+     */
+    public function testEnsureShardingForNewCollection()
+    {
+        $class = 'Documents\Sharded\ShardedOne';
+        $this->dm->getSchemaManager()->ensureDocumentSharding($class);
+
+        $collection = $this->dm->getDocumentCollection($class);
+        $indexes = $collection->getIndexInfo();
+        $stats = $this->dm->getDocumentDatabase($class)->command(array('collstats' => $collection->getName()));
+
+        $this->assertCount(2, $indexes);
+        $this->assertSame(array('k' => 1), $indexes[1]['key']);
+        $this->assertTrue($stats['sharded']);
+    }
+
+    /**
+     * @group sharding
+     */
+    public function testEnsureShardingForCollectionWithDocuments()
+    {
+        $class = 'Documents\Sharded\ShardedOne';
+        $collection = $this->dm->getDocumentCollection($class);
+        $doc = array('title' => 'hey', 'k' => 'hi');
+        $collection->insert($doc);
+
+        $this->dm->getSchemaManager()->ensureDocumentSharding($class);
+
+        $indexes = $collection->getIndexInfo();
+        $stats = $this->dm->getDocumentDatabase($class)->command(array('collstats' => $collection->getName()));
+
+        $this->assertCount(2, $indexes);
+        $this->assertSame(array('k' => 1), $indexes[1]['key']);
+        $this->assertTrue($stats['sharded']);
+    }
+}
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php
index 5dc2457580..64db477b85 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReadPreferenceTest.php
@@ -41,6 +41,7 @@ public function testHintIsNotSetByDefault()
     }
 
     /**
+     * @group replication_lag
      * @dataProvider provideReadPreferenceHints
      */
     public function testHintIsSetOnQuery($readPreference, array $tags = null)
@@ -62,6 +63,7 @@ public function testHintIsSetOnQuery($readPreference, array $tags = null)
     }
 
     /**
+     * @group replication_lag
      * @dataProvider provideReadPreferenceHints
      */
     public function testHintIsSetOnCursor($readPreference, array $tags = null)
@@ -87,6 +89,7 @@ public function testHintIsSetOnCursor($readPreference, array $tags = null)
     }
 
     /**
+     * @group replication_lag
      * @dataProvider provideReadPreferenceHints
      */
     public function testHintIsSetOnPersistentCollection($readPreference, array $tags = null)
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php
index 8f1f706b20..606772c687 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php
@@ -246,6 +246,9 @@ public function testPrimeReferencesIgnoresInitializedProxyObjects()
         $this->assertEquals(0, $invoked, 'Primer was not invoked when all references were already managed.');
     }
 
+    /**
+     * @group replication_lag
+     */
     public function testPrimeReferencesInvokesPrimer()
     {
         $group1 = new Group();
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ShardKeyTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ShardKeyTest.php
new file mode 100644
index 0000000000..1d4672ee43
--- /dev/null
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ShardKeyTest.php
@@ -0,0 +1,145 @@
+<?php
+
+namespace Doctrine\ODM\MongoDB\Tests\Functional;
+
+use Doctrine\ODM\MongoDB\DocumentManager;
+use Doctrine\ODM\MongoDB\Tests\BaseTest;
+use Documents\Sharded\ShardedOne;
+
+class ShardKeyTest extends BaseTest
+{
+    public function setUp()
+    {
+        parent::setUp();
+
+        $schemaManager = $this->dm->getSchemaManager();
+        $schemaManager->ensureDocumentSharding('Documents\Sharded\ShardedOne');
+    }
+
+    /**
+     * @group sharding
+     */
+    public function testUpdateAfterSave()
+    {
+        $queries = array();
+        $this->logQueries($queries);
+
+        $o = new ShardedOne();
+        $this->dm->persist($o);
+        $this->dm->flush();
+
+        /** @var \Documents\Sharded\ShardedOne $o */
+        $o = $this->dm->find(get_class($o), $o->id);
+        $o->title = 'test2';
+        $this->dm->flush();
+
+        $lastQuery = end($queries);
+        $this->assertTrue($lastQuery['update']);
+        $this->assertContains('k', array_keys($lastQuery['query']));
+        $this->assertEquals($o->key, $lastQuery['query']['k']);
+    }
+
+    /**
+     * @group sharding
+     */
+    public function testUpsert()
+    {
+        $queries = array();
+        $this->logQueries($queries);
+
+        $o = new ShardedOne();
+        $o->id = new \MongoId();
+        $this->dm->persist($o);
+        $this->dm->flush();
+
+        $lastQuery = end($queries);
+        $this->assertTrue($lastQuery['update']);
+        $this->assertContains('k', array_keys($lastQuery['query']));
+        $this->assertEquals($o->key, $lastQuery['query']['k']);
+    }
+
+    /**
+     * @group sharding
+     */
+    public function testRemove()
+    {
+        $queries = array();
+        $this->logQueries($queries);
+
+        $o = new ShardedOne();
+        $this->dm->persist($o);
+        $this->dm->flush();
+        $this->dm->remove($o);
+        $this->dm->flush();
+
+        $lastQuery = end($queries);
+        $this->assertTrue($lastQuery['remove']);
+        $this->assertContains('k', array_keys($lastQuery['query']));
+        $this->assertEquals($o->key, $lastQuery['query']['k']);
+    }
+
+    /**
+     * @group sharding
+     */
+    public function testRefresh()
+    {
+        $queries = array();
+        $this->logQueries($queries);
+
+        $o = new ShardedOne();
+        $this->dm->persist($o);
+        $this->dm->flush();
+        $this->dm->refresh($o);
+
+        $lastQuery = end($queries);
+        $this->assertTrue($lastQuery['findOne']);
+        $this->assertContains('k', array_keys($lastQuery['query']));
+        $this->assertEquals($o->key, $lastQuery['query']['k']);
+    }
+
+    /**
+     * @group sharding
+     * @expectedException \Doctrine\ODM\MongoDB\MongoDBException
+     */
+    public function testUpdateWithShardKeyChangeException()
+    {
+        $o = new ShardedOne();
+        $this->dm->persist($o);
+        $this->dm->flush();
+
+        $o->key = 'testing2';
+        $this->dm->flush();
+    }
+
+    /**
+     * @group sharding
+     * @expectedException \Doctrine\ODM\MongoDB\MongoDBException
+     */
+    public function testUpdateWithUpsertTrue()
+    {
+        $o = new ShardedOne();
+        $this->dm->persist($o);
+        $this->dm->flush();
+
+        $o->key = 'testing2';
+        $this->dm->flush(null, array('upsert' => true));
+    }
+
+    /**
+     * Replace DM with the one with enabled query logging
+     *
+     * @param $queries
+     */
+    private function logQueries(&$queries)
+    {
+        $this->dm->getConnection()->getConfiguration()->setLoggerCallable(
+            function (array $log) use (&$queries) {
+                $queries[] = $log;
+            }
+        );
+        $this->dm = DocumentManager::create(
+            $this->dm->getConnection(),
+            $this->dm->getConfiguration()
+        );
+    }
+}
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/SlaveOkayTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/SlaveOkayTest.php
index 48402fbad4..2d1dc62e15 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/SlaveOkayTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/SlaveOkayTest.php
@@ -20,6 +20,9 @@ public function setUp()
         $this->dm->clear();
     }
 
+    /**
+     * @group replication_lag
+     */
     public function testHintIsNotSetByDefault()
     {
         $cursor = $this->dm->getRepository('Documents\User')
@@ -36,6 +39,7 @@ public function testHintIsNotSetByDefault()
     }
 
     /**
+     * @group replication_lag
      * @dataProvider provideSlaveOkayHints
      */
     public function testHintIsSetOnQuery($slaveOkay)
@@ -55,6 +59,7 @@ public function testHintIsSetOnQuery($slaveOkay)
     }
 
     /**
+     * @group replication_lag
      * @dataProvider provideSlaveOkayHints
      */
     public function testHintIsSetOnCursor($slaveOkay)
@@ -75,6 +80,7 @@ public function testHintIsSetOnCursor($slaveOkay)
     }
 
     /**
+     * @group replication_lag
      * @dataProvider provideSlaveOkayHints
      */
     public function testHintIsSetOnPersistentCollection($slaveOkay)
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractMappingDriverTest.php
index 21731399dc..a1e3bdd53f 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractMappingDriverTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AbstractMappingDriverTest.php
@@ -3,8 +3,6 @@
 namespace Doctrine\ODM\MongoDB\Tests\Mapping;
 
 use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
-use Doctrine\ODM\MongoDB\Mapping\Driver\XmlDriver;
-use Doctrine\ODM\MongoDB\Mapping\Driver\YamlDriver;
 use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
 
 abstract class AbstractMappingDriverTest extends \Doctrine\ODM\MongoDB\Tests\BaseTest
@@ -320,6 +318,23 @@ public function testIndexes($class)
 
         return $class;
     }
+
+    /**
+     * @depends testIndexes
+     * @param ClassMetadata $class
+     */
+    public function testShardKey($class)
+    {
+        $shardKey = $class->getShardKey();
+
+        $this->assertTrue(isset($shardKey['keys']['name']), 'Shard key is not mapped');
+        $this->assertEquals(1, $shardKey['keys']['name'], 'Wrong value for shard key');
+
+        $this->assertTrue(isset($shardKey['options']['unique']), 'Shard key option is not mapped');
+        $this->assertTrue($shardKey['options']['unique'], 'Shard key option has wrong value');
+        $this->assertTrue(isset($shardKey['options']['numInitialChunks']), 'Shard key option is not mapped');
+        $this->assertEquals(4096, $shardKey['options']['numInitialChunks'], 'Shard key option has wrong value');
+    }
 }
 
 /**
@@ -329,6 +344,7 @@ public function testIndexes($class)
  * @ODM\DefaultDiscriminatorValue("default")
  * @ODM\HasLifecycleCallbacks
  * @ODM\Indexes(@ODM\Index(keys={"createdAt"="asc"},expireAfterSeconds=3600))
+ * @ODM\ShardKey(keys={"name"="asc"},unique=true,numInitialChunks=4096)
  */
 class AbstractMappingDriverUser
 {
@@ -519,5 +535,6 @@ public static function loadMetadata(ClassMetadata $metadata)
         $metadata->addIndex(array('email' => 'desc'), array('unique' => true, 'dropDups' => true));
         $metadata->addIndex(array('mysqlProfileId' => 'desc'), array('unique' => true, 'dropDups' => true));
         $metadata->addIndex(array('createdAt' => 'asc'), array('expireAfterSeconds' => 3600));
+        $metadata->setShardKey(array('name' => 'asc'), array('unique' => true, 'numInitialChunks' => 4096));
     }
 }
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AnnotationDriverTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AnnotationDriverTest.php
index 98bf3e04f8..0084012afc 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AnnotationDriverTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/AnnotationDriverTest.php
@@ -3,7 +3,6 @@
 namespace Doctrine\ODM\MongoDB\Tests\Mapping;
 
 use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
-use Doctrine\ODM\MongoDB\Events;
 use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
 
 class AnnotationDriverTest extends AbstractMappingDriverTest
@@ -140,6 +139,15 @@ public function testGetClassNamesReturnsOnlyTheAppropriateClasses()
         $this->assertNotContains($extraneousClassName, $classes);
     }
 
+    /**
+     * @expectedException \Doctrine\ODM\MongoDB\Mapping\MappingException
+     * @expectedExceptionMessage Embedded document can't have shard key
+     */
+    public function testEmbeddedClassCantHaveShardKey()
+    {
+        $this->dm->getClassMetadata(__NAMESPACE__ . '\AnnotationDriverEmbeddedWithShardKey');
+    }
+
     protected function _loadDriverForCMSDocuments()
     {
         $annotationDriver = $this->_loadDriver();
@@ -187,3 +195,13 @@ class AnnotationDriverTestChild extends AnnotationDriverTestParent
     /** @ODM\String */
     public $bar;
 }
+
+/**
+ * @ODM\EmbeddedDocument
+ * @ODM\ShardKey(keys={"foo"="asc"})
+ */
+class AnnotationDriverEmbeddedWithShardKey
+{
+    /** @ODM\String */
+    public $foo;
+}
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataInfoTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataInfoTest.php
index 77a7f9ece1..0b09b05870 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataInfoTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataInfoTest.php
@@ -234,7 +234,7 @@ public function testSimpleReferenceRequiresTargetDocument()
             'simple' => true,
         ));
     }
-    
+
     /**
      * @expectedException \Doctrine\ODM\MongoDB\Mapping\MappingException
      * @expectedExceptionMessage atomicSet collection strategy can be used only in top level document, used in stdClass::many
@@ -328,6 +328,91 @@ public function testReferenceManySortMustNotBeUsedWithNonSetCollectionStrategy()
             'sort' => array('foo' => 1)
         ));
     }
+
+    public function testSetShardKeyForClassWithoutInheritance()
+    {
+        $cm = new ClassMetadataInfo('stdClass');
+        $cm->setShardKey(array('id' => 'asc'));
+
+        $shardKey = $cm->getShardKey();
+
+        $this->assertEquals(array('id' => 1), $shardKey['keys']);
+    }
+
+    public function testSetShardKeyForClassWithSingleCollectionInheritance()
+    {
+        $cm = new ClassMetadataInfo('stdClass');
+        $cm->inheritanceType = ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION;
+        $cm->setShardKey(array('id' => 'asc'));
+
+        $shardKey = $cm->getShardKey();
+
+        $this->assertEquals(array('id' => 1), $shardKey['keys']);
+    }
+
+    /**
+     * @expectedException \Doctrine\ODM\MongoDB\Mapping\MappingException
+     * @expectedExceptionMessage Shard key overriding in subclass is forbidden for single collection inheritance
+     */
+    public function testSetShardKeyForClassWithSingleCollectionInheritanceWhichAlreadyHasIt()
+    {
+        $cm = new ClassMetadataInfo('stdClass');
+        $cm->setShardKey(array('id' => 'asc'));
+        $cm->inheritanceType = ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION;
+
+        $cm->setShardKey(array('foo' => 'asc'));
+    }
+
+    public function testSetShardKeyForClassWithCollPerClassInheritance()
+    {
+        $cm = new ClassMetadataInfo('stdClass');
+        $cm->inheritanceType = ClassMetadataInfo::INHERITANCE_TYPE_COLLECTION_PER_CLASS;
+        $cm->setShardKey(array('id' => 'asc'));
+
+        $shardKey = $cm->getShardKey();
+
+        $this->assertEquals(array('id' => 1), $shardKey['keys']);
+    }
+
+    public function testIsNotShardedIfThereIsNoShardKey()
+    {
+        $cm = new ClassMetadataInfo('stdClass');
+
+        $this->assertFalse($cm->isSharded());
+    }
+
+    public function testIsShardedIfThereIsAShardKey()
+    {
+        $cm = new ClassMetadataInfo('stdClass');
+        $cm->setShardKey(array('id' => 'asc'));
+
+        $this->assertTrue($cm->isSharded());
+    }
+
+    /**
+     * @expectedException \Doctrine\ODM\MongoDB\Mapping\MappingException
+     * @expectedExceptionMessage Embedded document can't have shard key: stdClass
+     */
+    public function testEmbeddedDocumentCantHaveShardKey()
+    {
+        $cm = new ClassMetadataInfo('stdClass');
+        $cm->isEmbeddedDocument = true;
+        $cm->setShardKey(array('id' => 'asc'));
+    }
+
+    /**
+     * @expectedException \Doctrine\ODM\MongoDB\Mapping\MappingException
+     * @expectedExceptionMessage No increment fields allowed in the shard key
+     */
+    public function testNoIncrementFieldsAllowedInShardKey()
+    {
+        $cm = new ClassMetadataInfo('stdClass');
+        $cm->mapField(array(
+            'fieldName' => 'inc',
+            'type' => 'increment'
+        ));
+        $cm->setShardKey(array('inc'));
+    }
 }
 
 class TestCustomRepositoryClass extends DocumentRepository
@@ -344,7 +429,7 @@ class EmbeddedAssociationsCascadeTest
 {
     /** @ODM\Id */
     public $id;
- 
+
     /** @ODM\EmbedOne(targetDocument="Documents\Address") */
     public $address;
 
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php
index df122515b1..b420737b3d 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php
@@ -4,7 +4,6 @@
 
 use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
 use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
-use Doctrine\ODM\MongoDB\Events;
 
 class ClassMetadataTest extends \Doctrine\ODM\MongoDB\Tests\BaseTest
 {
@@ -31,6 +30,7 @@ public function testClassMetadataInstanceSerialization()
         $cm->setFile('customFileProperty');
         $cm->setDistance('customDistanceProperty');
         $cm->setSlaveOkay(true);
+        $cm->setShardKey(array('_id' => '1'));
         $cm->setCollectionCapped(true);
         $cm->setCollectionMax(1000);
         $cm->setCollectionSize(500);
@@ -57,6 +57,7 @@ public function testClassMetadataInstanceSerialization()
         $this->assertEquals('customFileProperty', $cm->file);
         $this->assertEquals('customDistanceProperty', $cm->distance);
         $this->assertTrue($cm->slaveOkay);
+        $this->assertEquals(array('keys' => array('_id' => 1), 'options' => array()), $cm->getShardKey());
         $mapping = $cm->getFieldMapping('phonenumbers');
         $this->assertEquals('Documents\Bar', $mapping['targetDocument']);
         $this->assertTrue($cm->getCollectionCapped());
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ShardKeyInheritanceMappingTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ShardKeyInheritanceMappingTest.php
new file mode 100644
index 0000000000..5324863a39
--- /dev/null
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ShardKeyInheritanceMappingTest.php
@@ -0,0 +1,127 @@
+<?php
+
+namespace Doctrine\ODM\MongoDB\Tests\Mapping;
+
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory;
+use Doctrine\ODM\MongoDB\Tests\BaseTest;
+
+class ShardKeyInheritanceMappingTest extends BaseTest
+{
+    private $factory;
+
+    public function setUp()
+    {
+        parent::setUp();
+        $this->factory = new ClassMetadataFactory();
+        $this->factory->setDocumentManager($this->dm);
+        $this->factory->setConfiguration($this->dm->getConfiguration());
+    }
+
+
+    public function testShardKeyFromMappedSuperclass()
+    {
+        $class = $this->factory->getMetadataFor(__NAMESPACE__ . '\\ShardedSubclass');
+
+        $this->assertTrue($class->isSharded());
+        $this->assertEquals(array('keys' => array('_id' => 1), 'options' => array()), $class->getShardKey());
+    }
+
+    public function testShardKeySingleCollectionInheritance()
+    {
+        $class = $this->factory->getMetadataFor(__NAMESPACE__ . '\\ShardedSingleCollInheritance2');
+
+        $this->assertTrue($class->isSharded());
+        $this->assertEquals(array('keys' => array('_id' => 1), 'options' => array()), $class->getShardKey());
+    }
+
+    /**
+     * @expectedException Doctrine\ODM\MongoDB\Mapping\MappingException
+     */
+    public function testShardKeySingleCollectionInheritanceOverriding()
+    {
+        $this->factory->getMetadataFor(__NAMESPACE__ . '\\ShardedSingleCollInheritance3');
+    }
+
+    public function testShardKeyCollectionPerClassInheritance()
+    {
+        $class = $this->factory->getMetadataFor(__NAMESPACE__ . '\\ShardedCollectionPerClass2');
+
+        $this->assertTrue($class->isSharded());
+        $this->assertEquals(array('keys' => array('_id' => 1), 'options' => array()), $class->getShardKey());
+    }
+
+    public function testShardKeyCollectionPerClassInheritanceOverriding()
+    {
+        $class = $this->factory->getMetadataFor(__NAMESPACE__ . '\\ShardedCollectionPerClass3');
+
+        $this->assertTrue($class->isSharded());
+        $this->assertEquals(array('keys' => array('_id' => 'hashed'), 'options' => array()), $class->getShardKey());
+    }
+}
+
+
+/**
+ * @ODM\MappedSuperclass
+ * @ODM\ShardKey(keys={"_id"="asc"})
+ */
+class ShardedSuperclass
+{
+    /** @ODM\String */
+    private $name;
+}
+
+/** @ODM\Document */
+class ShardedSubclass extends ShardedSuperclass
+{
+    /** @ODM\Id */
+    private $id;
+}
+
+/**
+ * @ODM\Document
+ * @ODM\InheritanceType("SINGLE_COLLECTION")
+ * @ODM\ShardKey(keys={"_id"="asc"})
+ */
+class ShardedSingleCollInheritance1
+{
+    /** @ODM\Id */
+    private $id;
+}
+
+/**
+ * @ODM\Document
+ */
+class ShardedSingleCollInheritance2 extends ShardedSingleCollInheritance1
+{}
+
+/**
+ * @ODM\Document
+ * @ODM\ShardKey(keys={"_id"="hashed"})
+ */
+class ShardedSingleCollInheritance3 extends ShardedSingleCollInheritance1
+{}
+
+/**
+ * @ODM\Document
+ * @ODM\InheritanceType("COLLECTION_PER_CLASS")
+ * @ODM\ShardKey(keys={"_id"="asc"})
+ */
+class ShardedCollectionPerClass1
+{
+    /** @ODM\Id */
+    private $id;
+}
+
+/**
+ * @ODM\Document
+ */
+class ShardedCollectionPerClass2 extends ShardedCollectionPerClass1
+{}
+
+/**
+ * @ODM\Document
+ * @ODM\ShardKey(keys={"_id"="hashed"})
+ */
+class ShardedCollectionPerClass3 extends ShardedCollectionPerClass1
+{}
\ No newline at end of file
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/XmlMappingDriverTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/XmlMappingDriverTest.php
index e92cc67a1e..89865d2b4c 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/XmlMappingDriverTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/XmlMappingDriverTest.php
@@ -2,6 +2,7 @@
 
 namespace Doctrine\ODM\MongoDB\Tests\Mapping;
 
+use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
 use Doctrine\ODM\MongoDB\Mapping\Driver\XmlDriver;
 
 class XmlMappingDriverTest extends AbstractMappingDriverTest
@@ -10,4 +11,21 @@ protected function _loadDriver()
     {
         return new XmlDriver(__DIR__ . DIRECTORY_SEPARATOR . 'xml');
     }
+
+    public function testSetShardKeyOptionsByAttributes()
+    {
+        $class = new ClassMetadataInfo('doc');
+        $driver = $this->_loadDriver();
+        $element = new \SimpleXmlElement('<shard-key unique="true" numInitialChunks="4096"><key name="_id"/></shard-key>');
+
+        /** @uses XmlDriver::setShardKey */
+        $m = new \ReflectionMethod(get_class($driver), 'setShardKey');
+        $m->setAccessible(true);
+        $m->invoke($driver, $class, $element);
+
+        $this->assertTrue($class->isSharded());
+        $shardKey = $class->getShardKey();
+        $this->assertSame(array('unique' => true, 'numInitialChunks' => 4096), $shardKey['options']);
+        $this->assertSame(array('_id' => 1), $shardKey['keys']);
+    }
 }
\ No newline at end of file
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/xml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverUser.dcm.xml b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/xml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverUser.dcm.xml
index 25e74305b0..2a3cd81c6d 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/xml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverUser.dcm.xml
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/xml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverUser.dcm.xml
@@ -67,5 +67,10 @@
             <lifecycle-callback method="doOtherStuffOnPrePersistToo" type="prePersist" />
             <lifecycle-callback method="doStuffOnPostPersist" type="postPersist" />
         </lifecycle-callbacks>
+        <shard-key>
+            <key name="name" order="asc"/>
+            <option name="unique" value="true"/>
+            <option name="numInitialChunks" value="4096"/>
+        </shard-key>
     </document>
 </doctrine-mongo-mapping>
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/yaml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverUser.dcm.yml b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/yaml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverUser.dcm.yml
index 5291669693..e5718d4e34 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/yaml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverUser.dcm.yml
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/yaml/Doctrine.ODM.MongoDB.Tests.Mapping.AbstractMappingDriverUser.dcm.yml
@@ -46,6 +46,12 @@ Doctrine\ODM\MongoDB\Tests\Mapping\AbstractMappingDriverUser:
       options:
         unique: true
         dropDups: true
+  shardKey:
+    keys:
+      name: asc
+    options:
+      unique: true
+      numInitialChunks: 4096
   referenceOne:
     address:
       targetDocument: Address
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Persisters/DocumentPersisterGetShardKeyQueryTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Persisters/DocumentPersisterGetShardKeyQueryTest.php
new file mode 100644
index 0000000000..891865267d
--- /dev/null
+++ b/tests/Doctrine/ODM/MongoDB/Tests/Persisters/DocumentPersisterGetShardKeyQueryTest.php
@@ -0,0 +1,113 @@
+<?php
+
+namespace Doctrine\ODM\MongoDB\Tests\Persisters;
+
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+use Doctrine\ODM\MongoDB\Persisters\DocumentPersister;
+use Doctrine\ODM\MongoDB\Tests\BaseTest;
+
+class DocumentPersisterGetShardKeyQueryTest extends BaseTest
+{
+    public function testGetShardKeyQueryScalars()
+    {
+        $o = new ShardedByScalars();
+        $o->int = 1;
+        $o->string = 'hi';
+        $o->bool = true;
+        $o->float = 1.2;
+
+        /** @var DocumentPersister $persister */
+        $persister = $this->uow->getDocumentPersister(get_class($o));
+
+        $this->assertSame(
+            array('int' => $o->int, 'string' => $o->string, 'bool' => $o->bool, 'float' => $o->float),
+            $persister->getShardKeyQuery($o)
+        );
+    }
+
+    public function testGetShardKeyQueryObjects()
+    {
+        $o = new ShardedByObjects();
+        $o->oid = '54ca2c4c81fec698130041a7';
+        $o->bin = 'hi';
+        $o->date = new \DateTime();
+
+        /** @var DocumentPersister $persister */
+        $persister = $this->uow->getDocumentPersister(get_class($o));
+
+        $shardKeyQuery = $persister->getShardKeyQuery($o);
+
+        $this->assertInstanceOf('MongoId', $shardKeyQuery['oid']);
+        $this->assertSame($o->oid, $shardKeyQuery['oid']->{'$id'});
+
+        $this->assertInstanceOf('MongoBinData', $shardKeyQuery['bin']);
+        $this->assertSame($o->bin, $shardKeyQuery['bin']->bin);
+
+        $this->assertInstanceOf('MongoDate', $shardKeyQuery['date']);
+        $this->assertSame($o->date->getTimestamp(), $shardKeyQuery['date']->sec);
+        $this->assertSame(0, $shardKeyQuery['date']->usec);
+    }
+
+    public function testShardById()
+    {
+        $o = new ShardedById();
+        $o->identifier = new \MongoId();
+
+        /** @var DocumentPersister $persister */
+        $persister = $this->uow->getDocumentPersister(get_class($o));
+        $shardKeyQuery = $persister->getShardKeyQuery($o);
+
+        $this->assertSame(array('_id' => $o->identifier), $shardKeyQuery);
+    }
+}
+
+/**
+ * @ODM\Document
+ * @ODM\ShardKey(keys={"int"="asc","string"="asc","bool"="asc","float"="asc"})
+ */
+class ShardedByScalars
+{
+    /** @ODM\Id */
+    public $id;
+
+    /** @ODM\Int */
+    public $int;
+
+    /** @ODM\String */
+    public $string;
+
+    /** @ODM\Boolean */
+    public $bool;
+
+    /** @ODM\Float */
+    public $float;
+}
+
+/**
+ * @ODM\Document
+ * @ODM\ShardKey(keys={"oid"="asc","bin"="asc","date"="asc"})
+ */
+class ShardedByObjects
+{
+    /** @ODM\Id */
+    public $id;
+
+    /** @ODM\ObjectId */
+    public $oid;
+
+    /** @ODM\Bin */
+    public $bin;
+
+    /** @ODM\Date */
+    public $date;
+}
+
+/**
+ * @ODM\Document
+ * @ODM\ShardKey(keys={"_id"="asc"})
+ */
+class ShardedById
+{
+    /** @ODM\Id */
+    public $identifier;
+}
\ No newline at end of file
diff --git a/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php b/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php
index dde3c2753b..c32b6b5d1b 100644
--- a/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php
+++ b/tests/Doctrine/ODM/MongoDB/Tests/SchemaManagerTest.php
@@ -295,6 +295,149 @@ public function testDropDatabases()
         $this->schemaManager->dropDatabases();
     }
 
+    public function testEnsureDocumentSharding()
+    {
+        $dbName = DOCTRINE_MONGODB_DATABASE;
+        $classMetadata = $this->dm->getClassMetadata('Documents\Sharded\ShardedUser');
+        $collectionName = $classMetadata->getCollection();
+        $dbMock = $this->getMockDatabase();
+        $dbMock->method('getName')->willReturn($dbName);
+        $adminDBMock = $this->getMockDatabase();
+        $connMock = $this->getMockConnection();
+        $connMock->method('selectDatabase')->with('admin')->willReturn($adminDBMock);
+        $this->dm->connection = $connMock;
+        $this->dm->documentDatabases = array($classMetadata->getName() => $dbMock);
+
+        $adminDBMock
+            ->expects($this->at(0))
+            ->method('command')
+            ->with(array('enableSharding' => $dbName))
+            ->willReturn(array('ok' => 1));
+        $adminDBMock
+            ->expects($this->at(1))
+            ->method('command')
+            ->with(array('shardCollection' => $dbName . '.' . $collectionName, 'key' => array('_id' => 'hashed')))
+            ->willReturn(array('ok' => 1));
+
+        $this->schemaManager->ensureDocumentSharding($classMetadata->getName());
+    }
+
+    /**
+     * @expectedException \Doctrine\ODM\MongoDB\MongoDBException
+     * @expectedExceptionMessage Failed to ensure sharding for document
+     */
+    public function testEnsureDocumentShardingThrowsExceptionIfThereWasAnError()
+    {
+        $dbName = DOCTRINE_MONGODB_DATABASE;
+        $classMetadata = $this->dm->getClassMetadata('Documents\Sharded\ShardedUser');
+        $collectionName = $classMetadata->getCollection();
+        $dbMock = $this->getMockDatabase();
+        $dbMock->method('getName')->willReturn($dbName);
+        $adminDBMock = $this->getMockDatabase();
+        $connMock = $this->getMockConnection();
+        $connMock->method('selectDatabase')->with('admin')->willReturn($adminDBMock);
+        $this->dm->connection = $connMock;
+        $this->dm->documentDatabases = array($classMetadata->getName() => $dbMock);
+
+        $adminDBMock
+            ->expects($this->at(0))
+            ->method('command')
+            ->with(array('enableSharding' => $dbName))
+            ->willReturn(array('ok' => 1));
+        $adminDBMock
+            ->expects($this->at(1))
+            ->method('command')
+            ->with(array('shardCollection' => $dbName . '.' . $collectionName, 'key' => array('_id' => 'hashed')))
+            ->willReturn(array('ok' => 0, 'errmsg' => 'scary error'));
+
+        $this->schemaManager->ensureDocumentSharding($classMetadata->getName());
+    }
+
+    public function testEnsureDocumentShardingIgnoresAlreadyShardedError()
+    {
+        $dbName = DOCTRINE_MONGODB_DATABASE;
+        $classMetadata = $this->dm->getClassMetadata('Documents\Sharded\ShardedUser');
+        $collectionName = $classMetadata->getCollection();
+        $dbMock = $this->getMockDatabase();
+        $dbMock->method('getName')->willReturn($dbName);
+        $adminDBMock = $this->getMockDatabase();
+        $connMock = $this->getMockConnection();
+        $connMock->method('selectDatabase')->with('admin')->willReturn($adminDBMock);
+        $this->dm->connection = $connMock;
+        $this->dm->documentDatabases = array($classMetadata->getName() => $dbMock);
+
+        $adminDBMock
+            ->expects($this->at(0))
+            ->method('command')
+            ->with(array('enableSharding' => $dbName))
+            ->willReturn(array('ok' => 1));
+        $adminDBMock
+            ->expects($this->at(1))
+            ->method('command')
+            ->with(array('shardCollection' => $dbName . '.' . $collectionName, 'key' => array('_id' => 'hashed')))
+            ->willReturn(array('ok' => 0, 'errmsg' => 'already sharded'));
+
+        $this->schemaManager->ensureDocumentSharding($classMetadata->getName());
+    }
+
+    public function testEnableShardingForDb()
+    {
+        $adminDBMock = $this->getMockDatabase();
+        $adminDBMock
+            ->expects($this->once())
+            ->method('command')
+            ->with(array('enableSharding' => 'db'))
+            ->willReturn(array('ok' => 1));
+        $connMock = $this->getMockConnection();
+        $connMock->method('selectDatabase')->with('admin')->willReturn($adminDBMock);
+        $this->dm->connection = $connMock;
+        $dbMock = $this->getMockDatabase();
+        $dbMock->method('getName')->willReturn('db');
+        $this->dm->documentDatabases = array('Documents\Sharded\ShardedUser' => $dbMock);
+
+        $this->schemaManager->enableShardingForDbByDocumentName('Documents\Sharded\ShardedUser');
+    }
+
+    /**
+     * @expectedException \Doctrine\ODM\MongoDB\MongoDBException
+     * @expectedExceptionMessage Failed to enable sharding for database
+     */
+    public function testEnableShardingForDbThrowsExceptionInCaseOfError()
+    {
+        $adminDBMock = $this->getMockDatabase();
+        $adminDBMock
+            ->expects($this->once())
+            ->method('command')
+            ->with(array('enableSharding' => 'db'))
+            ->willReturn(array('ok' => 0, 'errmsg' => 'scary error'));
+        $connMock = $this->getMockConnection();
+        $connMock->method('selectDatabase')->with('admin')->willReturn($adminDBMock);
+        $this->dm->connection = $connMock;
+        $dbMock = $this->getMockDatabase();
+        $dbMock->method('getName')->willReturn('db');
+        $this->dm->documentDatabases = array('Documents\Sharded\ShardedUser' => $dbMock);
+
+        $this->schemaManager->enableShardingForDbByDocumentName('Documents\Sharded\ShardedUser');
+    }
+
+    public function testEnableShardingForDbIgnoresAlreadyShardedError()
+    {
+        $adminDBMock = $this->getMockDatabase();
+        $adminDBMock
+            ->expects($this->once())
+            ->method('command')
+            ->with(array('enableSharding' => 'db'))
+            ->willReturn(array('ok' => 0, 'errmsg' => 'already enabled'));
+        $connMock = $this->getMockConnection();
+        $connMock->method('selectDatabase')->with('admin')->willReturn($adminDBMock);
+        $this->dm->connection = $connMock;
+        $dbMock = $this->getMockDatabase();
+        $dbMock->method('getName')->willReturn('db');
+        $this->dm->documentDatabases = array('Documents\Sharded\ShardedUser' => $dbMock);
+
+        $this->schemaManager->enableShardingForDbByDocumentName('Documents\Sharded\ShardedUser');
+    }
+
     private function getMockCollection()
     {
         return $this->getMockBuilder('Doctrine\MongoDB\Collection')
@@ -345,4 +488,11 @@ private function getMockUnitOfWork()
 
         return $uow;
     }
+
+    private function getMockConnection()
+    {
+        return $this->getMockBuilder('Doctrine\MongoDB\Connection')
+            ->disableOriginalConstructor()
+            ->getMock();
+    }
 }
diff --git a/tests/Documents/Sharded/ShardedOne.php b/tests/Documents/Sharded/ShardedOne.php
new file mode 100644
index 0000000000..0fa5aaf89c
--- /dev/null
+++ b/tests/Documents/Sharded/ShardedOne.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Documents\Sharded;
+
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+/**
+ * @ODM\Document(collection="sharded.one")
+ * @ODM\ShardKey(keys={"k"="asc"})
+ */
+class ShardedOne
+{
+    /** @ODM\Id */
+    public $id;
+
+    /** @ODM\String */
+    public $title = 'test';
+
+    /** @ODM\String(name="k") */
+    public $key = 'testing';
+}
\ No newline at end of file
diff --git a/tests/Documents/Sharded/ShardedUser.php b/tests/Documents/Sharded/ShardedUser.php
new file mode 100644
index 0000000000..5aca4c9997
--- /dev/null
+++ b/tests/Documents/Sharded/ShardedUser.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Documents\Sharded;
+
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+/**
+ * @ODM\Document(collection="sharded.users")
+ * @ODM\ShardKey(keys={"_id"="hashed"})
+ */
+class ShardedUser
+{
+    /** @ODM\Id */
+    public $id;
+
+    /** @ODM\String */
+    public $name;
+}
\ No newline at end of file