Рефакторинг
This commit is contained in:
@@ -539,7 +539,7 @@ class DbCreator {
|
|||||||
|
|
||||||
//series
|
//series
|
||||||
callback({job: 'series save', jobMessage: 'Сохранение индекса серий', jobStep: 7, progress: 0});
|
callback({job: 'series save', jobMessage: 'Сохранение индекса серий', jobStep: 7, progress: 0});
|
||||||
await saveTable('series_temporary', seriesArr, () => {seriesArr = null}, true, true);
|
await saveTable('series', seriesArr, () => {seriesArr = null}, true, true);
|
||||||
|
|
||||||
//title
|
//title
|
||||||
callback({job: 'title save', jobMessage: 'Сохранение индекса названий', jobStep: 8, progress: 0});
|
callback({job: 'title save', jobMessage: 'Сохранение индекса названий', jobStep: 8, progress: 0});
|
||||||
@@ -561,21 +561,33 @@ class DbCreator {
|
|||||||
await db.create({table: 'file_hash'});
|
await db.create({table: 'file_hash'});
|
||||||
|
|
||||||
//-- завершающие шаги --------------------------------
|
//-- завершающие шаги --------------------------------
|
||||||
//оптимизация series, превращаем массив bookId в books
|
|
||||||
callback({job: 'series optimization', jobMessage: 'Оптимизация', jobStep: 11, progress: 0});
|
|
||||||
|
|
||||||
await db.open({
|
await db.open({
|
||||||
table: 'book',
|
table: 'book',
|
||||||
cacheSize: (config.lowMemoryMode ? 5 : 500),
|
cacheSize: (config.lowMemoryMode ? 5 : 500),
|
||||||
});
|
});
|
||||||
await db.open({table: 'series_temporary'});
|
|
||||||
await db.create({
|
|
||||||
table: 'series',
|
|
||||||
index: {field: 'value', unique: true, depth: 1000000},
|
|
||||||
});
|
|
||||||
|
|
||||||
const count = await db.select({table: 'series_temporary', count: true});
|
callback({job: 'series optimization', jobMessage: 'Оптимизация', jobStep: 11, progress: 0});
|
||||||
const seriesCount = (count.length ? count[0].count : 0);
|
await this.optimizeSeries(db, callback);
|
||||||
|
|
||||||
|
callback({job: 'files count', jobMessage: 'Подсчет статистики', jobStep: 12, progress: 0});
|
||||||
|
await this.countStats(db, callback, stats);
|
||||||
|
|
||||||
|
//чистка памяти, ибо жрет как не в себя
|
||||||
|
await db.close({table: 'book'});
|
||||||
|
await db.freeMemory();
|
||||||
|
utils.freeMemory();
|
||||||
|
|
||||||
|
callback({job: 'done', jobMessage: ''});
|
||||||
|
}
|
||||||
|
|
||||||
|
async optimizeSeries(db, callback) {
|
||||||
|
//оптимизация series, превращаем массив bookId в books, кладем все в series_book
|
||||||
|
await db.open({table: 'series'});
|
||||||
|
|
||||||
|
await db.create({
|
||||||
|
table: 'series_book',
|
||||||
|
flag: {name: 'toDel', check: 'r => r.toDel'},
|
||||||
|
});
|
||||||
|
|
||||||
const saveSeriesChunk = async(seriesChunk) => {
|
const saveSeriesChunk = async(seriesChunk) => {
|
||||||
const ids = [];
|
const ids = [];
|
||||||
@@ -594,52 +606,62 @@ class DbCreator {
|
|||||||
bookArr.set(row.id, row);
|
bookArr.set(row.id, row);
|
||||||
|
|
||||||
for (const s of seriesChunk) {
|
for (const s of seriesChunk) {
|
||||||
const sBooks = [];
|
s.books = [];
|
||||||
for (const id of s.bookId) {
|
for (const id of s.bookId) {
|
||||||
const rec = bookArr.get(id);
|
const rec = bookArr.get(id);
|
||||||
sBooks.push(rec);
|
s.books.push(rec);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.books.length) {
|
||||||
|
s.series = s.books[0].value;
|
||||||
|
} else {
|
||||||
|
s.toDel = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
s.books = JSON.stringify(sBooks);
|
|
||||||
delete s.bookId;
|
delete s.bookId;
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.insert({
|
await db.insert({
|
||||||
table: 'series',
|
table: 'series_book',
|
||||||
rows: seriesChunk,
|
rows: seriesChunk,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const rows = await db.select({table: 'series_temporary'});
|
const rows = await db.select({table: 'series'});
|
||||||
|
|
||||||
idsLen = 0;
|
let idsLen = 0;
|
||||||
aChunk = [];
|
let chunk = [];
|
||||||
proc = 0;
|
let processed = 0;
|
||||||
for (const row of rows) {// eslint-disable-line
|
for (const row of rows) {// eslint-disable-line
|
||||||
aChunk.push(row);
|
chunk.push(row);
|
||||||
idsLen += row.bookId.length;
|
idsLen += row.bookId.length;
|
||||||
proc++;
|
processed++;
|
||||||
|
|
||||||
if (idsLen > 20000) {//константа выяснена эмпирическим путем "память/скорость"
|
if (idsLen > 20000) {//константа выяснена эмпирическим путем "память/скорость"
|
||||||
await saveSeriesChunk(aChunk);
|
await saveSeriesChunk(chunk);
|
||||||
|
|
||||||
idsLen = 0;
|
idsLen = 0;
|
||||||
aChunk = [];
|
chunk = [];
|
||||||
|
|
||||||
callback({progress: proc/seriesCount});
|
callback({progress: processed/rows.length});
|
||||||
|
|
||||||
await utils.sleep(100);
|
await utils.sleep(100);
|
||||||
utils.freeMemory();
|
utils.freeMemory();
|
||||||
await db.freeMemory();
|
await db.freeMemory();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (aChunk.length) {
|
if (chunk.length) {
|
||||||
await saveSeriesChunk(aChunk);
|
await saveSeriesChunk(chunk);
|
||||||
aChunk = null;
|
chunk = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await db.delete({table: 'series_book', where: `@@flag('toDel')`});
|
||||||
|
await db.close({table: 'series_book'});
|
||||||
|
await db.close({table: 'series'});
|
||||||
|
}
|
||||||
|
|
||||||
|
async countStats(db, callback, stats) {
|
||||||
//статистика по количеству файлов
|
//статистика по количеству файлов
|
||||||
callback({job: 'files count', jobMessage: 'Подсчет статистики', jobStep: 12, progress: 0});
|
|
||||||
|
|
||||||
//эмуляция прогресса
|
//эмуляция прогресса
|
||||||
let countDone = false;
|
let countDone = false;
|
||||||
@@ -674,16 +696,6 @@ class DbCreator {
|
|||||||
]});
|
]});
|
||||||
}
|
}
|
||||||
countDone = true;
|
countDone = true;
|
||||||
|
|
||||||
//чистка памяти, ибо жрет как не в себя
|
|
||||||
await db.drop({table: 'series_temporary'});//таблица больше не понадобится
|
|
||||||
|
|
||||||
await db.close({table: 'book'});
|
|
||||||
await db.close({table: 'series'});
|
|
||||||
await db.freeMemory();
|
|
||||||
utils.freeMemory();
|
|
||||||
|
|
||||||
callback({job: 'done', jobMessage: ''});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
|
|
||||||
|
const maxMemCacheSize = 100;
|
||||||
|
|
||||||
const maxUtf8Char = String.fromCodePoint(0xFFFFF);
|
const maxUtf8Char = String.fromCodePoint(0xFFFFF);
|
||||||
const ruAlphabet = 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя';
|
const ruAlphabet = 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя';
|
||||||
const enAlphabet = 'abcdefghijklmnopqrstuvwxyz';
|
const enAlphabet = 'abcdefghijklmnopqrstuvwxyz';
|
||||||
@@ -16,6 +18,11 @@ class DbSearcher {
|
|||||||
this.timer = null;
|
this.timer = null;
|
||||||
this.closed = false;
|
this.closed = false;
|
||||||
|
|
||||||
|
db.searchCache = {
|
||||||
|
memCache: new Map(),
|
||||||
|
authorIdsAll: false,
|
||||||
|
};
|
||||||
|
|
||||||
this.periodicCleanCache();//no await
|
this.periodicCleanCache();//no await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,60 +187,82 @@ class DbSearcher {
|
|||||||
return authorIds;
|
return authorIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAuthorIds(query) {
|
queryKey(q) {
|
||||||
|
return JSON.stringify([q.author, q.series, q.title, q.genre, q.lang]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCached(key) {
|
||||||
|
if (!this.config.queryCacheEnabled)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
let result = null;
|
||||||
|
|
||||||
const db = this.db;
|
const db = this.db;
|
||||||
|
const memCache = db.searchCache.memCache;
|
||||||
|
|
||||||
if (!db.searchCache)
|
if (memCache.has(key)) {//есть в недавних
|
||||||
db.searchCache = {};
|
result = memCache.get(key);
|
||||||
|
|
||||||
let result;
|
//изменим порядок ключей, для последующей правильной чистки старых
|
||||||
|
memCache.delete(key);
|
||||||
|
memCache.set(key, result);
|
||||||
|
} else {//смотрим в таблице
|
||||||
|
const rows = await db.select({table: 'query_cache', where: `@@id(${db.esc(key)})`});
|
||||||
|
|
||||||
//сначала попробуем найти в кеше
|
if (rows.length) {//нашли в кеше
|
||||||
const q = query;
|
await db.insert({
|
||||||
const keyArr = [q.author, q.series, q.title, q.genre, q.lang];
|
table: 'query_time',
|
||||||
const keyStr = `query-${keyArr.join('')}`;
|
replace: true,
|
||||||
|
rows: [{id: key, time: Date.now()}],
|
||||||
if (!keyStr) {//пустой запрос
|
});
|
||||||
if (db.searchCache.authorIdsAll)
|
|
||||||
result = db.searchCache.authorIdsAll;
|
|
||||||
else
|
|
||||||
result = await this.selectAuthorIds(query);
|
|
||||||
|
|
||||||
} else {//непустой запрос
|
result = rows[0].value;
|
||||||
if (this.config.queryCacheEnabled) {
|
memCache.set(key, result);
|
||||||
const key = JSON.stringify(keyArr);
|
|
||||||
const rows = await db.select({table: 'query_cache', where: `@@id(${db.esc(key)})`});
|
|
||||||
|
|
||||||
if (rows.length) {//нашли в кеше
|
if (memCache.size > maxMemCacheSize) {
|
||||||
await db.insert({
|
//удаляем самый старый ключ-значение
|
||||||
table: 'query_time',
|
for (const k of memCache.keys()) {
|
||||||
replace: true,
|
memCache.delete(k);
|
||||||
rows: [{id: key, time: Date.now()}],
|
break;
|
||||||
});
|
}
|
||||||
|
|
||||||
result = rows[0].value;
|
|
||||||
} else {//не нашли в кеше, ищем в поисковых таблицах
|
|
||||||
result = await this.selectAuthorIds(query);
|
|
||||||
|
|
||||||
await db.insert({
|
|
||||||
table: 'query_cache',
|
|
||||||
replace: true,
|
|
||||||
rows: [{id: key, value: result}],
|
|
||||||
});
|
|
||||||
await db.insert({
|
|
||||||
table: 'query_time',
|
|
||||||
replace: true,
|
|
||||||
rows: [{id: key, time: Date.now()}],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
result = await this.selectAuthorIds(query);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async putCached(key, value) {
|
||||||
|
if (!this.config.queryCacheEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const db = this.db;
|
||||||
|
|
||||||
|
const memCache = db.searchCache.memCache;
|
||||||
|
memCache.set(key, value);
|
||||||
|
|
||||||
|
if (memCache.size > maxMemCacheSize) {
|
||||||
|
//удаляем самый старый ключ-значение
|
||||||
|
for (const k of memCache.keys()) {
|
||||||
|
memCache.delete(k);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//кладем в таблицу
|
||||||
|
await db.insert({
|
||||||
|
table: 'query_cache',
|
||||||
|
replace: true,
|
||||||
|
rows: [{id: key, value}],
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.insert({
|
||||||
|
table: 'query_time',
|
||||||
|
replace: true,
|
||||||
|
rows: [{id: key, time: Date.now()}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async search(query) {
|
async search(query) {
|
||||||
if (this.closed)
|
if (this.closed)
|
||||||
throw new Error('DbSearcher closed');
|
throw new Error('DbSearcher closed');
|
||||||
@@ -243,7 +272,15 @@ class DbSearcher {
|
|||||||
try {
|
try {
|
||||||
const db = this.db;
|
const db = this.db;
|
||||||
|
|
||||||
const authorIds = await this.getAuthorIds(query);
|
const key = `author-ids-${this.queryKey(query)}`;
|
||||||
|
|
||||||
|
//сначала попробуем найти в кеше
|
||||||
|
let authorIds = await this.getCached(key);
|
||||||
|
if (authorIds === null) {//не нашли в кеше, ищем в поисковых таблицах
|
||||||
|
authorIds = await this.selectAuthorIds(query);
|
||||||
|
|
||||||
|
await this.putCached(key, authorIds);
|
||||||
|
}
|
||||||
|
|
||||||
const totalFound = authorIds.length;
|
const totalFound = authorIds.length;
|
||||||
let limit = (query.limit ? query.limit : 100);
|
let limit = (query.limit ? query.limit : 100);
|
||||||
@@ -251,7 +288,7 @@ class DbSearcher {
|
|||||||
const offset = (query.offset ? query.offset : 0);
|
const offset = (query.offset ? query.offset : 0);
|
||||||
|
|
||||||
//выборка найденных авторов
|
//выборка найденных авторов
|
||||||
let result = await db.select({
|
const result = await db.select({
|
||||||
table: 'author',
|
table: 'author',
|
||||||
map: `(r) => ({id: r.id, author: r.author, bookCount: r.bookCount, bookDelCount: r.bookDelCount})`,
|
map: `(r) => ({id: r.id, author: r.author, bookCount: r.bookCount, bookDelCount: r.bookDelCount})`,
|
||||||
where: `@@id(${db.esc(authorIds.slice(offset, offset + limit))})`
|
where: `@@id(${db.esc(authorIds.slice(offset, offset + limit))})`
|
||||||
@@ -272,7 +309,7 @@ class DbSearcher {
|
|||||||
try {
|
try {
|
||||||
const db = this.db;
|
const db = this.db;
|
||||||
|
|
||||||
//выборка автора по authorId
|
//выборка книг автора по authorId
|
||||||
const rows = await db.select({
|
const rows = await db.select({
|
||||||
table: 'author_book',
|
table: 'author_book',
|
||||||
where: `@@id(${db.esc(authorId)})`
|
where: `@@id(${db.esc(authorId)})`
|
||||||
@@ -302,13 +339,26 @@ class DbSearcher {
|
|||||||
const db = this.db;
|
const db = this.db;
|
||||||
|
|
||||||
series = series.toLowerCase();
|
series = series.toLowerCase();
|
||||||
|
|
||||||
//выборка серии по названию серии
|
//выборка серии по названию серии
|
||||||
const rows = await db.select({
|
let rows = await db.select({
|
||||||
table: 'series',
|
table: 'series',
|
||||||
where: `@@dirtyIndexLR('value', ${db.esc(series)}, ${db.esc(series)})`
|
where: `@@dirtyIndexLR('value', ${db.esc(series)}, ${db.esc(series)})`
|
||||||
});
|
});
|
||||||
|
|
||||||
return {books: (rows.length ? rows[0].books : '')};
|
let books = [];
|
||||||
|
if (rows.length) {
|
||||||
|
//выборка книг серии
|
||||||
|
rows = await db.select({
|
||||||
|
table: 'series_book',
|
||||||
|
where: `@@id(${rows[0].id})`
|
||||||
|
});
|
||||||
|
|
||||||
|
if (rows.length)
|
||||||
|
books = rows[0].books;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {books: (books && books.length ? JSON.stringify(books) : '')};
|
||||||
} finally {
|
} finally {
|
||||||
this.searchFlag--;
|
this.searchFlag--;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user