|
@@ -2,6 +2,7 @@ import { Writable, Transform } from 'stream';
|
|
|
import { URL } from 'url';
|
|
import { URL } from 'url';
|
|
|
|
|
|
|
|
import elasticsearch7 from '@elastic/elasticsearch7';
|
|
import elasticsearch7 from '@elastic/elasticsearch7';
|
|
|
|
|
+import elasticsearch8 from '@elastic/elasticsearch8';
|
|
|
import gc from 'expose-gc/function';
|
|
import gc from 'expose-gc/function';
|
|
|
import mongoose from 'mongoose';
|
|
import mongoose from 'mongoose';
|
|
|
import streamToPromise from 'stream-to-promise';
|
|
import streamToPromise from 'stream-to-promise';
|
|
@@ -53,7 +54,7 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
|
|
|
|
|
|
|
|
socketIoService!: any;
|
|
socketIoService!: any;
|
|
|
|
|
|
|
|
- isElasticsearchV6: boolean;
|
|
|
|
|
|
|
+ isElasticsearchV7: boolean;
|
|
|
|
|
|
|
|
isElasticsearchReindexOnBoot: boolean;
|
|
isElasticsearchReindexOnBoot: boolean;
|
|
|
|
|
|
|
@@ -74,13 +75,13 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
|
|
|
|
|
|
|
|
const elasticsearchVersion: number = this.configManager.getConfig('crowi', 'app:elasticsearchVersion');
|
|
const elasticsearchVersion: number = this.configManager.getConfig('crowi', 'app:elasticsearchVersion');
|
|
|
|
|
|
|
|
- if (elasticsearchVersion !== 6 && elasticsearchVersion !== 7) {
|
|
|
|
|
|
|
+ if (elasticsearchVersion !== 7 && elasticsearchVersion !== 8) {
|
|
|
throw new Error('Unsupported Elasticsearch version. Please specify a valid number to \'ELASTICSEARCH_VERSION\'');
|
|
throw new Error('Unsupported Elasticsearch version. Please specify a valid number to \'ELASTICSEARCH_VERSION\'');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- this.isElasticsearchV6 = false;
|
|
|
|
|
|
|
+ this.isElasticsearchV7 = elasticsearchVersion === 7;
|
|
|
|
|
|
|
|
- this.elasticsearch = elasticsearch7;
|
|
|
|
|
|
|
+ this.elasticsearch = this.isElasticsearchV7 ? elasticsearch7 : elasticsearch8;
|
|
|
this.isElasticsearchReindexOnBoot = this.configManager.getConfig('crowi', 'app:elasticsearchReindexOnBoot');
|
|
this.isElasticsearchReindexOnBoot = this.configManager.getConfig('crowi', 'app:elasticsearchReindexOnBoot');
|
|
|
this.client = null;
|
|
this.client = null;
|
|
|
|
|
|
|
@@ -128,7 +129,7 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
getType() {
|
|
getType() {
|
|
|
- return this.isElasticsearchV6 ? 'pages' : '_doc';
|
|
|
|
|
|
|
+ return this.isElasticsearchV7 ? '_doc' : undefined;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -231,8 +232,8 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
|
|
|
const tmpIndexName = `${indexName}-tmp`;
|
|
const tmpIndexName = `${indexName}-tmp`;
|
|
|
|
|
|
|
|
// check existence
|
|
// check existence
|
|
|
- const { body: isExistsMainIndex } = await client.indices.exists({ index: indexName });
|
|
|
|
|
- const { body: isExistsTmpIndex } = await client.indices.exists({ index: tmpIndexName });
|
|
|
|
|
|
|
+ const isExistsMainIndex = await client.indices.exists({ index: indexName });
|
|
|
|
|
+ const isExistsTmpIndex = await client.indices.exists({ index: tmpIndexName });
|
|
|
|
|
|
|
|
// create indices name list
|
|
// create indices name list
|
|
|
const existingIndices: string[] = [];
|
|
const existingIndices: string[] = [];
|
|
@@ -248,9 +249,10 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const { body: indicesBody } = await client.indices.stats({ index: existingIndices, metric: ['docs', 'store', 'indexing'] });
|
|
|
|
|
- const { indices } = indicesBody;
|
|
|
|
|
- const { body: aliases } = await client.indices.getAlias({ index: existingIndices });
|
|
|
|
|
|
|
+ const indicesStats = await client.indices.stats({ index: existingIndices, metric: ['docs', 'store', 'indexing'] });
|
|
|
|
|
+ const { indices } = indicesStats;
|
|
|
|
|
+
|
|
|
|
|
+ const aliases = await client.indices.getAlias({ index: existingIndices });
|
|
|
|
|
|
|
|
const isMainIndexHasAlias = isExistsMainIndex && aliases[indexName].aliases != null && aliases[indexName].aliases[aliasName] != null;
|
|
const isMainIndexHasAlias = isExistsMainIndex && aliases[indexName].aliases != null && aliases[indexName].aliases[aliasName] != null;
|
|
|
const isTmpIndexHasAlias = isExistsTmpIndex && aliases[tmpIndexName].aliases != null && aliases[tmpIndexName].aliases[aliasName] != null;
|
|
const isTmpIndexHasAlias = isExistsTmpIndex && aliases[tmpIndexName].aliases != null && aliases[tmpIndexName].aliases[aliasName] != null;
|
|
@@ -262,6 +264,7 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
|
|
|
aliases,
|
|
aliases,
|
|
|
isNormalized,
|
|
isNormalized,
|
|
|
};
|
|
};
|
|
|
|
|
+
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -272,16 +275,24 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
|
|
|
|
|
|
|
|
const tmpIndexName = `${indexName}-tmp`;
|
|
const tmpIndexName = `${indexName}-tmp`;
|
|
|
|
|
|
|
|
- try {
|
|
|
|
|
- // reindex to tmp index
|
|
|
|
|
- await this.createIndex(tmpIndexName);
|
|
|
|
|
- await client.reindex({
|
|
|
|
|
|
|
+ const reindexRequest = this.isElasticsearchV7
|
|
|
|
|
+ ? {
|
|
|
waitForCompletion: false,
|
|
waitForCompletion: false,
|
|
|
body: {
|
|
body: {
|
|
|
source: { index: indexName },
|
|
source: { index: indexName },
|
|
|
dest: { index: tmpIndexName },
|
|
dest: { index: tmpIndexName },
|
|
|
},
|
|
},
|
|
|
- });
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ : {
|
|
|
|
|
+ wait_for_completion: false,
|
|
|
|
|
+ source: { index: indexName },
|
|
|
|
|
+ dest: { index: tmpIndexName },
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // reindex to tmp index
|
|
|
|
|
+ await this.createIndex(tmpIndexName);
|
|
|
|
|
+ await client.reindex(reindexRequest);
|
|
|
|
|
|
|
|
// update alias
|
|
// update alias
|
|
|
await client.indices.updateAliases({
|
|
await client.indices.updateAliases({
|
|
@@ -322,13 +333,13 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
|
|
|
const tmpIndexName = `${indexName}-tmp`;
|
|
const tmpIndexName = `${indexName}-tmp`;
|
|
|
|
|
|
|
|
// remove tmp index
|
|
// remove tmp index
|
|
|
- const { body: isExistsTmpIndex } = await client.indices.exists({ index: tmpIndexName });
|
|
|
|
|
|
|
+ const isExistsTmpIndex = await client.indices.exists({ index: tmpIndexName });
|
|
|
if (isExistsTmpIndex) {
|
|
if (isExistsTmpIndex) {
|
|
|
await client.indices.delete({ index: tmpIndexName });
|
|
await client.indices.delete({ index: tmpIndexName });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// create index
|
|
// create index
|
|
|
- const { body: isExistsIndex } = await client.indices.exists({ index: indexName });
|
|
|
|
|
|
|
+ const isExistsIndex = await client.indices.exists({ index: indexName });
|
|
|
if (!isExistsIndex) {
|
|
if (!isExistsIndex) {
|
|
|
await this.createIndex(indexName);
|
|
await this.createIndex(indexName);
|
|
|
}
|
|
}
|
|
@@ -344,10 +355,12 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async createIndex(index) {
|
|
async createIndex(index) {
|
|
|
- let mappings = require('^/resource/search/mappings-es7.json');
|
|
|
|
|
|
|
+ let mappings = this.isElasticsearchV7
|
|
|
|
|
+ ? require('^/resource/search/mappings-es7.json')
|
|
|
|
|
+ : require('^/resource/search/mappings-es8.json');
|
|
|
|
|
|
|
|
if (process.env.CI) {
|
|
if (process.env.CI) {
|
|
|
- mappings = require('^/resource/search/mappings-es7-for-ci.json');
|
|
|
|
|
|
|
+ mappings = require('^/resource/search/mappings-es8-for-ci.json');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return this.client.indices.create({
|
|
return this.client.indices.create({
|
|
@@ -572,14 +585,14 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
|
|
|
batch.forEach(doc => prepareBodyForCreate(body, doc));
|
|
batch.forEach(doc => prepareBodyForCreate(body, doc));
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- const { body: res } = await bulkWrite({
|
|
|
|
|
|
|
+ const bulkResponse = await bulkWrite({
|
|
|
body,
|
|
body,
|
|
|
// requestTimeout: Infinity,
|
|
// requestTimeout: Infinity,
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- count += (res.items || []).length;
|
|
|
|
|
|
|
+ count += (bulkResponse.items || []).length;
|
|
|
|
|
|
|
|
- logger.info(`Adding pages progressing: (count=${count}, errors=${res.errors}, took=${res.took}ms)`);
|
|
|
|
|
|
|
+ logger.info(`Adding pages progressing: (count=${count}, errors=${bulkResponse.errors}, took=${bulkResponse.took}ms)`);
|
|
|
|
|
|
|
|
if (shouldEmitProgress) {
|
|
if (shouldEmitProgress) {
|
|
|
socket?.emit('addPageProgress', { totalCount, count, skipped });
|
|
socket?.emit('addPageProgress', { totalCount, count, skipped });
|
|
@@ -646,7 +659,7 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
|
|
|
if (process.env.NODE_ENV === 'development') {
|
|
if (process.env.NODE_ENV === 'development') {
|
|
|
logger.debug('query: ', JSON.stringify(query, null, 2));
|
|
logger.debug('query: ', JSON.stringify(query, null, 2));
|
|
|
|
|
|
|
|
- const { body: result } = await this.client.indices.validateQuery({
|
|
|
|
|
|
|
+ const validateQueryResponse = await this.client.indices.validateQuery({
|
|
|
index: query.index,
|
|
index: query.index,
|
|
|
type: query.type,
|
|
type: query.type,
|
|
|
explain: true,
|
|
explain: true,
|
|
@@ -654,21 +667,20 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
|
|
|
query: query.body.query,
|
|
query: query.body.query,
|
|
|
},
|
|
},
|
|
|
});
|
|
});
|
|
|
|
|
+
|
|
|
// for debug
|
|
// for debug
|
|
|
- logger.debug('ES result: ', result);
|
|
|
|
|
|
|
+ logger.debug('ES result: ', validateQueryResponse);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const { body: result } = await this.client.search(query);
|
|
|
|
|
-
|
|
|
|
|
- const totalValue = this.isElasticsearchV6 ? result.hits.total : result.hits.total.value;
|
|
|
|
|
|
|
+ const searchResponse = await this.client.search(query);
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
|
meta: {
|
|
meta: {
|
|
|
- took: result.took,
|
|
|
|
|
- total: totalValue,
|
|
|
|
|
- hitsCount: result.hits.hits.length,
|
|
|
|
|
|
|
+ took: searchResponse.took,
|
|
|
|
|
+ total: searchResponse.hits.total.value,
|
|
|
|
|
+ hitsCount: searchResponse.hits.hits.length,
|
|
|
},
|
|
},
|
|
|
- data: result.hits.hits.map((elm) => {
|
|
|
|
|
|
|
+ data: searchResponse.hits.hits.map((elm) => {
|
|
|
return {
|
|
return {
|
|
|
_id: elm._id,
|
|
_id: elm._id,
|
|
|
_score: elm._score,
|
|
_score: elm._score,
|