Kiến thức cần có: mongodb, nodejs.
Hay đơn giản, bạn chỉ cần tư duy backend là hiểu rồi.

1 Yêu cầu?

Khi nhiều operation chạy quá dài trên server sẽ gây lag server.
Nếu vào những đợt cao điểm mà client connect quá nhiều sẽ kéo dài thời gian này hơn.
=> Mục tiêu : Phòng chống lag server

2 Giải pháp:
-kiểm tra xem có operation nào của mongodb đang chạy mà thời gian quá lâu > 30s hoặc 40s.
-Lấy opid rồi thực hiện lệnh kill:
vd: db.adminCommand( { "killOp": 1, "op": 3478 } )

Ờ, có vẻ cũng đơn giản. Nhưng mà có chắc bạn sẽ muốn trực 24/24 vào giờ cao điểm chỉ để kill các op có currentOpTime( thời gian hoạt động) lớn không?
Ok vậy là giải pháp: Cần build 1 service riêng để thực hiện tìm ra các op có currentOpTime lớn hơn thời gian yêu cầu thì sẽ auto kill.

3 Start

Mình sẽ đưa code trước để bạn hình dung project mình có gì rồi mình sẽ giải thích nghiệp vụ.

Bắt đầu service mới với npm init
Và những dependencies mà các bạn cần có:

{
  "name": "auto-kill-operation-mongodb",
  "version": "1.0.0",
  "description": "auto-kill-operation-mongodb from chuyen muc it",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "tungdt",
  "license": "ISC",
  "dependencies": {
    "lodash": "^4.17.20",
    "moment": "^2.27.0",
    "mongoose": "^5.10.0",
    "node-schedule": "^1.3.2"
  }
}

Việc đầu tiên là cần kết nối tới server mà bạn muốn control:
Mình có file config.js trong dir config, mục đính là chứa chuỗi kết nối:

module.exports = {
    db: 'mongodb://localhost:27017/chuyenmucit-work-dev'
}

Sau đó kết nối với db: chúng ta có server.js ở thư mục gốc trong project

const schedule = require('node-schedule')
const config  = require('./config/config')
const jobMongos = require('./schedule/mongodb-kill-operation')
const mongoose = require('mongoose')
async function connectDatabase(callback) {
    try {
      const db = await mongoose.connect(config.db, {
        useNewUrlParser: true,
        useFindAndModify: true,
        useCreateIndex: true,
        reconnectTries: Number.MAX_VALUE,
        reconnectInterval: 2000,
      })
      console.log('Connected to MongoDB')
      if (callback) callback()
      return db
    } catch (err) {
      console.error('Could not connect to MongoDB!')
    }
  }

async function main() {
    await connectDatabase()
  // then do everything
}
main()

Phần import trong file server.js bạn thấy có gì đặc biệt ngoài connect mongodb chứ?
Đó là mình sử dụng đến “node-schedule” link-package.
Vì nghiệp vụ chính của service này là auto 1 công việc, nên “node-chedule” là package quan trọng trong project này.
Vậy nó làm gì trong project này:
1 – Cứ sau 10 giây, check tới server-mongodb

schedule.scheduleJob(`*/10 * * * * *`, () => {
   // do it
})

Bên trong hàm calback sẽ được gọi cứ sau 10 giây.

Mục đích là check xem có op nào có thời gian hoạt đông > 40 giây hay không. Vì thế, chúng ta có file mongodb-kill-operation.js thực hiện nhiệm vụ này:

const moment = require('moment')

module.exports = (schedule, mongooes) => {
    const db = mongooes.connection.db;
    const options = {
        active: true,
    }
    
    schedule.scheduleJob(`*/10 * * * * *`, function(){

    db.executeDbAdminCommand({

        currentOp: {options},
        "microsecs_running" : {"$gte" : 40000}
            
    }, function (err, ops) { 

            if (err) { console.log(err) } 

            if(ops) {
       
            }
        })
    });

Minh giải thích 1 chút:
– 2 đối số schedule và mongooes được truyền từ server.js sang.
– Phương thức db.executeDbAdminCommand Sẽ thực hiện câu lệnh với server mongos, trong đó trường currentOp tương đương với chúng ta đang chạy 1 command ở temmilar :db.currentOp()
db.currentOp() sẽ trả về thông tin các hoạt động đang diễn ra trong cơ sở dữ liệu ở thời điểm hiện tại, và được cung cấp vởi mongodb.
– Trường "microsecs_running" : {"$gte" : 40000} kết hợp cùng currentOp sẽ trả về các hoạt động trên server lâu hơn 40 giây.(40000ms)

Và nếu không lỗi, bạn sẽ nhận được kết quả giống như này:


{
	"inprog" : [
		{
			"type" : "op",
			"host" : "invocker-Vostro-15-3568:27017",
			"desc" : "conn5",
			"connectionId" : 5,
			"client" : "127.0.0.1:38632",
			"appName" : "MongoDB Shell",
			"clientMetadata" : {
				"application" : {
					"name" : "MongoDB Shell"
				},
				"driver" : {
					"name" : "MongoDB Internal Client",
					"version" : "4.4.0"
				},
				"os" : {
					"type" : "Linux",
					"name" : "Ubuntu",
					"architecture" : "x86_64",
					"version" : "20.04"
				}
			},
			"active" : true,
			"currentOpTime" : "2020-08-23T07:52:56.284+07:00",
			"opid" : 137073,
			"lsid" : {
				"id" : UUID("6deac838-0824-4d92-8a5d-4fc18d907387"),
				"uid" : BinData(0,"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=")
			},
			"secs_running" : NumberLong(0),
			"microsecs_running" : NumberLong(336),
			"op" : "command",
			"ns" : "admin.$cmd.aggregate",
			"command" : {
				"currentOp" : 1,
				"lsid" : {
					"id" : UUID("6deac838-0824-4d92-8a5d-4fc18d907387")
				},
				"$db" : "admin"
			},
			"numYields" : 0,
			"locks" : {
				
			},
			"waitingForLock" : false,
			"lockStats" : {
				
			},
			"waitingForFlowControl" : false,
			"flowControlStats" : {
				
			}
		}
	],
	"ok" : 1
}

Ok bạn thấy "microsecs_running" : NumberLong(336) chứ. Vì mình chạy local nên chỉ 366 milliseconds. với server thương mại, nhiều client chắc chắn con số này sẽ rất lớn.

Như vậy coi như đã tự động tìm được op(Hoạt động) trên server có time hoạt động dài. Bây giờ chỉ cần kill đúng op là hoàn thành.
Mình có full file mongodb-kill-operation.js như sau.

const moment = require('moment')
const fs = require('fs')
const path = require('path')
const _ = require('lodash')

const filePath = path.resolve('../mongo-log.log')
function appendFile(content) {
    fs.appendFileSync(filePath, content + '\n')    
}

module.exports = (schedule, mongooes) => {
    const db = mongooes.connection.db;
    const options = {
        active: true,
    }
    
    schedule.scheduleJob(`*/10 * * * * *`, function(){

    db.executeDbAdminCommand({

        currentOp: {options},
        "microsecs_running" : {"$gte" : 40000}
            
    }, function (err, ops) { 

            if (err) { console.log(err) } 

            if(ops) {
                _.forEach(ops.inprog, op => {
                    console.log("ops.in",ops.inprog)
                    if (_.get(op, 'clientMetadata.driver.name', '') === 'nodejs') {
                        killOp(op)
                        console.log("da kill")
                    }
                })
            }
        })
    });

function killOp(op) {

    db.executeDbAdminCommand( { "killOp": 1, "op": op.opid } ) 
    appendFile(`Killed ${op.opid}`)
    appendFile(moment().format())
    appendFile(JSON.stringify(op, null, '  '))
    }

}


Đoạn này: db.executeDbAdminCommand( { "killOp": 1, "op": op.opid }
sẽ kill op với opid tìm được.
– Và well, tất cả đều nằm trong hàm callback “scheduleJob” tức là cứ 10s thì kiểm tra 1 lần xem có op nào quá lâu không, nếu có thì kill đi.
:)) Ngon nhỉ,
– vậy là có 1 service tự động cho chúng ta công viêc kill các operation chạy quá lâu trên mongodb này rồi.

Ngoài ra, nếu xem kĩ file mongodb-kill-operation.js bạn sẽ thấy, mình dùng ” fs” và “path” để lưu lại log trong file mongo-log.log để tiện sau này tra cứu.
cứ mỗi khi kill thì lưu thông tin op này lại, đoạn code này ở:

function killOp(op) {

    db.executeDbAdminCommand( { "killOp": 1, "op": op.opid } ) 
// ghi vào file log
    appendFile(`Killed ${op.opid}`) 
    appendFile(moment().format())
    appendFile(JSON.stringify(op, null, '  '))
    }

}

Ok, mình tin chắc các hệ thống lớn phải làm điều này (kill các operation chạy quá lâu trên database). chỉ có làm điều làm theo cách nào thôi. Mong rằng option của mình sẽ giảm được gánh nặng trên server của các bạn.

happy codeding!