Встала задача миграции sql lite жр во внешнюю базу.
Данное решение было создано:
1. для хранения жр за весь период
2. для ускорения работы с жр
3. для ускорения сервера предприятия, так как именно он (а точнее рагент) пытается записать данные в жр sql lite(фактически файл на диске), после увеличения размера файла более 10 гб, поступали жалобы по вопросу быстродействия 1с (и не только ради этого)
Данная публикация может быть полезной администраторам, программистам, оптимизаторам.
Компоненты, технологии:
1. вин служба(vb net) — читает данные из sql lite, вставляет в базу ms sql, удаляет записи из sql lite
2. web app back end(python) — многопоточная обработка запросов front end, выполняет запросы, отдает json на front
3. web app front end(js) — форма для пользовательской работы
4. nginx
5. 1c для чтения истории изменения объекта из 1с
6. ms sql server
За основу миграции данных из sql lite был взят проект(сами не стали писать, погуглили, нашли, посмотрели — вроде подходит):
https://github.com/alekseybochkov/EventLogLoader/tree/master/EventLogLoaderManager. Алексей(автор проекта) проделал хорошую работу, спасибо ему огромное, для законченной функциональности мне не хватало:
1. очистка sql lite после чтения
2. ссылка на 1с в формате 1с предприятия
Данные пункты запилил. Компонент миграции данных из sql lite в сиквел готов. Все работает, все ок.
Доработка базы(ms sql)+win служба:
Добавим вьюху, чтобы с нее читать с но лок:
SELECT dbo.Events.DateTime, dbo.Events.DataString, dbo.Events.DataStructure, dbo.Events.Comment, dbo.Computers.Name AS ComputerName,
dbo.Users.Name AS UsersName, dbo.Events.MetadataID, dbo.Metadata.Name AS MetadataName, dbo.Infobases.Name AS InfobasesName, dbo.Events.UserName,
dbo.Events.ref_ones, dbo.Events.InfobaseCode,
CASE WHEN dbo.EventsType.Name = '_$Session$_.Start' THEN 'Сеанс. Начало' WHEN dbo.EventsType.Name = '_$Session$_.Finish' THEN 'Сеанс. Завершение' WHEN
dbo.EventsType.Name = '_$InfoBase$_.ConfigUpdate' THEN 'Информационная база. Изменение конфигурации' WHEN dbo.EventsType.Name = '_$InfoBase$_.DBConfigUpdate'
THEN 'Информационная база. Изменение конфигурации базы данных' WHEN dbo.EventsType.Name = '_$InfoBase$_.EventLogSettingsUpdate' THEN 'Информационная база. Изменение параметров журнала регистрации'
WHEN dbo.EventsType.Name = '_$InfoBase$_.InfoBaseAdmParamsUpdate' THEN 'Информационная база. Изменение параметров информационной базы' WHEN
dbo.EventsType.Name = '_$InfoBase$_.MasterNodeUpdate' THEN 'Информационная база. Изменение главного узла' WHEN dbo.EventsType.Name = '_$InfoBase$_.RegionalSettingsUpdate'
THEN 'Информационная база. Изменение региональных установок' WHEN dbo.EventsType.Name = '_$InfoBase$_.TARInfo' THEN 'Тестирование и исправление. Сообщение'
WHEN dbo.EventsType.Name = '_$InfoBase$_.TARMess' THEN 'Тестирование и исправление. Предупреждение' WHEN dbo.EventsType.Name = '_$InfoBase$_.TARImportant'
THEN 'Тестирование и исправление. Ошибка' WHEN dbo.EventsType.Name = '_$Data$_.New' THEN 'Данные. Добавление' WHEN dbo.EventsType.Name = '_$Data$_.Update'
THEN 'Данные. Изменение' WHEN dbo.EventsType.Name = '_$Data$_.Delete' THEN 'Данные. Удаление' WHEN dbo.EventsType.Name = '_$Data$_.TotalsPeriodUpdate'
THEN 'Данные. Изменение периода рассчитанных итогов' WHEN dbo.EventsType.Name = '_$Data$_.Post' THEN 'Данные. Проведение' WHEN dbo.EventsType.Name
= '_$Data$_.Unpost' THEN 'Данные. Отмена проведения' WHEN dbo.EventsType.Name = '_$User$_.New' THEN 'Пользователи. Добавление' WHEN dbo.EventsType.Name
= '_$User$_.Update' THEN 'Пользователи. Изменение' WHEN dbo.EventsType.Name = '_$User$_.Delete' THEN 'Пользователи. Удаление' WHEN dbo.EventsType.Name
= '_$Job$_.Start' THEN 'Фоновое задание. Запуск' WHEN dbo.EventsType.Name = '_$Job$_.Succeed' THEN 'Фоновое задание. Успешное завершение' WHEN dbo.EventsType.Name
= '_$Job$_.Fail' THEN 'Фоновое задание. Ошибка выполнения' WHEN dbo.EventsType.Name = '_$Job$_.Cancel' THEN 'Фоновое задание. Отмена' WHEN dbo.EventsType.Name
= '_$PerformError$_' THEN 'Ошибка выполнения' WHEN dbo.EventsType.Name = '_$Transaction$_.Begin' THEN 'Транзакция. Начало' WHEN dbo.EventsType.Name
= '_$Transaction$_.Commit' THEN 'Транзакция. Фиксация' WHEN dbo.EventsType.Name = '_$Transaction$_.Rollback' THEN 'Транзакция. Отмена' ELSE dbo.EventsType.Name
END AS EventTypeName, dbo.Applications.Name AS ApplicationName, dbo.Events.EventType, dbo.Events.EventID, dbo.Events.TransactionStatus
FROM dbo.Events WITH (NOLOCK) LEFT OUTER JOIN
dbo.EventsType ON dbo.Events.EventID = dbo.EventsType.Code AND dbo.Events.InfobaseCode = dbo.EventsType.InfobaseCode LEFT OUTER JOIN
dbo.Applications ON dbo.Events.InfobaseCode = dbo.Applications.InfobaseCode AND dbo.Events.AppName = dbo.Applications.Code LEFT OUTER JOIN
dbo.Metadata ON dbo.Events.InfobaseCode = dbo.Metadata.InfobaseCode AND dbo.Events.MetadataID = dbo.Metadata.Code LEFT OUTER JOIN
dbo.Users ON dbo.Events.InfobaseCode = dbo.Users.InfobaseCode AND dbo.Events.UserName = dbo.Users.Code LEFT OUTER JOIN
dbo.Computers ON dbo.Events.ComputerName = dbo.Computers.Code AND dbo.Events.ComputerName = dbo.Computers.Code AND
dbo.Events.InfobaseCode = dbo.Computers.InfobaseCode LEFT OUTER JOIN
dbo.Infobases ON dbo.Events.InfobaseCode = dbo.Infobases.Code
Доработка EventLogProcessor.vb:
Sub LoadEvents83(FileName As String)
...
Command.CommandText = "SELECT
[rowID],
[severity],
[date],
[connectID],
[session],
[transactionStatus],
[transactionDate],
[transactionID],
[userCode],
[computerCode],
[appCode],
[eventCode],
[comment],
[metadataCodes],
[sessionDataSplitCode],
[dataType],
[data],
[dataPresentation],
[workServerCode],
[primaryPortCode],
[secondaryPortCode],
CASE
WHEN instr(data, ':')>0
THEN
substr(data, 25 + instr(data, ':'), 8) || '-' ||
substr(data, 21 + instr(data, ':'), 4) || '-' ||
substr(data, 17 + instr(data, ':'), 4) || '-' ||
substr(data, 1 + instr(data, ':'), 4) || '-' ||
substr(data, 5 + instr(data, ':'), 12)
ELSE
''
END as ref_ones
FROM [EventLog]
WHERE [rowID] > @LastEventNumber83 AND [date] >= @MinimumDate
ORDER BY 1
LIMIT 100000"
Command.Parameters.AddWithValue("LastEventNumber83", LastEventNumber83)
Dim noOfSeconds As Int64 = (LoadEventsStartingAt - New Date).TotalSeconds
Command.Parameters.AddWithValue("MinimumDate", noOfSeconds * 10000)
....
Command_DELETE.CommandText = "PRAGMA journal_mode = TRUNCATE; DELETE From EventLog WHERE rowID < @LastEventNumber83;"
Command_DELETE.Parameters.AddWithValue("LastEventNumber83", LastEventNumber83)
Try
Command_DELETE_Count = Command_DELETE.ExecuteNonQuery()
Log.Info("Try delete(EventLog) success: " + Command_DELETE_Count.ToString + " LastEventNumber83:" + LastEventNumber83.ToString)
Catch ex As Exception
Log.Error(ex, "Try delete failed")
End Try
Command_DELETE.CommandText = "PRAGMA journal_mode = TRUNCATE; DELETE From EventLogMetadata WHERE eventLogID < @LastEventNumber83;"
Command_DELETE.Parameters.AddWithValue("LastEventNumber83", LastEventNumber83)
...
web app.
Итак, backend python:
1.api_log.py(основной файл)
from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
import threading
import json
from urllib.parse import urlparse, parse_qs
import socket
import api_log_func
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
path = urlparse(self.path).path
qs = urlparse(self.path).query
qs = parse_qs(qs)
res = api_log_func.callback(path, qs, self)
resp = json.dumps(res, default=api_log_func.json_serial).encode()
self.send_response(200)
self.send_header("Content-type", "application/json")
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Expose-Headers", "Access-Control-Allow-Origin")
self.send_header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
self.end_headers()
self.wfile.write(resp)
return
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
"""Handle requests in a separate thread."""
if __name__ == '__main__':
server = ThreadedHTTPServer(('0.0.0.0', 8010), Handler)
print('Starting server, use <Ctrl-C> to stop')
server.serve_forever()
слушает порт, выполняет методы, возвращает json
2. api_log_func
#import peewee
#import json
from datetime import datetime
from datetime import timedelta
#import os
#import orm.fields
#import gevent
#import threading
## from call import Call # TODO loopback!
#import inspect
## import asyncio
import pymssql, base64, sys, json, time
def get_mssql_conn():
conn = pymssql.connect(server='', user='sa', password='', database='ExLogBook2')
cursor = conn.cursor()
return cursor
def api_log_get_meta_data(qs):
#res = (OrgCRM
# .select(OrgCRM.ref, OrgCRM.name)
# .where(OrgCRM.mark == False)
# .where(OrgCRM.show == True))
#res = res.dicts()
#return list(res)
d = []
cursor = get_mssql_conn()
cursor.execute('SELECT [Code],[Name] FROM [ExLogBook2].[dbo].[Metadata] WHERE [InfobaseCode]=1;')
row = cursor.fetchone()
while row:
d.append({'code':row[0],'name':row[1]})
row = cursor.fetchone()
#return sorted(d, key=lambda e: e['name'])
return d
def api_log_get_event_objs(qs):
#in:
#metadata_id
#data_string
d = []
query_text = 'SELECT DISTINCT [DataString], [DataStructure], [ref_ones] FROM [ExLogBook2].[dbo].[v_Events] WHERE [InfobaseCode]=1 %data_string% %metadata_id% %date_start% %date_end%'
if qs.get('data_string'):
query_text = query_text.replace('%data_string%', ' AND [DataString] LIKE '%'+qs.get('data_string')[0]+'%'')
else:
query_text = query_text.replace('%data_string%', '')
if qs.get('metadata_id'):
query_text = query_text.replace('%metadata_id%', ' AND [MetadataId] = '+qs.get('metadata_id')[0])
else:
query_text = query_text.replace('%metadata_id%', '')
if qs.get('date_start'):
query_text = query_text.replace('%date_start%', ' AND [DateTime] >= CONVERT(DATETIME, ''+qs.get('date_start')[0]+' 00:00', 103)')
else:
query_text = query_text.replace('%date_start%', '')
if qs.get('date_end'):
query_text = query_text.replace('%date_end%', ' AND [DateTime] <= CONVERT(DATETIME, ''+qs.get('date_end')[0]+' 23:59', 103)')
else:
query_text = query_text.replace('%date_end%', '')
cursor = get_mssql_conn()
cursor.execute(query_text)
row = cursor.fetchone()
while row:
d.append(
{
#'date_time':'',
#'comment':row[1],
'data_string':row[0],
'data_structure':row[1],
#'user_name':row[4],
#'computer_name':row[5]
'ref_ones':row[2]
}
)
row = cursor.fetchone()
return sorted(d, key=lambda e: e['data_string'])
def api_log_get_results(qs):
#in:
#data_structure
#data_string
##data_string
d = []
query_text = 'SELECT [DateTime], [Comment], [DataString], [DataStructure], [UsersName], [ComputerName], [MetadataName], [EventTypeName], [ApplicationName], [EventType], [EventID], [TransactionStatus] FROM [ExLogBook2].[dbo].[v_Events] WHERE [InfobaseCode]=1 %data_structure% %data_string% %metadata_id% %ref_ones% %date_start% %date_end%'
if qs.get('data_string'):
query_text = query_text.replace('%data_string%', ' AND [DataString] LIKE '%'+qs.get('data_string')[0]+'%'')
else:
query_text = query_text.replace('%data_string%', '')
if qs.get('data_structure'):
query_text = query_text.replace('%data_structure%', ' AND [DataStructure] = '+qs.get('data_structure')[0])
else:
query_text = query_text.replace('%data_structure%', '')
if qs.get('metadata_id'):
query_text = query_text.replace('%metadata_id%', ' AND [MetadataId] = '+qs.get('metadata_id')[0])
else:
query_text = query_text.replace('%metadata_id%', '')
if qs.get('ref_ones'):
query_text = query_text.replace('%ref_ones%', ' AND [ref_ones] = ''+qs.get('ref_ones')[0]+''')
else:
query_text = query_text.replace('%ref_ones%', '')
if qs.get('date_start'):
query_text = query_text.replace('%date_start%', ' AND [DateTime] >= CONVERT(DATETIME, ''+qs.get('date_start')[0]+' 00:00', 103)')
else:
query_text = query_text.replace('%date_start%', '')
if qs.get('date_end'):
query_text = query_text.replace('%date_end%', ' AND [DateTime] <= CONVERT(DATETIME, ''+qs.get('date_end')[0]+' 23:59', 103)')
else:
query_text = query_text.replace('%date_end%', '')
cursor = get_mssql_conn()
cursor.execute(query_text)
row = cursor.fetchone()
while row:
d.append(
{
'date_time':row[0],
'comment':row[1],
'data_string':row[2],
'data_structure':row[3],
'user_name':row[4],
'computer_name':row[5],
'metadata_name':row[6],
'event_type_name':row[7],
'application_name':row[8],
'event_type':row[9],
'event_id':row[10],
'transaction_status':row[11]
}
)
row = cursor.fetchone()
return sorted(d, key=lambda e: e['date_time'])
def callback(path, qs, self):
res = eval('%s(qs)' % path[1:])
return res
def api_log_get_result_mod_zn(qs):
#in:
#data_structure
#data_string
##data_string
d = []
query_text = 'SELECT DISTINCT DataString, UsersName, mod_post_doc, ref_ones, var_1, var_2, var_3, var_4 FROM (SELECT [DateTime], [Comment], [DataString], [DataStructure], [UsersName], [ComputerName], [MetadataName], [EventTypeName], [ApplicationName], [EventType], [EventID], [TransactionStatus], CASE WHEN [Comment] like '%'+'Статус проведения документа до изменения:Да'+'%' THEN 1 ELSE 0 END AS mod_post_doc, [ref_ones], CASE WHEN [Comment] like '%<Контрагент>:%' AND [Comment] like '%<Автомобиль>:%'THEN 1 ELSE 0 END AS var_1, CASE WHEN [Comment] like '%<Автомобиль>:%'THEN 1 ELSE 0 END AS var_2, CASE WHEN [Comment] like '%<Контрагент>:%'THEN 1 ELSE 0 END AS var_3, CASE WHEN [Comment] like '%Таблица товаров до записи:%' THEN 1 ELSE 0 END AS var_4 FROM [ExLogBook2].[dbo].[v_Events] WHERE [InfobaseCode]=1 and ([Comment] like '%'+'Измененнные реквизиты шапки до записи:'+'%' or [Comment] like '%+'+'Таблица товаров до записи:'+ '%' or [Comment] like '%'+'Статус проведения документа до изменения:%') %metadata_id% %date_start% %date_end% ) AS T1 WHERE (T1.mod_post_doc=0 and (T1.var_1=1 or T1.var_2=1 or T1.var_3=1)) or (T1.mod_post_doc=1 and T1.var_4=1) order by var_1 desc, var_2 desc, var_3 desc, var_4 desc '
query_text = query_text.replace('%metadata_id%', ' AND [MetadataId] = 12')
if qs.get('date_start'):
query_text = query_text.replace('%date_start%', ' AND [DateTime] >= CONVERT(DATETIME, ''+qs.get('date_start')[0]+' 00:00', 103)')
else:
query_text = query_text.replace('%date_start%', '')
if qs.get('date_end'):
query_text = query_text.replace('%date_end%', ' AND [DateTime] <= CONVERT(DATETIME, ''+qs.get('date_end')[0]+' 23:59', 103)')
else:
query_text = query_text.replace('%date_end%', '')
cursor = get_mssql_conn()
cursor.execute(query_text)
row = cursor.fetchone()
while row:
d.append(
{
#'date_time':row[0],
#'comment':row[1],
'data_string':row[0],
#'data_structure':row[3],
'user_name':row[1],
#'computer_name':row[5],
#'metadata_name':row[6],
#'event_type_name':row[7],
#'application_name':row[8],
#'event_type':row[9],
#'event_id':row[10],
#'transaction_status':row[11],
'mod_post_doc':row[2],
'ref_ones':row[3],
'var_1':row[4],
'var_2':row[5],
'var_3':row[6],
'var_4':row[7]
}
)
row = cursor.fetchone()
return d
def json_serial(obj):
if isinstance(obj, (datetime, datetime.date)):
return obj.isoformat()
raise TypeError("Type is not serializable %s" % type(obj))
выполняет запросы к бд. (про сиквел инъекции я в курсе, про то, что параметры в запрос нужно устанавливать по-другому, я тоже в курсе, и про качество кода тоже в курсе)
Общая логика работы:
1. слушать порт
2. выполнить запрос
3. преобразовать в json
4. отдать по запросу
Фронт енд
Фронт енд запиливал коллега. Ничего сказать не могу. Выглядит так:
web app готово.
Общий вид работы:
Пример получения данных из 1С:
Процедура ЗаполнитьДаннымиИзВнешнегоЖурналаРегистрации(ЖурналРегистрации, ОбъектСсылка)
ИдОбъекта = Строка(ОбъектСсылка.УникальныйИдентификатор());
Если Не ЗначениеЗаполнено(ИдОбъекта) Тогда
Возврат;
КонецЕсли;
HTTPСоединение = HTTPСоединение();
Результат = Неопределено;
ИмяФайлаОтвета = ПолучитьИмяВременногоФайла();
Адрес = "/api_log_get_results?ref_ones="+ИдОбъекта;
HTTPЗапрос = Новый HTTPЗапрос(Адрес);
HTTPОтвет = HTTPСоединение.Получить(HTTPЗапрос, ИмяФайлаОтвета);
Если HTTPОтвет.КодСостояния=200 Тогда
Результат = ПрочитатьФайлОтвета(ИмяФайлаОтвета);
КонецЕсли;
УдалитьФайлы(ИмяФайлаОтвета);
HTTPОтвет = Неопределено;
HTTPСоединение = Неопределено;
Если Не Результат=Неопределено Тогда
Для Каждого ТекСтрокаРез Из Результат.Данные Цикл
СтрТЗ = ЖурналРегистрации.Добавить();
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Результат:
Тех требования:
1С Предприятие 8.Х (жр в sql lite)
nginx
python 3.5>
ms sql server
ОС не имеет значения, мы деплоили на debian, на windows тоже вполне себе работать будет
Результат работы над поделкой превзошел все мои ожидания (в плане скорости отклика жр)
В архивах сорцы. гуд лак
сейчас придёт Люстин и будет ругаться что не в грэйлог
делов-то
(1) это что вообще?
Автор, а можете пояснить что у вас в архиве «src win ser допиленный (EventLogLoader-master)»
там очень много всего и непонятно что в итоге рабочее?
Настройки подключения я так понял в config.json?
рабочее всё, допиленное service вот дифhttps://github.com/dmarenin/EventLogLoader/commit/92334322c77765b737c982e2b 7080f41e0872567