IT

mongodb에서 중복 문서를 가장 빨리 제거하는 방법

itgroup 2023. 6. 2. 20:22
반응형

mongodb에서 중복 문서를 가장 빨리 제거하는 방법

mongodb(향후 10m+)에 약 170만 건의 문서를 보유하고 있습니다.그 중 일부는 제가 원하지 않는 중복 항목을 나타냅니다.문서의 구조는 다음과 같습니다.

{
    _id: 14124412,
    nodes: [
        12345,
        54321
        ],
    name: "Some beauty"
}

이름이 같은 다른 문서와 동일한 노드가 하나 이상 있는 경우 문서는 중복됩니다.중복을 제거하는 가장 빠른 방법은 무엇입니까?

dropDups: true3.0에서는 옵션을 할 수 .

중복 항목을 수집한 다음 한 번에 제거할 수 있는 집계 프레임워크가 있는 솔루션이 있습니다.

시스템 수준 "인덱스" 변경보다 다소 느릴 수 있습니다.그러나 중복된 문서를 제거하는 방법을 고려하는 것이 좋습니다.

한 번에 모든 문서 제거

var duplicates = [];

db.collectionName.aggregate([
  { $match: { 
    name: { "$ne": '' }  // discard selection criteria
  }},
  { $group: { 
    _id: { name: "$name"}, // can be grouped on multiple properties 
    dups: { "$addToSet": "$_id" }, 
    count: { "$sum": 1 } 
  }},
  { $match: { 
    count: { "$gt": 1 }    // Duplicates considered as count greater than one
  }}
],
{allowDiskUse: true}       // For faster processing if set is larger
)               // You can display result until this and check duplicates 
.forEach(function(doc) {
    doc.dups.shift();      // First element skipped for deleting
    doc.dups.forEach( function(dupId){ 
        duplicates.push(dupId);   // Getting all duplicate ids
        }
    )
})

// If you want to Check all "_id" which you are deleting else print statement not needed
printjson(duplicates);     

// Remove all duplicates in one go    
db.collectionName.remove({_id:{$in:duplicates}})  

문서를 하나씩 삭제할 수 있습니다.

db.collectionName.aggregate([
  // discard selection criteria, You can remove "$match" section if you want
  { $match: { 
    source_references.key: { "$ne": '' }  
  }},
  { $group: { 
    _id: { source_references.key: "$source_references.key"}, // can be grouped on multiple properties 
    dups: { "$addToSet": "$_id" }, 
    count: { "$sum": 1 } 
  }}, 
  { $match: { 
    count: { "$gt": 1 }    // Duplicates considered as count greater than one
  }}
],
{allowDiskUse: true}       // For faster processing if set is larger
)               // You can display result until this and check duplicates 
.forEach(function(doc) {
    doc.dups.shift();      // First element skipped for deleting
    db.collectionName.remove({_id : {$in: doc.dups }});  // Delete remaining duplicates
})

된 문서를 합니다.name+nodes할 수 .unique다음 옵션을 사용한 색인:

db.test.ensureIndex({name: 1, nodes: 1}, {unique: true, dropDups: true}) 

문서에 나와 있듯이 데이터베이스에서 데이터가 삭제되므로 이 작업에 매우 주의해야 합니다.데이터베이스가 예상대로 작동하지 않을 경우 먼저 데이터베이스를 백업합니다.

갱신하다

은 "2.x"라는으로 MongoDB 2합니다.dropDups3.0(하드웨어)에서는 옵션을 더 이상 사용할 수 없습니다.

mongodump를 사용하여 수집 덤프 생성

수집 지우기

고유 인덱스 추가

mongore restore를 사용하여 컬렉션 복원

MongoDB 3.4에서 작동하는 이 솔루션을 찾았습니다: 중복이 있는 필드를 fieldX라고 가정하겠습니다.

db.collection.aggregate([
{
    // only match documents that have this field
    // you can omit this stage if you don't have missing fieldX
    $match: {"fieldX": {$nin:[null]}}  
},
{
    $group: { "_id": "$fieldX", "doc" : {"$first": "$$ROOT"}}
},
{
    $replaceRoot: { "newRoot": "$doc"}
}
],
{allowDiskUse:true})

mongoDB를 처음 접하는 저는 많은 시간을 들였고 중복되는 것을 찾아 삭제하기 위해 다른 긴 솔루션을 사용했습니다.하지만, 저는 이 해결책이 깔끔하고 이해하기 쉽다고 생각합니다.

필드 X가 포함된 문서를 먼저 일치시키는 방식으로 작동합니다(이 필드가 없는 일부 문서를 가지고 있었는데 추가로 빈 결과가 하나 더 있습니다).

다음 단계에서는 필드 X별로 문서를 그룹화하고 $$ROOT를 사용하여 각 그룹에 $first 문서만 삽입합니다.마지막으로 $first 및 $$ROOT를 사용하여 찾은 문서로 전체 집계 그룹을 대체합니다.

컬렉션이 크기 때문에 allowDiskUse를 추가해야 했습니다.

파이프라인 개수에 상관없이 추가할 수 있으며, $first에 대한 문서에 $first를 사용하기 전에 정렬 단계가 언급되어 있지만, $first를 사용하기 전에 저에게 적합했습니다.여기에 링크를 게시할 수 없습니다. 제 평판은 10 미만입니다. :( "

$out 단계를 추가하여 결과를 새 컬렉션에 저장할 수 있습니다...

또는 replaceRoot 없이 그룹 단계에서 전체 문서가 아닌 field1, field2와 같은 일부 필드에만 관심이 있는 경우:

db.collection.aggregate([
{
    // only match documents that have this field
    $match: {"fieldX": {$nin:[null]}}  
},
{
    $group: { "_id": "$fieldX", "field1": {"$first": "$$ROOT.field1"}, "field2": { "$first": "$field2" }}
}
],
{allowDiskUse:true})

다음 Mongo 집계 파이프라인은 중복제거를 수행하여 동일하거나 다른 컬렉션으로 다시 출력합니다.

collection.aggregate([
  { $group: {
    _id: '$field_to_dedup',
    doc: { $first: '$$ROOT' }
  } },
  { $replaceRoot: {
    newRoot: '$doc'
  } },
  { $out: 'collection' }
], { allowDiskUse: true })

제 DB에는 수백만 개의 중복 레코드가 있었습니다.@수백만 개의 중복된 기록을 삭제하려는 사람들에게 효과적인 해결책을 쓰는 것처럼, nath의 대답은 효과가 없었습니다.

/** Create a array to store all duplicate records ids*/
var duplicates = [];

/** Start Aggregation pipeline*/
db.collection.aggregate([
  {
    $match: { /** Add any filter here. Add index for filter keys*/
      filterKey: {
        $exists: false
      }
    }
  },
  {
    $sort: { /** Sort it in such a way that you want to retain first element*/
      createdAt: -1
    }
  },
  {
    $group: {
      _id: {
        key1: "$key1", key2:"$key2" /** These are the keys which define the duplicate. Here document with same value for key1 and key2 will be considered duplicate*/
      },
      dups: {
        $push: {
          _id: "$_id"
        }
      },
      count: {
        $sum: 1
      }
    }
  },
  {
    $match: {
      count: {
        "$gt": 1
      }
    }
  }
],
{
  allowDiskUse: true
}).forEach(function(doc){
  doc.dups.shift();
  doc.dups.forEach(function(dupId){
    duplicates.push(dupId._id);
  })
})

/** Delete the duplicates*/
var i,j,temparray,chunk = 100000;
for (i=0,j=duplicates.length; i<j; i+=chunk) {
    temparray = duplicates.slice(i,i+chunk);
    db.collection.bulkWrite([{deleteMany:{"filter":{"_id":{"$in":temparray}}}}])
}

다음은 조금 더 '수동적'인 방법입니다.

기본적으로 먼저 관심 있는 모든 고유 키 목록을 가져옵니다.

그런 다음 각 키를 사용하여 검색을 수행하고 검색 결과가 1보다 크면 삭제합니다.

  db.collection.distinct("key").forEach((num)=>{
    var i = 0;
    db.collection.find({key: num}).forEach((doc)=>{
      if (i)   db.collection.remove({key: num}, { justOne: true })
      i++
    })
  });

문서의 일부만 복제되는 경우 속도를 높이는 팁:

  1. 중복 항목을 탐지하려면 필드에 색인이 있어야 합니다.
  2. $group은 인덱스를 사용하지 않지만 $185를 사용할 수 있으며 $185는 인덱스를 사용합니다. 그래서 당신은 처음에 $185 스텝을 넣어야 합니다.
  3. $out to new collection 대신 delete_many()를 사용하면 IO 시간과 디스크 공간을 많이 절약할 수 있습니다.

Pymongo를 사용하면 다음을 수행할 수 있습니다.

index_uuid = IndexModel(
    [
        ('uuid', pymongo.ASCENDING)
    ],
)
col.create_indexes([index_uuid])
pipeline = [
    {"$sort": {"uuid":1}},
    {
        "$group": {
            "_id": "$uuid",
            "dups": {"$addToSet": "$_id"},
            "count": {"$sum": 1}
        }
    },
    {
        "$match": {"count": {"$gt": 1}}
    },
]
it_cursor = col.aggregate(
    pipeline, allowDiskUse=True
)
# skip 1st dup of each dups group
dups = list(itertools.chain.from_iterable(map(lambda x: x["dups"][1:], it_cursor)))
col.delete_many({"_id":{"$in": dups}})

성능

저는 데이터베이스에 3,000만 개의 문서와 1TB의 대용량 문서를 포함하여 테스트합니다.

  • 색인/정렬을 사용하지 않으면 커서를 가져오는 데 한 시간 이상이 걸립니다(환자가 기다릴 필요조차 없습니다).
  • $out을 사용하여 새 컬렉션으로 출력합니다.파일 시스템이 스냅샷을 지원하지 않는 경우 더 안전합니다.하지만 SSD를 사용하고 있음에도 불구하고 디스크 공간이 많이 필요하고 완료하는 데 40분 이상이 걸립니다.HDD RAID를 사용하면 속도가 훨씬 느려집니다.
  • 인덱스/인스턴스 및 inplace delete_many를 사용하면 총 5분 정도 소요됩니다.

다음 방법은 동일한 이름의 문서를 병합하면서 중복 없이 고유한 노드만 유지합니다.

를 사용하여 찾았습니다.$out연산자는 간단한 방법입니다.저는 배열을 푼 다음 집합에 추가하여 그룹화합니다.$outoperator를 사용하면 집계 결과가 지속될 수 있습니다.컬렉션 이름을 입력하면 컬렉션이 새 데이터로 바뀝니다.이름이 존재하지 않으면 새 컬렉션이 만들어집니다.

이게 도움이 되길 바랍니다.

allowDiskUse파이프라인에 추가해야 할 수 있습니다.

db.collectionName.aggregate([
  {
    $unwind:{path:"$nodes"},
  },
  {
    $group:{
      _id:"$name",
      nodes:{
        $addToSet:"$nodes"
      }
  },
  {
    $project:{
      _id:0,
      name:"$_id.name",
      nodes:1
    }
  },
  {
    $out:"collectionNameWithoutDuplicates"
  }
])

파이몬고를 사용하면 효과가 있을 것입니다.

unique_field에서 컬렉션에 대해 고유해야 하는 필드를 추가합니다.

unique_field = {"field1":"$field1","field2":"$field2"}

cursor = DB.COL.aggregate([{"$group":{"_id":unique_field, "dups":{"$push":"$uuid"}, "count": {"$sum": 1}}},{"$match":{"count": {"$gt": 1}}},{"$group":"_id":None,"dups":{"$addToSet":{"$arrayElemAt":["$dups",1]}}}}],allowDiskUse=True)

중복 횟수에 따라 dups 배열을 분할합니다(여기서는 모두에 대해 하나의 추가 중복만 있음).

items = list(cursor)
removeIds = items[0]['dups']
hold.remove({"uuid":{"$in":removeIds}})

주요 질문에 답할지는 모르겠지만, 다른 사람들에게는 유용할 것입니다. 1.findOne() 메서드를 사용하여 중복 행을 쿼리하고 개체로 저장합니다.

const User = db.User.findOne({_id:"duplicateid"});

2. deleteMany() 메서드를 실행하여 ID가 "duplicateid"인 모든 행을 제거합니다.

db.User.deleteMany({_id:"duplicateid"});

3. User 개체에 저장된 값을 삽입합니다.

db.User.insertOne(User);

쉽고 빠르게!!!!

먼저 DB에서 모든 중복 항목을 찾아 해당 중복 항목을 제거할 수 있습니다.여기서는 중복 항목을 확인하고 제거하기 위해 ID 열을 사용합니다.

db.collection.aggregate([
    { "$group": { "_id": "$id", "count": { "$sum": 1 } } },
    { "$match": { "_id": { "$ne": null }, "count": { "$gt": 1 } } },
    { "$sort": { "count": -1 } },
    { "$project": { "name": "$_id", "_id": 0 } }
]).then(data => {
    var dr = data.map(d => d.name);
    console.log("duplicate Recods:: ", dr);
    db.collection.remove({ id: { $in: dr } }).then(removedD => {
        console.log("Removed duplicate Data:: ", removedD);
    })
})
  1. 일반적인 아이디어는 findOne https://docs.mongodb.com/manual/reference/method/db.collection.findOne/ 을 사용하여 컬렉션의 중복 레코드에서 하나의 임의 ID를 검색하는 것입니다.

  2. findOne 옵션에서 검색한 random-id 이외의 모든 레코드를 삭제합니다.

만약 당신이 피몽고에서 그것을 하려고 한다면 당신은 이것과 같은 것을 할 수 있습니다.

def _run_query():

        try:

            for record in (aggregate_based_on_field(collection)):
                if not record:
                    continue
                _logger.info("Working on Record %s", record)

                try:
                    retain = db.collection.find_one(find_one({'fie1d1': 'x',  'field2':'y'}, {'_id': 1}))
                    _logger.info("_id to retain from duplicates %s", retain['_id'])

                    db.collection.remove({'fie1d1': 'x',  'field2':'y', '_id': {'$ne': retain['_id']}})

                except Exception as ex:
                    _logger.error(" Error when retaining the record :%s Exception: %s", x, str(ex))

        except Exception as e:
            _logger.error("Mongo error when deleting duplicates %s", str(e))


def aggregate_based_on_field(collection):
    return collection.aggregate([{'$group' : {'_id': "$fieldX"}}])

셸에서:

  1. find_one을 findOne으로 바꿉니다.
  2. 동일한 제거 명령이 작동해야 합니다.

저는 3M 중복 레코드를 제거해야 했고 Mongo shell에서 다음과 같은 작업을 했습니다.

  1. use your_DB_name
for (var i = 1; i <= 30; i++) {
var data = db.collectionName.aggregate([
     {"$group" : { "_id":"$yourGroupById" , "count": { "$sum": 1 },"data": { "$push": "$$ROOT" }}},
  {
       $project:
          {
            result: { $slice: [ "$data", { $subtract: [{ $size: "$data" }, 1] } ] },
      count:"$count"
          }
     },{"$unwind": "$result"},{ $limit : 100000 }
],{allowDiskUse:true}).toArray()

data = data.map(r=>r.result._id)
db.collectionName.deleteMany({_id:{$in:data}})
data = []
}

언급URL : https://stackoverflow.com/questions/14184099/fastest-way-to-remove-duplicate-documents-in-mongodb

반응형