cs-element
Version:
Advanced reactive data management library with state machines, blueprints, persistence, compression, networking, and multithreading support
972 lines (806 loc) • 33.1 kB
Markdown
# 🚀 Batch операции и массовые операции
CSElement предоставляет мощную систему для выполнения массовых операций с оптимизацией производительности и гибким управлением выполнением.
## 📋 Содержание
1. [Основы batch операций](#основы-batch-операций)
2. [Создание и управление батчами](#создание-и-управление-батчами)
3. [Типы операций](#типы-операций)
4. [Стратегии выполнения](#стратегии-выполнения)
5. [Обработка ошибок](#обработка-ошибок)
6. [Мониторинг и прогресс](#мониторинг-и-прогресс)
7. [Оптимизация производительности](#оптимизация-производительности)
8. [Полный пример: Система импорта данных](#полный-пример-система-импорта-данных)
## Основы batch операций
Batch операции позволяют группировать множество операций и выполнять их эффективно с контролем производительности, обработкой ошибок и отслеживанием прогресса.
### Простой пример
```typescript
import { CSElement, BatchManager, BatchOperationType, BatchPriority } from 'cs-element';
// Создаем менеджер батчей
const batchManager = new BatchManager();
// Создаем новый батч
const batchId = batchManager.createBatch({
executionStrategy: 'parallel',
maxConcurrency: 5
});
// Добавляем операции
const operations = [
{
id: 'create-user-1',
type: BatchOperationType.CREATE,
priority: BatchPriority.NORMAL,
execute: async () => {
const user = new CSElement('user-1');
await user.setData('name', 'Алексей');
await user.setData('email', 'alexey@example.com');
return user;
}
},
{
id: 'create-user-2',
type: BatchOperationType.CREATE,
priority: BatchPriority.NORMAL,
execute: async () => {
const user = new CSElement('user-2');
await user.setData('name', 'Мария');
await user.setData('email', 'maria@example.com');
return user;
}
}
];
batchManager.addOperations(batchId, operations);
// Выполняем батч
const result = await batchManager.executeBatch(batchId);
console.log(`Батч выполнен: ${result.success}`);
console.log(`Операций выполнено: ${result.operationResults.length}`);
```
## Создание и управление батчами
### Конфигурация батча
```typescript
import {
BatchExecutionStrategy,
BatchErrorMode,
BatchConfig
} from 'cs-element';
const config: BatchConfig = {
executionStrategy: BatchExecutionStrategy.PARALLEL,
errorMode: BatchErrorMode.COLLECT_ERRORS,
maxConcurrency: 10,
timeout: 300000, // 5 минут
progressUpdateInterval: 1000, // 1 секунда
enableRollback: true,
validateBeforeExecution: true,
persistIntermediateResults: false
};
const batchId = batchManager.createBatch(config);
```
### Управление жизненным циклом
```typescript
// Создание батча
const batchId = batchManager.createBatch();
// Проверка статуса
const progress = batchManager.getProgress(batchId);
console.log(`Прогресс: ${progress?.percentage}%`);
// Отмена выполнения
await batchManager.cancelBatch(batchId);
// Получение результата
const result = batchManager.getBatchResult(batchId);
// Удаление батча
batchManager.removeBatch(batchId);
```
## Типы операций
### Базовые операции с элементами
```typescript
import { DefaultBatchOperationFactory } from 'cs-element';
const factory = new DefaultBatchOperationFactory();
const root = new CSElement('root');
// Операция создания элемента
const createOp = factory.createElementOperation({
name: 'new-element',
data: { type: 'document', status: 'draft' }
});
// Операция обновления элемента
const element = await root.addElement('existing-element');
const updateOp = factory.updateElementOperation(element, {
status: 'published',
lastModified: new Date()
});
// Операция удаления элемента
const deleteOp = factory.deleteElementOperation(element);
// Операция перемещения элемента
const newParent = new CSElement('new-parent');
const moveOp = factory.moveElementOperation(element, newParent);
// Операция копирования элемента
const copyOp = factory.copyElementOperation(element, newParent);
```
### Пользовательские операции
```typescript
// Сложная пользовательская операция
const customOperation = {
id: 'complex-data-processing',
type: BatchOperationType.CUSTOM,
priority: BatchPriority.HIGH,
timeout: 60000,
maxRetries: 3,
validate: async (context) => {
// Валидация перед выполнением
const hasRequiredData = context.sharedData.has('inputData');
return {
valid: hasRequiredData,
errors: hasRequiredData ? [] : ['Отсутствуют входные данные'],
warnings: []
};
},
execute: async (context) => {
const inputData = context.sharedData.get('inputData');
// Обновляем прогресс
context.updateProgress?.({
currentOperation: 'Обработка данных...'
});
// Выполняем обработку
const processedData = await processComplexData(inputData);
// Сохраняем результат для других операций
context.sharedData.set('processedData', processedData);
return processedData;
},
rollback: async (context) => {
// Откат изменений при ошибке
context.sharedData.delete('processedData');
console.log('Откат операции обработки данных');
}
};
async function processComplexData(data: any): Promise<any> {
// Симуляция сложной обработки
return new Promise(resolve => {
setTimeout(() => {
resolve({ ...data, processed: true, timestamp: Date.now() });
}, 2000);
});
}
```
## Стратегии выполнения
### Параллельное выполнение
```typescript
// Быстрое выполнение независимых операций
const parallelBatch = batchManager.createBatch({
executionStrategy: BatchExecutionStrategy.PARALLEL,
maxConcurrency: 8
});
const parallelOps = Array.from({ length: 20 }, (_, i) => ({
id: `parallel-op-${i}`,
type: BatchOperationType.CREATE,
priority: BatchPriority.NORMAL,
execute: async () => {
const element = new CSElement(`element-${i}`);
await element.setData('index', i);
await element.setData('created', new Date());
return element;
}
}));
batchManager.addOperations(parallelBatch, parallelOps);
const parallelResult = await batchManager.executeBatch(parallelBatch);
```
### Последовательное выполнение
```typescript
// Операции с зависимостями
const sequentialBatch = batchManager.createBatch({
executionStrategy: BatchExecutionStrategy.SEQUENTIAL
});
const sequentialOps = [
{
id: 'step-1',
type: BatchOperationType.CREATE,
priority: BatchPriority.HIGH,
execute: async (context) => {
const config = new CSElement('config');
await config.setData('database', 'initialized');
context.sharedData.set('config', config);
return config;
}
},
{
id: 'step-2',
type: BatchOperationType.CREATE,
priority: BatchPriority.NORMAL,
dependencies: ['step-1'],
execute: async (context) => {
const config = context.sharedData.get('config');
const users = new CSElement('users');
await users.setData('configRef', config.id);
context.sharedData.set('users', users);
return users;
}
},
{
id: 'step-3',
type: BatchOperationType.UPDATE,
priority: BatchPriority.NORMAL,
dependencies: ['step-1', 'step-2'],
execute: async (context) => {
const config = context.sharedData.get('config');
const users = context.sharedData.get('users');
await config.setData('usersRef', users.id);
await config.setData('status', 'ready');
return { config, users };
}
}
];
batchManager.addOperations(sequentialBatch, sequentialOps);
```
### Смешанная стратегия
```typescript
// Комбинация параллельного и последовательного выполнения
const mixedBatch = batchManager.createBatch({
executionStrategy: BatchExecutionStrategy.MIXED,
maxConcurrency: 4
});
const mixedOps = [
// Группа 1: Параллельная инициализация
{
id: 'init-database',
type: BatchOperationType.CREATE,
priority: BatchPriority.CRITICAL,
execute: async () => {
const db = new CSElement('database');
await db.setData('status', 'initializing');
await new Promise(resolve => setTimeout(resolve, 1000));
await db.setData('status', 'ready');
return db;
}
},
{
id: 'init-cache',
type: BatchOperationType.CREATE,
priority: BatchPriority.CRITICAL,
execute: async () => {
const cache = new CSElement('cache');
await cache.setData('status', 'initializing');
await new Promise(resolve => setTimeout(resolve, 800));
await cache.setData('status', 'ready');
return cache;
}
},
// Группа 2: Зависимые операции (выполняются после группы 1)
{
id: 'create-users',
type: BatchOperationType.CREATE,
priority: BatchPriority.HIGH,
dependencies: ['init-database'],
execute: async () => {
const users = new CSElement('users');
await users.setData('table', 'users');
return users;
}
},
{
id: 'create-sessions',
type: BatchOperationType.CREATE,
priority: BatchPriority.HIGH,
dependencies: ['init-cache'],
execute: async () => {
const sessions = new CSElement('sessions');
await sessions.setData('storage', 'cache');
return sessions;
}
}
];
batchManager.addOperations(mixedBatch, mixedOps);
```
## Обработка ошибок
### Режимы обработки ошибок
```typescript
// Остановка при первой ошибке
const failFastBatch = batchManager.createBatch({
errorMode: BatchErrorMode.FAIL_FAST
});
// Продолжение выполнения несмотря на ошибки
const continueBatch = batchManager.createBatch({
errorMode: BatchErrorMode.CONTINUE
});
// Сбор всех ошибок
const collectErrorsBatch = batchManager.createBatch({
errorMode: BatchErrorMode.COLLECT_ERRORS
});
```
### Операции с обработкой ошибок
```typescript
const resilientOp = {
id: 'resilient-operation',
type: BatchOperationType.CUSTOM,
priority: BatchPriority.NORMAL,
maxRetries: 3,
execute: async (context) => {
const attempt = context.operation.metadata?.attempt || 1;
try {
// Операция, которая может завершиться ошибкой
if (Math.random() < 0.7 && attempt < 3) {
throw new Error(`Временная ошибка (попытка ${attempt})`);
}
return { success: true, attempt };
} catch (error) {
console.log(`Ошибка в попытке ${attempt}: ${error.message}`);
if (attempt >= 3) {
// Последняя попытка - выполняем fallback
return { success: false, fallback: true };
}
throw error; // Повторить попытку
}
},
rollback: async (context) => {
console.log('Выполняем откат операции');
// Логика отката
}
};
```
## Мониторинг и прогресс
### Отслеживание прогресса
```typescript
// Подписка на события батча
batchManager.addEventListener({
onBatchStart: (batchId) => {
console.log(`🚀 Начало выполнения батча: ${batchId}`);
},
onBatchComplete: (result) => {
console.log(`✅ Батч завершен: ${result.batchId}`);
console.log(`Успешно: ${result.success}`);
console.log(`Время выполнения: ${result.totalExecutionTime}мс`);
},
onOperationStart: (operation) => {
console.log(`▶️ Начало операции: ${operation.id}`);
},
onOperationComplete: (result) => {
console.log(`✔️ Операция завершена: ${result.operationId}`);
},
onProgressUpdate: (progress) => {
console.log(`📊 Прогресс: ${progress.percentage}% (${progress.completedOperations}/${progress.totalOperations})`);
if (progress.estimatedTimeRemaining) {
console.log(`⏱️ Осталось: ${Math.round(progress.estimatedTimeRemaining / 1000)}с`);
}
},
onBatchError: (batchId, error) => {
console.error(`❌ Ошибка в батче ${batchId}:`, error.message);
}
});
```
### Статистика выполнения
```typescript
// Получение статистики менеджера
const stats = batchManager.getStatistics();
console.log('📈 Статистика BatchManager:');
console.log(`Всего батчей: ${stats.totalBatches}`);
console.log(`Успешных: ${stats.successfulBatches}`);
console.log(`Неудачных: ${stats.failedBatches}`);
console.log(`Активных: ${stats.activeBatches}`);
console.log(`Среднее время выполнения: ${stats.averageBatchExecutionTime}мс`);
// Статистика конкретного батча
const batchResult = batchManager.getBatchResult(batchId);
if (batchResult) {
console.log('📊 Статистика батча:');
console.log(`Операций: ${batchResult.statistics.totalOperations}`);
console.log(`Успешных: ${batchResult.statistics.successfulOperations}`);
console.log(`Неудачных: ${batchResult.statistics.failedOperations}`);
console.log(`Среднее время операции: ${batchResult.statistics.averageExecutionTime}мс`);
}
```
## Оптимизация производительности
### Настройка параллелизма
```typescript
// Оптимальная настройка для разных типов операций
const cpuIntensiveBatch = batchManager.createBatch({
executionStrategy: BatchExecutionStrategy.PARALLEL,
maxConcurrency: Math.max(1, Math.floor(navigator.hardwareConcurrency * 0.8))
});
const ioIntensiveBatch = batchManager.createBatch({
executionStrategy: BatchExecutionStrategy.PARALLEL,
maxConcurrency: 20 // Больше для I/O операций
});
const mixedWorkloadBatch = batchManager.createBatch({
executionStrategy: BatchExecutionStrategy.MIXED,
maxConcurrency: 6
});
```
### Группировка операций
```typescript
// Группировка схожих операций для лучшей производительности
const groupedOperations = [
// Группа 1: Быстрые операции создания
...Array.from({ length: 100 }, (_, i) => ({
id: `quick-create-${i}`,
type: BatchOperationType.CREATE,
priority: BatchPriority.NORMAL,
execute: async () => {
const element = new CSElement(`quick-${i}`);
await element.setData('type', 'quick');
return element;
}
})),
// Группа 2: Медленные операции обработки
...Array.from({ length: 10 }, (_, i) => ({
id: `slow-process-${i}`,
type: BatchOperationType.TRANSFORM,
priority: BatchPriority.LOW,
execute: async () => {
// Симуляция тяжелой обработки
await new Promise(resolve => setTimeout(resolve, 2000));
return { processed: i };
}
}))
];
```
### Управление памятью
```typescript
const memoryOptimizedBatch = batchManager.createBatch({
executionStrategy: BatchExecutionStrategy.SEQUENTIAL,
persistIntermediateResults: false, // Не сохраняем промежуточные результаты
progressUpdateInterval: 5000 // Реже обновляем прогресс
});
// Операция с очисткой памяти
const memoryEfficientOp = {
id: 'memory-efficient',
type: BatchOperationType.CUSTOM,
priority: BatchPriority.NORMAL,
execute: async (context) => {
try {
// Обработка данных
const result = await processLargeDataset();
// Очистка промежуточных данных
context.sharedData.clear();
return result;
} finally {
// Принудительная сборка мусора (если доступна)
if (global.gc) {
global.gc();
}
}
}
};
async function processLargeDataset(): Promise<any> {
// Симуляция обработки большого объема данных
return { size: 'large', processed: true };
}
```
## Полный пример: Система импорта данных
```typescript
import {
CSElement,
BatchManager,
DefaultBatchOperationFactory,
BatchOperationType,
BatchPriority,
BatchExecutionStrategy,
BatchErrorMode
} from 'cs-element';
interface ImportData {
users: Array<{
id: string;
name: string;
email: string;
department: string;
}>;
departments: Array<{
id: string;
name: string;
description: string;
}>;
projects: Array<{
id: string;
name: string;
departmentId: string;
memberIds: string[];
}>;
}
class DataImportSystem {
private batchManager: BatchManager;
private factory: DefaultBatchOperationFactory;
private root: CSElement;
constructor() {
this.batchManager = new BatchManager();
this.factory = new DefaultBatchOperationFactory();
this.root = new CSElement('company');
}
async importData(data: ImportData): Promise<{
success: boolean;
imported: {
departments: number;
users: number;
projects: number;
};
errors: string[];
}> {
console.log('🚀 Начинаем импорт данных...');
// Создаем батч с оптимальной конфигурацией
const batchId = this.batchManager.createBatch({
executionStrategy: BatchExecutionStrategy.MIXED,
errorMode: BatchErrorMode.COLLECT_ERRORS,
maxConcurrency: 6,
timeout: 600000, // 10 минут
enableRollback: true,
validateBeforeExecution: true
});
// Настраиваем мониторинг
this.setupProgressMonitoring(batchId);
try {
// Создаем операции импорта
const operations = [
...this.createDepartmentOperations(data.departments),
...this.createUserOperations(data.users),
...this.createProjectOperations(data.projects),
this.createValidationOperation(),
this.createIndexingOperation()
];
this.batchManager.addOperations(batchId, operations);
// Выполняем импорт
const result = await this.batchManager.executeBatch(batchId);
// Анализируем результаты
const stats = this.analyzeResults(result);
console.log('✅ Импорт завершен!');
console.log(`📊 Статистика: ${JSON.stringify(stats, null, 2)}`);
return {
success: result.success,
imported: stats.imported,
errors: result.errors.map(e => e.message)
};
} catch (error) {
console.error('❌ Критическая ошибка импорта:', error);
return {
success: false,
imported: { departments: 0, users: 0, projects: 0 },
errors: [error.message]
};
} finally {
// Очистка ресурсов
this.batchManager.removeBatch(batchId);
}
}
private createDepartmentOperations(departments: ImportData['departments']) {
return departments.map(dept => ({
id: `import-dept-${dept.id}`,
type: BatchOperationType.CREATE,
priority: BatchPriority.HIGH, // Высокий приоритет - другие зависят от отделов
validate: async () => ({
valid: !!dept.name && !!dept.id,
errors: [
!dept.id && 'Отсутствует ID отдела',
!dept.name && 'Отсутствует название отдела'
].filter(Boolean),
warnings: []
}),
execute: async (context) => {
context.updateProgress?.({
currentOperation: `Создание отдела: ${dept.name}`
});
const department = await this.root.addElement(`dept-${dept.id}`) as CSElement;
await department.setData('name', dept.name);
await department.setData('description', dept.description);
await department.setData('type', 'department');
await department.setData('importId', dept.id);
// Сохраняем для использования в других операциях
context.sharedData.set(`dept-${dept.id}`, department);
return department;
},
rollback: async (context) => {
const department = context.sharedData.get(`dept-${dept.id}`);
if (department) {
await this.root.removeElement(department);
context.sharedData.delete(`dept-${dept.id}`);
}
}
}));
}
private createUserOperations(users: ImportData['users']) {
return users.map(user => ({
id: `import-user-${user.id}`,
type: BatchOperationType.CREATE,
priority: BatchPriority.NORMAL,
dependencies: [`import-dept-${user.department}`], // Зависит от создания отдела
execute: async (context) => {
context.updateProgress?.({
currentOperation: `Создание пользователя: ${user.name}`
});
const department = context.sharedData.get(`dept-${user.department}`);
if (!department) {
throw new Error(`Отдел ${user.department} не найден для пользователя ${user.name}`);
}
const userElement = await department.addElement(`user-${user.id}`) as CSElement;
await userElement.setData('name', user.name);
await userElement.setData('email', user.email);
await userElement.setData('type', 'user');
await userElement.setData('importId', user.id);
context.sharedData.set(`user-${user.id}`, userElement);
return userElement;
}
}));
}
private createProjectOperations(projects: ImportData['projects']) {
return projects.map(project => ({
id: `import-project-${project.id}`,
type: BatchOperationType.CREATE,
priority: BatchPriority.NORMAL,
dependencies: [
`import-dept-${project.departmentId}`,
...project.memberIds.map(id => `import-user-${id}`)
],
execute: async (context) => {
context.updateProgress?.({
currentOperation: `Создание проекта: ${project.name}`
});
const department = context.sharedData.get(`dept-${project.departmentId}`);
const projectElement = await department.addElement(`project-${project.id}`) as CSElement;
await projectElement.setData('name', project.name);
await projectElement.setData('type', 'project');
await projectElement.setData('importId', project.id);
// Связываем с участниками
for (const memberId of project.memberIds) {
const user = context.sharedData.get(`user-${memberId}`);
if (user) {
await projectElement.addElement(user);
}
}
return projectElement;
}
}));
}
private createValidationOperation() {
return {
id: 'validate-import',
type: BatchOperationType.VALIDATE,
priority: BatchPriority.LOW,
dependencies: [], // Выполняется в конце
execute: async (context) => {
context.updateProgress?.({
currentOperation: 'Валидация импортированных данных'
});
const departments = this.root.query('[type="department"]');
const users = this.root.query('[type="user"]');
const projects = this.root.query('[type="project"]');
const validation = {
departments: departments.length,
users: users.length,
projects: projects.length,
issues: []
};
// Проверяем целостность данных
for (const project of projects) {
const members = project.getAllElements().filter(e =>
e.getData('type') === 'user'
);
if (members.length === 0) {
validation.issues.push(`Проект ${project.getData('name')} не имеет участников`);
}
}
console.log('🔍 Результаты валидации:', validation);
return validation;
}
};
}
private createIndexingOperation() {
return {
id: 'create-indexes',
type: BatchOperationType.CUSTOM,
priority: BatchPriority.LOW,
execute: async (context) => {
context.updateProgress?.({
currentOperation: 'Создание индексов для быстрого поиска'
});
// Создаем индексы для быстрого поиска
const emailIndex = new CSElement('email-index');
const departmentIndex = new CSElement('department-index');
await this.root.addElement(emailIndex);
await this.root.addElement(departmentIndex);
// Индексируем пользователей по email
const users = this.root.query('[type="user"]');
for (const user of users) {
const email = user.getData('email');
if (email) {
await emailIndex.setData(email, user.id);
}
}
// Индексируем по отделам
const departments = this.root.query('[type="department"]');
for (const dept of departments) {
const name = dept.getData('name');
if (name) {
await departmentIndex.setData(name, dept.id);
}
}
return { emailIndex: emailIndex.id, departmentIndex: departmentIndex.id };
}
};
}
private setupProgressMonitoring(batchId: string) {
this.batchManager.addEventListener({
onProgressUpdate: (progress) => {
const percentage = Math.round(progress.percentage);
const completed = progress.completedOperations;
const total = progress.totalOperations;
const current = progress.currentOperation || 'Выполнение операций';
console.log(`📊 ${percentage}% (${completed}/${total}) - ${current}`);
if (progress.estimatedTimeRemaining) {
const minutes = Math.round(progress.estimatedTimeRemaining / 60000);
console.log(`⏱️ Осталось примерно: ${minutes} мин`);
}
},
onOperationComplete: (result) => {
if (result.success) {
console.log(`✔️ ${result.operationId} (${result.executionTime}мс)`);
} else {
console.log(`❌ ${result.operationId}: ${result.error?.message}`);
}
}
});
}
private analyzeResults(result: any) {
const imported = {
departments: 0,
users: 0,
projects: 0
};
for (const opResult of result.operationResults) {
if (opResult.success) {
if (opResult.operationId.startsWith('import-dept-')) {
imported.departments++;
} else if (opResult.operationId.startsWith('import-user-')) {
imported.users++;
} else if (opResult.operationId.startsWith('import-project-')) {
imported.projects++;
}
}
}
return {
imported,
totalTime: result.totalExecutionTime,
averageOpTime: result.statistics.averageExecutionTime,
successRate: (result.statistics.successfulOperations / result.statistics.totalOperations) * 100
};
}
}
// Использование системы импорта
async function demonstrateImport() {
const importSystem = new DataImportSystem();
const sampleData: ImportData = {
departments: [
{ id: 'IT', name: 'IT отдел', description: 'Информационные технологии' },
{ id: 'HR', name: 'HR отдел', description: 'Управление персоналом' },
{ id: 'SALES', name: 'Отдел продаж', description: 'Продажи и маркетинг' }
],
users: [
{ id: '1', name: 'Алексей Петров', email: 'alexey@company.com', department: 'IT' },
{ id: '2', name: 'Мария Иванова', email: 'maria@company.com', department: 'HR' },
{ id: '3', name: 'Сергей Сидоров', email: 'sergey@company.com', department: 'IT' },
{ id: '4', name: 'Анна Козлова', email: 'anna@company.com', department: 'SALES' }
],
projects: [
{
id: 'P1',
name: 'Новая CRM система',
departmentId: 'IT',
memberIds: ['1', '3']
},
{
id: 'P2',
name: 'Кампания по найму',
departmentId: 'HR',
memberIds: ['2']
}
]
};
const result = await importSystem.importData(sampleData);
console.log('🎉 Результат импорта:');
console.log(`Успешно: ${result.success}`);
console.log(`Импортировано отделов: ${result.imported.departments}`);
console.log(`Импортировано пользователей: ${result.imported.users}`);
console.log(`Импортировано проектов: ${result.imported.projects}`);
if (result.errors.length > 0) {
console.log('❌ Ошибки:', result.errors);
}
}
// demonstrateImport();
```
## 🎯 Заключение
Система batch операций CSElement предоставляет:
- **🚀 Высокую производительность** - оптимизированное выполнение массовых операций
- **🔧 Гибкую конфигурацию** - различные стратегии выполнения и обработки ошибок
- **📊 Подробный мониторинг** - отслеживание прогресса и статистики
- **🛡️ Надежность** - обработка ошибок, откат операций и валидация
- **⚡ Масштабируемость** - эффективная работа с большими объемами данных
Используйте batch операции для импорта данных, массовых обновлений, миграций и других задач, требующих обработки множества элементов.
---
**Следующий раздел:** [React интеграция](06-react-integration.md) - изучите, как интегрировать CSElement с React приложениями.