public function lock(string $id, bool $blocking = true, int $limit = 300): void
    {
        $this->autoCreateIndexes && $this->createIndexes();

        if ($limit > $this->limit) {
            throw new \LogicException('The limit could not be greater than a default one. The default is used to set an expiration index');
        }

        $timeout = time() + $limit; // I think it must be a bit greater then mongos index ttl so there is a way to process data.

        while (time() < $timeout) {
            try {
                $result = $this->collection->insertOne([
                    'id' => $id,
                    'timestamp' => new UTCDatetime(time() * 1000),
                    'sessionId' => $this->sessionId,
                ]);

                if (false == $result->isAcknowledged()) {
                    throw new \LogicException(sprintf('Cannot obtain the lock for id %s. The insertOne operation is not acknowledged.', $id));
                }

                return;
            } catch (BulkWriteException $e) {
            } catch (DuplicateKeyException $e) {
                // The lock is obtained by another process. Let's try again later.
            }

            if (false == $blocking) {
                throw PessimisticLockException::failedObtainLock($id, $limit);
            }

            // Mongo does database lock level on insert, so everything has to wait even reads.
            // I decided to do it rarely to decrease global lock rate.
            // We will have at least 150 attempts to get the lock, pretty enough IMO.
            // More here http://docs.mongodb.org/manual/faq/concurrency/
            usleep(200000);
        }

        throw PessimisticLockException::failedObtainLock($id, $limit);
    }