diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2017-02-21 21:35:59 +0100 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2017-02-26 20:01:26 +0100 |
commit | 9e167724f7e933f41d9ea2e1c31772bf4c560a28 (patch) | |
tree | 093cb7c1b088f35aaf847f859a313a121c8cd233 /server/models | |
parent | 0150b17e51df3e9fad8a59133d828c68f8ba672b (diff) | |
download | PeerTube-9e167724f7e933f41d9ea2e1c31772bf4c560a28.tar.gz PeerTube-9e167724f7e933f41d9ea2e1c31772bf4c560a28.tar.zst PeerTube-9e167724f7e933f41d9ea2e1c31772bf4c560a28.zip |
Server: make a basic "quick and dirty update" for videos
This system will be useful to to update some int video attributes
(likes, dislikes, views...)
The classic system is not used because we need some optimization for
scaling
Diffstat (limited to 'server/models')
-rw-r--r-- | server/models/pod.js | 63 | ||||
-rw-r--r-- | server/models/request-to-pod.js | 4 | ||||
-rw-r--r-- | server/models/request-video-qadu.js | 154 | ||||
-rw-r--r-- | server/models/request.js | 63 | ||||
-rw-r--r-- | server/models/video.js | 13 |
5 files changed, 232 insertions, 65 deletions
diff --git a/server/models/pod.js b/server/models/pod.js index 79afb737a..14814708e 100644 --- a/server/models/pod.js +++ b/server/models/pod.js | |||
@@ -1,8 +1,11 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const each = require('async/each') | ||
3 | const map = require('lodash/map') | 4 | const map = require('lodash/map') |
5 | const waterfall = require('async/waterfall') | ||
4 | 6 | ||
5 | const constants = require('../initializers/constants') | 7 | const constants = require('../initializers/constants') |
8 | const logger = require('../helpers/logger') | ||
6 | const customPodsValidators = require('../helpers/custom-validators').pods | 9 | const customPodsValidators = require('../helpers/custom-validators').pods |
7 | 10 | ||
8 | // --------------------------------------------------------------------------- | 11 | // --------------------------------------------------------------------------- |
@@ -62,6 +65,7 @@ module.exports = function (sequelize, DataTypes) { | |||
62 | listBadPods, | 65 | listBadPods, |
63 | load, | 66 | load, |
64 | loadByHost, | 67 | loadByHost, |
68 | updatePodsScore, | ||
65 | removeAll | 69 | removeAll |
66 | }, | 70 | }, |
67 | instanceMethods: { | 71 | instanceMethods: { |
@@ -144,7 +148,7 @@ function listAllIds (transaction, callback) { | |||
144 | }) | 148 | }) |
145 | } | 149 | } |
146 | 150 | ||
147 | function listRandomPodIdsWithRequest (limit, callback) { | 151 | function listRandomPodIdsWithRequest (limit, tableRequestPod, callback) { |
148 | const self = this | 152 | const self = this |
149 | 153 | ||
150 | self.count().asCallback(function (err, count) { | 154 | self.count().asCallback(function (err, count) { |
@@ -166,7 +170,7 @@ function listRandomPodIdsWithRequest (limit, callback) { | |||
166 | where: { | 170 | where: { |
167 | id: { | 171 | id: { |
168 | $in: [ | 172 | $in: [ |
169 | this.sequelize.literal('SELECT "podId" FROM "RequestToPods"') | 173 | this.sequelize.literal('SELECT "podId" FROM "' + tableRequestPod + '"') |
170 | ] | 174 | ] |
171 | } | 175 | } |
172 | } | 176 | } |
@@ -207,3 +211,58 @@ function loadByHost (host, callback) { | |||
207 | function removeAll (callback) { | 211 | function removeAll (callback) { |
208 | return this.destroy().asCallback(callback) | 212 | return this.destroy().asCallback(callback) |
209 | } | 213 | } |
214 | |||
215 | function updatePodsScore (goodPods, badPods) { | ||
216 | const self = this | ||
217 | |||
218 | logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length) | ||
219 | |||
220 | if (goodPods.length !== 0) { | ||
221 | this.incrementScores(goodPods, constants.PODS_SCORE.BONUS, function (err) { | ||
222 | if (err) logger.error('Cannot increment scores of good pods.', { error: err }) | ||
223 | }) | ||
224 | } | ||
225 | |||
226 | if (badPods.length !== 0) { | ||
227 | this.incrementScores(badPods, constants.PODS_SCORE.MALUS, function (err) { | ||
228 | if (err) logger.error('Cannot decrement scores of bad pods.', { error: err }) | ||
229 | removeBadPods.call(self) | ||
230 | }) | ||
231 | } | ||
232 | } | ||
233 | |||
234 | // --------------------------------------------------------------------------- | ||
235 | |||
236 | // Remove pods with a score of 0 (too many requests where they were unreachable) | ||
237 | function removeBadPods () { | ||
238 | const self = this | ||
239 | |||
240 | waterfall([ | ||
241 | function findBadPods (callback) { | ||
242 | self.sequelize.models.Pod.listBadPods(function (err, pods) { | ||
243 | if (err) { | ||
244 | logger.error('Cannot find bad pods.', { error: err }) | ||
245 | return callback(err) | ||
246 | } | ||
247 | |||
248 | return callback(null, pods) | ||
249 | }) | ||
250 | }, | ||
251 | |||
252 | function removeTheseBadPods (pods, callback) { | ||
253 | each(pods, function (pod, callbackEach) { | ||
254 | pod.destroy().asCallback(callbackEach) | ||
255 | }, function (err) { | ||
256 | return callback(err, pods.length) | ||
257 | }) | ||
258 | } | ||
259 | ], function (err, numberOfPodsRemoved) { | ||
260 | if (err) { | ||
261 | logger.error('Cannot remove bad pods.', { error: err }) | ||
262 | } else if (numberOfPodsRemoved) { | ||
263 | logger.info('Removed %d pods.', numberOfPodsRemoved) | ||
264 | } else { | ||
265 | logger.info('No need to remove bad pods.') | ||
266 | } | ||
267 | }) | ||
268 | } | ||
diff --git a/server/models/request-to-pod.js b/server/models/request-to-pod.js index f42a53458..0e01a842e 100644 --- a/server/models/request-to-pod.js +++ b/server/models/request-to-pod.js | |||
@@ -17,7 +17,7 @@ module.exports = function (sequelize, DataTypes) { | |||
17 | } | 17 | } |
18 | ], | 18 | ], |
19 | classMethods: { | 19 | classMethods: { |
20 | removePodOf | 20 | removeByRequestIdsAndPod |
21 | } | 21 | } |
22 | }) | 22 | }) |
23 | 23 | ||
@@ -26,7 +26,7 @@ module.exports = function (sequelize, DataTypes) { | |||
26 | 26 | ||
27 | // --------------------------------------------------------------------------- | 27 | // --------------------------------------------------------------------------- |
28 | 28 | ||
29 | function removePodOf (requestsIds, podId, callback) { | 29 | function removeByRequestIdsAndPod (requestsIds, podId, callback) { |
30 | if (!callback) callback = function () {} | 30 | if (!callback) callback = function () {} |
31 | 31 | ||
32 | const query = { | 32 | const query = { |
diff --git a/server/models/request-video-qadu.js b/server/models/request-video-qadu.js new file mode 100644 index 000000000..7010fc992 --- /dev/null +++ b/server/models/request-video-qadu.js | |||
@@ -0,0 +1,154 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | /* | ||
4 | Request Video for Quick And Dirty Updates like: | ||
5 | - views | ||
6 | - likes | ||
7 | - dislikes | ||
8 | |||
9 | We can't put it in the same system than basic requests for efficiency. | ||
10 | Moreover we don't want to slow down the basic requests with a lot of views/likes/dislikes requests. | ||
11 | So we put it an independant request scheduler. | ||
12 | */ | ||
13 | |||
14 | const values = require('lodash/values') | ||
15 | |||
16 | const constants = require('../initializers/constants') | ||
17 | |||
18 | // --------------------------------------------------------------------------- | ||
19 | |||
20 | module.exports = function (sequelize, DataTypes) { | ||
21 | const RequestVideoQadu = sequelize.define('RequestVideoQadu', | ||
22 | { | ||
23 | type: { | ||
24 | type: DataTypes.ENUM(values(constants.REQUEST_VIDEO_QADU_TYPES)), | ||
25 | allowNull: false | ||
26 | } | ||
27 | }, | ||
28 | { | ||
29 | timestamps: false, | ||
30 | indexes: [ | ||
31 | { | ||
32 | fields: [ 'podId' ] | ||
33 | }, | ||
34 | { | ||
35 | fields: [ 'videoId' ] | ||
36 | } | ||
37 | ], | ||
38 | classMethods: { | ||
39 | associate, | ||
40 | |||
41 | listWithLimitAndRandom, | ||
42 | |||
43 | countTotalRequests, | ||
44 | removeAll, | ||
45 | removeByRequestIdsAndPod | ||
46 | } | ||
47 | } | ||
48 | ) | ||
49 | |||
50 | return RequestVideoQadu | ||
51 | } | ||
52 | |||
53 | // ------------------------------ STATICS ------------------------------ | ||
54 | |||
55 | function associate (models) { | ||
56 | this.belongsTo(models.Pod, { | ||
57 | foreignKey: { | ||
58 | name: 'podId', | ||
59 | allowNull: false | ||
60 | }, | ||
61 | onDelete: 'CASCADE' | ||
62 | }) | ||
63 | |||
64 | this.belongsTo(models.Video, { | ||
65 | foreignKey: { | ||
66 | name: 'videoId', | ||
67 | allowNull: false | ||
68 | }, | ||
69 | onDelete: 'CASCADE' | ||
70 | }) | ||
71 | } | ||
72 | |||
73 | function countTotalRequests (callback) { | ||
74 | const query = { | ||
75 | include: [ this.sequelize.models.Pod ] | ||
76 | } | ||
77 | |||
78 | return this.count(query).asCallback(callback) | ||
79 | } | ||
80 | |||
81 | function listWithLimitAndRandom (limitPods, limitRequestsPerPod, callback) { | ||
82 | const self = this | ||
83 | const Pod = this.sequelize.models.Pod | ||
84 | |||
85 | Pod.listRandomPodIdsWithRequest(limitPods, 'RequestVideoQadus', function (err, podIds) { | ||
86 | if (err) return callback(err) | ||
87 | |||
88 | // We don't have friends that have requests | ||
89 | if (podIds.length === 0) return callback(null, []) | ||
90 | |||
91 | const query = { | ||
92 | include: [ | ||
93 | { | ||
94 | model: self.sequelize.models.Pod, | ||
95 | where: { | ||
96 | id: { | ||
97 | $in: podIds | ||
98 | } | ||
99 | } | ||
100 | }, | ||
101 | { | ||
102 | model: self.sequelize.models.Video | ||
103 | } | ||
104 | ] | ||
105 | } | ||
106 | |||
107 | self.findAll(query).asCallback(function (err, requests) { | ||
108 | if (err) return callback(err) | ||
109 | |||
110 | const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod) | ||
111 | return callback(err, requestsGrouped) | ||
112 | }) | ||
113 | }) | ||
114 | } | ||
115 | |||
116 | function removeByRequestIdsAndPod (ids, podId, callback) { | ||
117 | const query = { | ||
118 | where: { | ||
119 | id: { | ||
120 | $in: ids | ||
121 | }, | ||
122 | podId | ||
123 | } | ||
124 | } | ||
125 | |||
126 | this.destroy(query).asCallback(callback) | ||
127 | } | ||
128 | |||
129 | function removeAll (callback) { | ||
130 | // Delete all requests | ||
131 | this.truncate({ cascade: true }).asCallback(callback) | ||
132 | } | ||
133 | |||
134 | // --------------------------------------------------------------------------- | ||
135 | |||
136 | function groupAndTruncateRequests (requests, limitRequestsPerPod) { | ||
137 | const requestsGrouped = {} | ||
138 | |||
139 | requests.forEach(function (request) { | ||
140 | const pod = request.Pod | ||
141 | |||
142 | if (!requestsGrouped[pod.id]) requestsGrouped[pod.id] = [] | ||
143 | |||
144 | if (requestsGrouped[pod.id].length < limitRequestsPerPod) { | ||
145 | requestsGrouped[pod.id].push({ | ||
146 | request: request, | ||
147 | video: request.Video, | ||
148 | pod | ||
149 | }) | ||
150 | } | ||
151 | }) | ||
152 | |||
153 | return requestsGrouped | ||
154 | } | ||
diff --git a/server/models/request.js b/server/models/request.js index ca616d130..de73501fc 100644 --- a/server/models/request.js +++ b/server/models/request.js | |||
@@ -1,11 +1,8 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const each = require('async/each') | ||
4 | const waterfall = require('async/waterfall') | ||
5 | const values = require('lodash/values') | 3 | const values = require('lodash/values') |
6 | 4 | ||
7 | const constants = require('../initializers/constants') | 5 | const constants = require('../initializers/constants') |
8 | const logger = require('../helpers/logger') | ||
9 | 6 | ||
10 | // --------------------------------------------------------------------------- | 7 | // --------------------------------------------------------------------------- |
11 | 8 | ||
@@ -28,8 +25,6 @@ module.exports = function (sequelize, DataTypes) { | |||
28 | listWithLimitAndRandom, | 25 | listWithLimitAndRandom, |
29 | 26 | ||
30 | countTotalRequests, | 27 | countTotalRequests, |
31 | removeBadPods, | ||
32 | updatePodsScore, | ||
33 | removeAll, | 28 | removeAll, |
34 | removeWithEmptyTo | 29 | removeWithEmptyTo |
35 | } | 30 | } |
@@ -60,71 +55,17 @@ function countTotalRequests (callback) { | |||
60 | return this.count(query).asCallback(callback) | 55 | return this.count(query).asCallback(callback) |
61 | } | 56 | } |
62 | 57 | ||
63 | // Remove pods with a score of 0 (too many requests where they were unreachable) | ||
64 | function removeBadPods () { | ||
65 | const self = this | ||
66 | |||
67 | waterfall([ | ||
68 | function findBadPods (callback) { | ||
69 | self.sequelize.models.Pod.listBadPods(function (err, pods) { | ||
70 | if (err) { | ||
71 | logger.error('Cannot find bad pods.', { error: err }) | ||
72 | return callback(err) | ||
73 | } | ||
74 | |||
75 | return callback(null, pods) | ||
76 | }) | ||
77 | }, | ||
78 | |||
79 | function removeTheseBadPods (pods, callback) { | ||
80 | each(pods, function (pod, callbackEach) { | ||
81 | pod.destroy().asCallback(callbackEach) | ||
82 | }, function (err) { | ||
83 | return callback(err, pods.length) | ||
84 | }) | ||
85 | } | ||
86 | ], function (err, numberOfPodsRemoved) { | ||
87 | if (err) { | ||
88 | logger.error('Cannot remove bad pods.', { error: err }) | ||
89 | } else if (numberOfPodsRemoved) { | ||
90 | logger.info('Removed %d pods.', numberOfPodsRemoved) | ||
91 | } else { | ||
92 | logger.info('No need to remove bad pods.') | ||
93 | } | ||
94 | }) | ||
95 | } | ||
96 | |||
97 | function updatePodsScore (goodPods, badPods) { | ||
98 | const self = this | ||
99 | const Pod = this.sequelize.models.Pod | ||
100 | |||
101 | logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length) | ||
102 | |||
103 | if (goodPods.length !== 0) { | ||
104 | Pod.incrementScores(goodPods, constants.PODS_SCORE.BONUS, function (err) { | ||
105 | if (err) logger.error('Cannot increment scores of good pods.', { error: err }) | ||
106 | }) | ||
107 | } | ||
108 | |||
109 | if (badPods.length !== 0) { | ||
110 | Pod.incrementScores(badPods, constants.PODS_SCORE.MALUS, function (err) { | ||
111 | if (err) logger.error('Cannot decrement scores of bad pods.', { error: err }) | ||
112 | removeBadPods.call(self) | ||
113 | }) | ||
114 | } | ||
115 | } | ||
116 | |||
117 | function listWithLimitAndRandom (limitPods, limitRequestsPerPod, callback) { | 58 | function listWithLimitAndRandom (limitPods, limitRequestsPerPod, callback) { |
118 | const self = this | 59 | const self = this |
119 | const Pod = this.sequelize.models.Pod | 60 | const Pod = this.sequelize.models.Pod |
120 | 61 | ||
121 | Pod.listRandomPodIdsWithRequest(limitPods, function (err, podIds) { | 62 | Pod.listRandomPodIdsWithRequest(limitPods, 'RequestToPods', function (err, podIds) { |
122 | if (err) return callback(err) | 63 | if (err) return callback(err) |
123 | 64 | ||
124 | // We don't have friends that have requests | 65 | // We don't have friends that have requests |
125 | if (podIds.length === 0) return callback(null, []) | 66 | if (podIds.length === 0) return callback(null, []) |
126 | 67 | ||
127 | // The the first x requests of these pods | 68 | // The first x requests of these pods |
128 | // It is very important to sort by id ASC to keep the requests order! | 69 | // It is very important to sort by id ASC to keep the requests order! |
129 | const query = { | 70 | const query = { |
130 | order: [ | 71 | order: [ |
diff --git a/server/models/video.js b/server/models/video.js index d0fd61eb4..daa273845 100644 --- a/server/models/video.js +++ b/server/models/video.js | |||
@@ -80,6 +80,15 @@ module.exports = function (sequelize, DataTypes) { | |||
80 | if (res === false) throw new Error('Video duration is not valid.') | 80 | if (res === false) throw new Error('Video duration is not valid.') |
81 | } | 81 | } |
82 | } | 82 | } |
83 | }, | ||
84 | views: { | ||
85 | type: DataTypes.INTEGER, | ||
86 | allowNull: false, | ||
87 | defaultValue: 0, | ||
88 | validate: { | ||
89 | min: 0, | ||
90 | isInt: true | ||
91 | } | ||
83 | } | 92 | } |
84 | }, | 93 | }, |
85 | { | 94 | { |
@@ -101,6 +110,9 @@ module.exports = function (sequelize, DataTypes) { | |||
101 | }, | 110 | }, |
102 | { | 111 | { |
103 | fields: [ 'infoHash' ] | 112 | fields: [ 'infoHash' ] |
113 | }, | ||
114 | { | ||
115 | fields: [ 'views' ] | ||
104 | } | 116 | } |
105 | ], | 117 | ], |
106 | classMethods: { | 118 | classMethods: { |
@@ -336,6 +348,7 @@ function toFormatedJSON () { | |||
336 | magnetUri: this.generateMagnetUri(), | 348 | magnetUri: this.generateMagnetUri(), |
337 | author: this.Author.name, | 349 | author: this.Author.name, |
338 | duration: this.duration, | 350 | duration: this.duration, |
351 | views: this.views, | ||
339 | tags: map(this.Tags, 'name'), | 352 | tags: map(this.Tags, 'name'), |
340 | thumbnailPath: pathUtils.join(constants.STATIC_PATHS.THUMBNAILS, this.getThumbnailName()), | 353 | thumbnailPath: pathUtils.join(constants.STATIC_PATHS.THUMBNAILS, this.getThumbnailName()), |
341 | createdAt: this.createdAt, | 354 | createdAt: this.createdAt, |