mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-04-25 16:36:21 +02:00
781 lines
31 KiB
Markdown
781 lines
31 KiB
Markdown
|
|
---
|
||
|
|
layout: default
|
||
|
|
title: "Especificação Técnica: Suporte para Multi-Tenancy"
|
||
|
|
parent: "Portuguese (Beta)"
|
||
|
|
---
|
||
|
|
|
||
|
|
# Especificação Técnica: Suporte para Multi-Tenancy
|
||
|
|
|
||
|
|
> **Beta Translation:** This document was translated via Machine Learning and as such may not be 100% accurate. All non-English languages are currently classified as Beta.
|
||
|
|
|
||
|
|
## Visão Geral
|
||
|
|
|
||
|
|
Habilite implantações multi-tenant corrigindo incompatibilidades de nomes de parâmetros que impedem a personalização da fila e adicionando parametrização do keyspace do Cassandra.
|
||
|
|
|
||
|
|
## Contexto da Arquitetura
|
||
|
|
|
||
|
|
### Resolução de Filas Baseada em Fluxo
|
||
|
|
|
||
|
|
O sistema TrustGraph usa uma **arquitetura baseada em fluxo** para a resolução dinâmica de filas, que suporta inerentemente o multi-tenancy:
|
||
|
|
|
||
|
|
As **Definições de Fluxo** são armazenadas no Cassandra e especificam os nomes das filas por meio de definições de interface.
|
||
|
|
**Os nomes das filas usam modelos** com variáveis `{id}` que são substituídas pelos IDs das instâncias de fluxo.
|
||
|
|
**Os serviços resolvem dinamicamente as filas** consultando as configurações de fluxo no momento da solicitação.
|
||
|
|
**Cada tenant pode ter fluxos únicos** com nomes de fila diferentes, proporcionando isolamento.
|
||
|
|
|
||
|
|
Exemplo de definição de interface de fluxo:
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"interfaces": {
|
||
|
|
"triples-store": "persistent://tg/flow/triples-store:{id}",
|
||
|
|
"graph-embeddings-store": "persistent://tg/flow/graph-embeddings-store:{id}"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Quando o tenant A inicia o fluxo `tenant-a-prod` e o tenant B inicia o fluxo `tenant-b-prod`, eles automaticamente recebem filas isoladas:
|
||
|
|
`persistent://tg/flow/triples-store:tenant-a-prod`
|
||
|
|
`persistent://tg/flow/triples-store:tenant-b-prod`
|
||
|
|
|
||
|
|
**Serviços corretamente projetados para multi-tenancy:**
|
||
|
|
✅ **Knowledge Management (cores)** - Resolve dinamicamente as filas a partir da configuração do fluxo passada nas requisições
|
||
|
|
|
||
|
|
**Serviços que precisam de correções:**
|
||
|
|
🔴 **Config Service** - Incompatibilidade no nome do parâmetro impede a personalização da fila
|
||
|
|
🔴 **Librarian Service** - Tópicos de gerenciamento de armazenamento codificados (discutido abaixo)
|
||
|
|
🔴 **Todos os Serviços** - Não é possível personalizar o keyspace do Cassandra
|
||
|
|
|
||
|
|
## Declaração do Problema
|
||
|
|
|
||
|
|
### Problema #1: Incompatibilidade no Nome do Parâmetro no AsyncProcessor
|
||
|
|
**CLI define:** `--config-queue` (nomeação pouco clara)
|
||
|
|
**Argparse converte para:** `config_queue` (no dicionário de parâmetros)
|
||
|
|
**Código procura por:** `config_push_queue`
|
||
|
|
**Resultado:** O parâmetro é ignorado, usa o valor padrão de `persistent://tg/config/config`
|
||
|
|
**Impacto:** Afeta todos os 32+ serviços que herdam do AsyncProcessor
|
||
|
|
**Bloqueia:** Impossibilita o uso de filas de configuração específicas do tenant em implantações multi-tenant
|
||
|
|
**Solução:** Renomear o parâmetro da CLI para `--config-push-queue` para maior clareza (alteração disruptiva aceitável, já que o recurso está atualmente com defeito)
|
||
|
|
|
||
|
|
### Problema #2: Incompatibilidade no Nome do Parâmetro no Config Service
|
||
|
|
**CLI define:** `--push-queue` (nomeação ambígua)
|
||
|
|
**Argparse converte para:** `push_queue` (no dicionário de parâmetros)
|
||
|
|
**Código procura por:** `config_push_queue`
|
||
|
|
**Resultado:** O parâmetro é ignorado
|
||
|
|
**Impacto:** O Config service não pode usar uma fila de push personalizada
|
||
|
|
**Solução:** Renomear o parâmetro da CLI para `--config-push-queue` para consistência e clareza (alteração disruptiva aceitável)
|
||
|
|
|
||
|
|
### Problema #3: Keyspace do Cassandra Codificado
|
||
|
|
**Atual:** Keyspace codificado como `"config"`, `"knowledge"`, `"librarian"` em vários serviços
|
||
|
|
**Resultado:** Impossibilita a personalização do keyspace para implantações multi-tenant
|
||
|
|
**Impacto:** Serviços Config, cores e librarian
|
||
|
|
**Bloqueia:** Múltiplos tenants não podem usar keyspaces separados do Cassandra
|
||
|
|
|
||
|
|
### Problema #4: Arquitetura de Gerenciamento de Coleções ✅ CONCLUÍDO
|
||
|
|
**Anterior:** Coleções armazenadas no keyspace do librarian do Cassandra em uma tabela de coleções separada
|
||
|
|
**Anterior:** O librarian usava 4 tópicos de gerenciamento de armazenamento codificados para coordenar a criação/exclusão de coleções:
|
||
|
|
`vector_storage_management_topic`
|
||
|
|
`object_storage_management_topic`
|
||
|
|
`triples_storage_management_topic`
|
||
|
|
`storage_management_response_topic`
|
||
|
|
**Problemas (Resolvidos):**
|
||
|
|
Tópicos codificados não podiam ser personalizados para implantações multi-tenant
|
||
|
|
Coordenação assíncrona complexa entre o librarian e 4+ serviços de armazenamento
|
||
|
|
Tabela separada do Cassandra e infraestrutura de gerenciamento
|
||
|
|
Filas de requisição/resposta não persistentes para operações críticas
|
||
|
|
**Solução Implementada:** Migrou as coleções para o armazenamento do serviço de configuração, usa push de configuração para distribuição
|
||
|
|
**Status:** Todos os backends de armazenamento migrados para o padrão `CollectionConfigHandler`
|
||
|
|
|
||
|
|
## Solução
|
||
|
|
|
||
|
|
Esta especificação aborda os problemas #1, #2, #3 e #4.
|
||
|
|
|
||
|
|
### Parte 1: Corrigir Incompatibilidades no Nome do Parâmetro
|
||
|
|
|
||
|
|
#### Alteração 1: Classe Base AsyncProcessor - Renomear Parâmetro da CLI
|
||
|
|
**Arquivo:** `trustgraph-base/trustgraph/base/async_processor.py`
|
||
|
|
**Linha:** 260-264
|
||
|
|
|
||
|
|
**Atual:**
|
||
|
|
```python
|
||
|
|
parser.add_argument(
|
||
|
|
'--config-queue',
|
||
|
|
default=default_config_queue,
|
||
|
|
help=f'Config push queue {default_config_queue}',
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Corrigido:**
|
||
|
|
```python
|
||
|
|
parser.add_argument(
|
||
|
|
'--config-push-queue',
|
||
|
|
default=default_config_queue,
|
||
|
|
help=f'Config push queue (default: {default_config_queue})',
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Justificativa:**
|
||
|
|
Nomenclatura mais clara e explícita
|
||
|
|
Coincide com o nome da variável interna `config_push_queue`
|
||
|
|
Mudança disruptiva aceitável, já que a funcionalidade está atualmente inativa
|
||
|
|
Não é necessária nenhuma alteração no código em params.get() - ele já procura pelo nome correto
|
||
|
|
|
||
|
|
#### Mudança 2: Serviço de Configuração - Renomear Parâmetro da CLI
|
||
|
|
**Arquivo:** `trustgraph-flow/trustgraph/config/service/service.py`
|
||
|
|
**Linha:** 276-279
|
||
|
|
|
||
|
|
**Atual:**
|
||
|
|
```python
|
||
|
|
parser.add_argument(
|
||
|
|
'--push-queue',
|
||
|
|
default=default_config_push_queue,
|
||
|
|
help=f'Config push queue (default: {default_config_push_queue})'
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Corrigido:**
|
||
|
|
```python
|
||
|
|
parser.add_argument(
|
||
|
|
'--config-push-queue',
|
||
|
|
default=default_config_push_queue,
|
||
|
|
help=f'Config push queue (default: {default_config_push_queue})'
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Justificativa:**
|
||
|
|
Nomes mais claros - "config-push-queue" é mais explícito do que apenas "push-queue".
|
||
|
|
Compatível com o nome da variável interna `config_push_queue`.
|
||
|
|
Consistente com o parâmetro `--config-push-queue` do AsyncProcessor.
|
||
|
|
Mudança disruptiva aceitável, já que o recurso está atualmente inativo.
|
||
|
|
Nenhuma alteração de código necessária em params.get() - ele já procura pelo nome correto.
|
||
|
|
|
||
|
|
### Parte 2: Adicionar Parametrização do Keyspace do Cassandra
|
||
|
|
|
||
|
|
#### Mudança 3: Adicionar Parâmetro de Keyspace ao Módulo cassandra_config
|
||
|
|
**Arquivo:** `trustgraph-base/trustgraph/base/cassandra_config.py`
|
||
|
|
|
||
|
|
**Adicionar argumento de linha de comando** (na função `add_cassandra_args()`):
|
||
|
|
```python
|
||
|
|
parser.add_argument(
|
||
|
|
'--cassandra-keyspace',
|
||
|
|
default=None,
|
||
|
|
help='Cassandra keyspace (default: service-specific)'
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Adicionar suporte para variáveis de ambiente** (na função `resolve_cassandra_config()`):
|
||
|
|
```python
|
||
|
|
keyspace = params.get(
|
||
|
|
"cassandra_keyspace",
|
||
|
|
os.environ.get("CASSANDRA_KEYSPACE")
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Atualizar o valor de retorno** de `resolve_cassandra_config()`:
|
||
|
|
Atualmente retorna: `(hosts, username, password)`
|
||
|
|
Alterar para retornar: `(hosts, username, password, keyspace)`
|
||
|
|
|
||
|
|
**Justificativa:**
|
||
|
|
Consistente com o padrão de configuração existente do Cassandra
|
||
|
|
Disponível para todos os serviços via `add_cassandra_args()`
|
||
|
|
Suporta configuração via linha de comando e variáveis de ambiente
|
||
|
|
|
||
|
|
#### Mudança 4: Serviço de Configuração - Usar Keyspace Parametrizados
|
||
|
|
**Arquivo:** `trustgraph-flow/trustgraph/config/service/service.py`
|
||
|
|
|
||
|
|
**Linha 30** - Remover o keyspace codificado:
|
||
|
|
```python
|
||
|
|
# DELETE THIS LINE:
|
||
|
|
keyspace = "config"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Linhas 69-73** - Atualização da resolução da configuração do Cassandra:
|
||
|
|
|
||
|
|
**Atual:**
|
||
|
|
```python
|
||
|
|
cassandra_host, cassandra_username, cassandra_password = \
|
||
|
|
resolve_cassandra_config(params)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Corrigido:**
|
||
|
|
```python
|
||
|
|
cassandra_host, cassandra_username, cassandra_password, keyspace = \
|
||
|
|
resolve_cassandra_config(params, default_keyspace="config")
|
||
|
|
```
|
||
|
|
|
||
|
|
**Justificativa:**
|
||
|
|
Mantém a compatibilidade com versões anteriores, utilizando "config" como padrão.
|
||
|
|
Permite a substituição através de `--cassandra-keyspace` ou `CASSANDRA_KEYSPACE`.
|
||
|
|
|
||
|
|
#### Mudança 5: Cores/Serviço de Conhecimento - Utilizar Chaves de Espaço de Chaves Parametrizadas
|
||
|
|
**Arquivo:** `trustgraph-flow/trustgraph/cores/service.py`
|
||
|
|
|
||
|
|
**Linha 37** - Remover o espaço de chaves codificado:
|
||
|
|
```python
|
||
|
|
# DELETE THIS LINE:
|
||
|
|
keyspace = "knowledge"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Atualização da resolução de configuração do Cassandra** (localização semelhante ao serviço de configuração):
|
||
|
|
```python
|
||
|
|
cassandra_host, cassandra_username, cassandra_password, keyspace = \
|
||
|
|
resolve_cassandra_config(params, default_keyspace="knowledge")
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Mudança 6: Serviço de Bibliotecário - Use Chaves de Espaço de Chaves Parametrizadas
|
||
|
|
**Arquivo:** `trustgraph-flow/trustgraph/librarian/service.py`
|
||
|
|
|
||
|
|
**Linha 51** - Remova a chave de espaço de chaves codificada:
|
||
|
|
```python
|
||
|
|
# DELETE THIS LINE:
|
||
|
|
keyspace = "librarian"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Atualização da resolução de configuração do Cassandra** (localização semelhante ao serviço de configuração):
|
||
|
|
```python
|
||
|
|
cassandra_host, cassandra_username, cassandra_password, keyspace = \
|
||
|
|
resolve_cassandra_config(params, default_keyspace="librarian")
|
||
|
|
```
|
||
|
|
|
||
|
|
### Parte 3: Migrar o Gerenciamento de Coleções para o Serviço de Configuração
|
||
|
|
|
||
|
|
#### Visão Geral
|
||
|
|
Migrar as coleções do keyspace do Cassandra librarian para o armazenamento do serviço de configuração. Isso elimina os tópicos de gerenciamento de armazenamento codificados e simplifica a arquitetura, utilizando o mecanismo de push de configuração existente para distribuição.
|
||
|
|
|
||
|
|
#### Arquitetura Atual
|
||
|
|
```
|
||
|
|
API Request → Gateway → Librarian Service
|
||
|
|
↓
|
||
|
|
CollectionManager
|
||
|
|
↓
|
||
|
|
Cassandra Collections Table (librarian keyspace)
|
||
|
|
↓
|
||
|
|
Broadcast to 4 Storage Management Topics (hardcoded)
|
||
|
|
↓
|
||
|
|
Wait for 4+ Storage Service Responses
|
||
|
|
↓
|
||
|
|
Response to Gateway
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Nova Arquitetura
|
||
|
|
```
|
||
|
|
API Request → Gateway → Librarian Service
|
||
|
|
↓
|
||
|
|
CollectionManager
|
||
|
|
↓
|
||
|
|
Config Service API (put/delete/getvalues)
|
||
|
|
↓
|
||
|
|
Cassandra Config Table (class='collections', key='user:collection')
|
||
|
|
↓
|
||
|
|
Config Push (to all subscribers on config-push-queue)
|
||
|
|
↓
|
||
|
|
All Storage Services receive config update independently
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Mudança 7: Gerenciador de Coleções - Usar API do Serviço de Configuração
|
||
|
|
**Arquivo:** `trustgraph-flow/trustgraph/librarian/collection_manager.py`
|
||
|
|
|
||
|
|
**Remover:**
|
||
|
|
Uso de `LibraryTableStore` (Linhas 33, 40-41)
|
||
|
|
Inicialização de produtores de gerenciamento de armazenamento (Linhas 86-140)
|
||
|
|
Método `on_storage_response` (Linhas 400-430)
|
||
|
|
Rastreamento de `pending_deletions` (Linhas 57, 90-96 e uso em todo o código)
|
||
|
|
|
||
|
|
**Adicionar:**
|
||
|
|
Cliente do serviço de configuração para chamadas de API (padrão de solicitação/resposta)
|
||
|
|
|
||
|
|
**Configuração do Cliente:**
|
||
|
|
```python
|
||
|
|
# In __init__, add config request/response producers/consumers
|
||
|
|
from trustgraph.schema.services.config import ConfigRequest, ConfigResponse
|
||
|
|
|
||
|
|
# Producer for config requests
|
||
|
|
self.config_request_producer = Producer(
|
||
|
|
client=pulsar_client,
|
||
|
|
topic=config_request_queue,
|
||
|
|
schema=ConfigRequest,
|
||
|
|
)
|
||
|
|
|
||
|
|
# Consumer for config responses (with correlation ID)
|
||
|
|
self.config_response_consumer = Consumer(
|
||
|
|
taskgroup=taskgroup,
|
||
|
|
client=pulsar_client,
|
||
|
|
flow=None,
|
||
|
|
topic=config_response_queue,
|
||
|
|
subscriber=f"{id}-config",
|
||
|
|
schema=ConfigResponse,
|
||
|
|
handler=self.on_config_response,
|
||
|
|
)
|
||
|
|
|
||
|
|
# Tracking for pending config requests
|
||
|
|
self.pending_config_requests = {} # request_id -> asyncio.Event
|
||
|
|
```
|
||
|
|
|
||
|
|
**Modificar `list_collections` (Linhas 145-180):**
|
||
|
|
```python
|
||
|
|
async def list_collections(self, user, tag_filter=None, limit=None):
|
||
|
|
"""List collections from config service"""
|
||
|
|
# Send getvalues request to config service
|
||
|
|
request = ConfigRequest(
|
||
|
|
id=str(uuid.uuid4()),
|
||
|
|
operation='getvalues',
|
||
|
|
type='collections',
|
||
|
|
)
|
||
|
|
|
||
|
|
# Send request and wait for response
|
||
|
|
response = await self.send_config_request(request)
|
||
|
|
|
||
|
|
# Parse collections from response
|
||
|
|
collections = []
|
||
|
|
for key, value_json in response.values.items():
|
||
|
|
if ":" in key:
|
||
|
|
coll_user, collection = key.split(":", 1)
|
||
|
|
if coll_user == user:
|
||
|
|
metadata = json.loads(value_json)
|
||
|
|
collections.append(CollectionMetadata(**metadata))
|
||
|
|
|
||
|
|
# Apply tag filtering in-memory (as before)
|
||
|
|
if tag_filter:
|
||
|
|
collections = [c for c in collections if any(tag in c.tags for tag in tag_filter)]
|
||
|
|
|
||
|
|
# Apply limit
|
||
|
|
if limit:
|
||
|
|
collections = collections[:limit]
|
||
|
|
|
||
|
|
return collections
|
||
|
|
|
||
|
|
async def send_config_request(self, request):
|
||
|
|
"""Send config request and wait for response"""
|
||
|
|
event = asyncio.Event()
|
||
|
|
self.pending_config_requests[request.id] = event
|
||
|
|
|
||
|
|
await self.config_request_producer.send(request)
|
||
|
|
await event.wait()
|
||
|
|
|
||
|
|
return self.pending_config_requests.pop(request.id + "_response")
|
||
|
|
|
||
|
|
async def on_config_response(self, message, consumer, flow):
|
||
|
|
"""Handle config response"""
|
||
|
|
response = message.value()
|
||
|
|
if response.id in self.pending_config_requests:
|
||
|
|
self.pending_config_requests[response.id + "_response"] = response
|
||
|
|
self.pending_config_requests[response.id].set()
|
||
|
|
```
|
||
|
|
|
||
|
|
**Modificar `update_collection` (Linhas 182-312):**
|
||
|
|
```python
|
||
|
|
async def update_collection(self, user, collection, name, description, tags):
|
||
|
|
"""Update collection via config service"""
|
||
|
|
# Create metadata
|
||
|
|
metadata = CollectionMetadata(
|
||
|
|
user=user,
|
||
|
|
collection=collection,
|
||
|
|
name=name,
|
||
|
|
description=description,
|
||
|
|
tags=tags,
|
||
|
|
)
|
||
|
|
|
||
|
|
# Send put request to config service
|
||
|
|
request = ConfigRequest(
|
||
|
|
id=str(uuid.uuid4()),
|
||
|
|
operation='put',
|
||
|
|
type='collections',
|
||
|
|
key=f'{user}:{collection}',
|
||
|
|
value=json.dumps(metadata.to_dict()),
|
||
|
|
)
|
||
|
|
|
||
|
|
response = await self.send_config_request(request)
|
||
|
|
|
||
|
|
if response.error:
|
||
|
|
raise RuntimeError(f"Config update failed: {response.error.message}")
|
||
|
|
|
||
|
|
# Config service will trigger config push automatically
|
||
|
|
# Storage services will receive update and create collections
|
||
|
|
```
|
||
|
|
|
||
|
|
**Modificar `delete_collection` (Linhas 314-398):**
|
||
|
|
```python
|
||
|
|
async def delete_collection(self, user, collection):
|
||
|
|
"""Delete collection via config service"""
|
||
|
|
# Send delete request to config service
|
||
|
|
request = ConfigRequest(
|
||
|
|
id=str(uuid.uuid4()),
|
||
|
|
operation='delete',
|
||
|
|
type='collections',
|
||
|
|
key=f'{user}:{collection}',
|
||
|
|
)
|
||
|
|
|
||
|
|
response = await self.send_config_request(request)
|
||
|
|
|
||
|
|
if response.error:
|
||
|
|
raise RuntimeError(f"Config delete failed: {response.error.message}")
|
||
|
|
|
||
|
|
# Config service will trigger config push automatically
|
||
|
|
# Storage services will receive update and delete collections
|
||
|
|
```
|
||
|
|
|
||
|
|
**Formato de Metadados de Coleção:**
|
||
|
|
Armazenado na tabela de configuração como: `class='collections', key='user:collection'`
|
||
|
|
O valor é uma CollectionMetadata serializada em JSON (sem campos de timestamp)
|
||
|
|
Campos: `user`, `collection`, `name`, `description`, `tags`
|
||
|
|
Exemplo: `class='collections', key='alice:my-docs', value='{"user":"alice","collection":"my-docs","name":"My Documents","description":"...","tags":["work"]}'`
|
||
|
|
|
||
|
|
#### Mudança 8: Serviço de Bibliotecário - Remover a Infraestrutura de Gerenciamento de Armazenamento
|
||
|
|
**Arquivo:** `trustgraph-flow/trustgraph/librarian/service.py`
|
||
|
|
|
||
|
|
**Remover:**
|
||
|
|
Produtores de gerenciamento de armazenamento (Linhas 173-190):
|
||
|
|
`vector_storage_management_producer`
|
||
|
|
`object_storage_management_producer`
|
||
|
|
`triples_storage_management_producer`
|
||
|
|
Consumidor de resposta de armazenamento (Linhas 192-201)
|
||
|
|
Manipulador `on_storage_response` (Linhas 467-473)
|
||
|
|
|
||
|
|
**Modificar:**
|
||
|
|
Inicialização do CollectionManager (Linhas 215-224) - remover os parâmetros do produtor de armazenamento
|
||
|
|
|
||
|
|
**Observação:** A API externa de coleções permanece inalterada:
|
||
|
|
`list-collections`
|
||
|
|
`update-collection`
|
||
|
|
`delete-collection`
|
||
|
|
|
||
|
|
#### Mudança 9: Remover a Tabela de Coleções do LibraryTableStore
|
||
|
|
**Arquivo:** `trustgraph-flow/trustgraph/tables/library.py`
|
||
|
|
|
||
|
|
**Excluir:**
|
||
|
|
Instrução CREATE da tabela de coleções (Linhas 114-127)
|
||
|
|
Prepared statements de coleções (Linhas 205-240)
|
||
|
|
Todos os métodos de coleção (Linhas 578-717):
|
||
|
|
`ensure_collection_exists`
|
||
|
|
`list_collections`
|
||
|
|
`update_collection`
|
||
|
|
`delete_collection`
|
||
|
|
`get_collection`
|
||
|
|
`create_collection`
|
||
|
|
|
||
|
|
**Justificativa:**
|
||
|
|
As coleções agora são armazenadas na tabela de configuração
|
||
|
|
Mudança disruptiva aceitável - nenhuma migração de dados necessária
|
||
|
|
Simplifica significativamente o serviço de bibliotecário
|
||
|
|
|
||
|
|
#### Mudança 10: Serviços de Armazenamento - Gerenciamento de Coleção Baseado em Configuração ✅ CONCLUÍDO
|
||
|
|
|
||
|
|
**Status:** Todos os 11 backends de armazenamento foram migrados para usar `CollectionConfigHandler`.
|
||
|
|
|
||
|
|
**Serviços Afetados (11 no total):**
|
||
|
|
Embeddings de documentos: milvus, pinecone, qdrant
|
||
|
|
Embeddings de grafos: milvus, pinecone, qdrant
|
||
|
|
Armazenamento de objetos: cassandra
|
||
|
|
Armazenamento de triplas: cassandra, falkordb, memgraph, neo4j
|
||
|
|
|
||
|
|
**Arquivos:**
|
||
|
|
`trustgraph-flow/trustgraph/storage/doc_embeddings/milvus/write.py`
|
||
|
|
`trustgraph-flow/trustgraph/storage/doc_embeddings/pinecone/write.py`
|
||
|
|
`trustgraph-flow/trustgraph/storage/doc_embeddings/qdrant/write.py`
|
||
|
|
`trustgraph-flow/trustgraph/storage/graph_embeddings/milvus/write.py`
|
||
|
|
`trustgraph-flow/trustgraph/storage/graph_embeddings/pinecone/write.py`
|
||
|
|
`trustgraph-flow/trustgraph/storage/graph_embeddings/qdrant/write.py`
|
||
|
|
`trustgraph-flow/trustgraph/storage/objects/cassandra/write.py`
|
||
|
|
`trustgraph-flow/trustgraph/storage/triples/cassandra/write.py`
|
||
|
|
`trustgraph-flow/trustgraph/storage/triples/falkordb/write.py`
|
||
|
|
`trustgraph-flow/trustgraph/storage/triples/memgraph/write.py`
|
||
|
|
`trustgraph-flow/trustgraph/storage/triples/neo4j/write.py`
|
||
|
|
|
||
|
|
**Padrão de Implementação (todos os serviços):**
|
||
|
|
|
||
|
|
1. **Registrar o manipulador de configuração em `__init__`:**
|
||
|
|
```python
|
||
|
|
# Add after AsyncProcessor initialization
|
||
|
|
self.register_config_handler(self.on_collection_config)
|
||
|
|
self.known_collections = set() # Track (user, collection) tuples
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **Implementar o gerenciador de configuração:**
|
||
|
|
```python
|
||
|
|
async def on_collection_config(self, config, version):
|
||
|
|
"""Handle collection configuration updates"""
|
||
|
|
logger.info(f"Collection config version: {version}")
|
||
|
|
|
||
|
|
if "collections" not in config:
|
||
|
|
return
|
||
|
|
|
||
|
|
# Parse collections from config
|
||
|
|
# Key format: "user:collection" in config["collections"]
|
||
|
|
config_collections = set()
|
||
|
|
for key in config["collections"].keys():
|
||
|
|
if ":" in key:
|
||
|
|
user, collection = key.split(":", 1)
|
||
|
|
config_collections.add((user, collection))
|
||
|
|
|
||
|
|
# Determine changes
|
||
|
|
to_create = config_collections - self.known_collections
|
||
|
|
to_delete = self.known_collections - config_collections
|
||
|
|
|
||
|
|
# Create new collections (idempotent)
|
||
|
|
for user, collection in to_create:
|
||
|
|
try:
|
||
|
|
await self.create_collection_internal(user, collection)
|
||
|
|
self.known_collections.add((user, collection))
|
||
|
|
logger.info(f"Created collection: {user}/{collection}")
|
||
|
|
except Exception as e:
|
||
|
|
logger.error(f"Failed to create {user}/{collection}: {e}")
|
||
|
|
|
||
|
|
# Delete removed collections (idempotent)
|
||
|
|
for user, collection in to_delete:
|
||
|
|
try:
|
||
|
|
await self.delete_collection_internal(user, collection)
|
||
|
|
self.known_collections.discard((user, collection))
|
||
|
|
logger.info(f"Deleted collection: {user}/{collection}")
|
||
|
|
except Exception as e:
|
||
|
|
logger.error(f"Failed to delete {user}/{collection}: {e}")
|
||
|
|
```
|
||
|
|
|
||
|
|
3. **Inicialize as coleções conhecidas na inicialização:**
|
||
|
|
```python
|
||
|
|
async def start(self):
|
||
|
|
"""Start the processor"""
|
||
|
|
await super().start()
|
||
|
|
await self.sync_known_collections()
|
||
|
|
|
||
|
|
async def sync_known_collections(self):
|
||
|
|
"""Query backend to populate known_collections set"""
|
||
|
|
# Backend-specific implementation:
|
||
|
|
# - Milvus/Pinecone/Qdrant: List collections/indexes matching naming pattern
|
||
|
|
# - Cassandra: Query keyspaces or collection metadata
|
||
|
|
# - Neo4j/Memgraph/FalkorDB: Query CollectionMetadata nodes
|
||
|
|
pass
|
||
|
|
```
|
||
|
|
|
||
|
|
4. **Refatore os métodos de tratamento existentes:**
|
||
|
|
```python
|
||
|
|
# Rename and remove response sending:
|
||
|
|
# handle_create_collection → create_collection_internal
|
||
|
|
# handle_delete_collection → delete_collection_internal
|
||
|
|
|
||
|
|
async def create_collection_internal(self, user, collection):
|
||
|
|
"""Create collection (idempotent)"""
|
||
|
|
# Same logic as current handle_create_collection
|
||
|
|
# But remove response producer calls
|
||
|
|
# Handle "already exists" gracefully
|
||
|
|
pass
|
||
|
|
|
||
|
|
async def delete_collection_internal(self, user, collection):
|
||
|
|
"""Delete collection (idempotent)"""
|
||
|
|
# Same logic as current handle_delete_collection
|
||
|
|
# But remove response producer calls
|
||
|
|
# Handle "not found" gracefully
|
||
|
|
pass
|
||
|
|
```
|
||
|
|
|
||
|
|
5. **Remover a infraestrutura de gerenciamento de armazenamento:**
|
||
|
|
Remover a configuração e inicialização de `self.storage_request_consumer`
|
||
|
|
Remover a configuração de `self.storage_response_producer`
|
||
|
|
Remover o método de dispatcher de `on_storage_management`
|
||
|
|
Remover as métricas para o gerenciamento de armazenamento
|
||
|
|
Remover as importações: `StorageManagementRequest`, `StorageManagementResponse`
|
||
|
|
|
||
|
|
**Considerações Específicas para o Backend:**
|
||
|
|
|
||
|
|
**Bancos de dados vetoriais (Milvus, Pinecone, Qdrant):** Rastrear a lógica `(user, collection)` em `known_collections`, mas pode criar múltiplas coleções de backend por dimensão. Continuar o padrão de criação preguiçosa. As operações de exclusão devem remover todas as variantes de dimensão.
|
||
|
|
|
||
|
|
**Cassandra Objects:** As coleções são propriedades de linha, não estruturas. Rastrear informações no nível do keyspace.
|
||
|
|
|
||
|
|
**Bancos de dados de grafos (Neo4j, Memgraph, FalkorDB):** Consultar nós `CollectionMetadata` na inicialização. Criar/excluir nós de metadados na sincronização.
|
||
|
|
|
||
|
|
**Cassandra Triples:** Usar a API `KnowledgeGraph` para operações de coleção.
|
||
|
|
|
||
|
|
**Pontos-Chave do Design:**
|
||
|
|
|
||
|
|
**Consistência eventual:** Não há mecanismo de solicitação/resposta, o envio de configuração é transmitido.
|
||
|
|
**Idempotência:** Todas as operações de criação/exclusão devem ser seguras para serem repetidas.
|
||
|
|
**Tratamento de erros:** Registrar erros, mas não bloquear as atualizações de configuração.
|
||
|
|
**Autorreparação:** As operações com falha serão repetidas na próxima atualização de configuração.
|
||
|
|
**Formato da chave da coleção:** `"user:collection"` em `config["collections"]`
|
||
|
|
|
||
|
|
#### Mudança 11: Atualizar o Esquema da Coleção - Remover Timestamps
|
||
|
|
**Arquivo:** `trustgraph-base/trustgraph/schema/services/collection.py`
|
||
|
|
|
||
|
|
**Modificar CollectionMetadata (Linhas 13-21):**
|
||
|
|
Remover os campos `created_at` e `updated_at`:
|
||
|
|
```python
|
||
|
|
class CollectionMetadata(Record):
|
||
|
|
user = String()
|
||
|
|
collection = String()
|
||
|
|
name = String()
|
||
|
|
description = String()
|
||
|
|
tags = Array(String())
|
||
|
|
# Remove: created_at = String()
|
||
|
|
# Remove: updated_at = String()
|
||
|
|
```
|
||
|
|
|
||
|
|
**Modificar CollectionManagementRequest (linhas 25-47):**
|
||
|
|
Remover campos de timestamp:
|
||
|
|
```python
|
||
|
|
class CollectionManagementRequest(Record):
|
||
|
|
operation = String()
|
||
|
|
user = String()
|
||
|
|
collection = String()
|
||
|
|
timestamp = String()
|
||
|
|
name = String()
|
||
|
|
description = String()
|
||
|
|
tags = Array(String())
|
||
|
|
# Remove: created_at = String()
|
||
|
|
# Remove: updated_at = String()
|
||
|
|
tag_filter = Array(String())
|
||
|
|
limit = Integer()
|
||
|
|
```
|
||
|
|
|
||
|
|
**Justificativa:**
|
||
|
|
Os carimbos de data e hora não agregam valor para coleções.
|
||
|
|
O serviço de configuração mantém seu próprio rastreamento de versão.
|
||
|
|
Simplifica o esquema e reduz o armazenamento.
|
||
|
|
|
||
|
|
#### Benefícios da Migração do Serviço de Configuração
|
||
|
|
|
||
|
|
1. ✅ **Elimina tópicos de gerenciamento de armazenamento codificados** - Resolve o bloqueio multi-inquilino.
|
||
|
|
2. ✅ **Coordenação mais simples** - Sem espera assíncrona complexa por 4 ou mais respostas de armazenamento.
|
||
|
|
3. ✅ **Consistência eventual** - Os serviços de armazenamento são atualizados independentemente por meio de push de configuração.
|
||
|
|
4. ✅ **Melhor confiabilidade** - Push de configuração persistente versus solicitação/resposta não persistente.
|
||
|
|
5. ✅ **Modelo de configuração unificado** - Coleções tratadas como configuração.
|
||
|
|
6. ✅ **Reduz a complexidade** - Remove aproximadamente 300 linhas de código de coordenação.
|
||
|
|
7. ✅ **Pronto para multi-inquilino** - A configuração já suporta o isolamento de inquilinos por meio de keyspace.
|
||
|
|
8. ✅ **Rastreamento de versão** - O mecanismo de versão do serviço de configuração fornece um histórico de auditoria.
|
||
|
|
|
||
|
|
## Notas de Implementação
|
||
|
|
|
||
|
|
### Compatibilidade com versões anteriores
|
||
|
|
|
||
|
|
**Alterações de parâmetros:**
|
||
|
|
As alterações de nome dos parâmetros da CLI são alterações disruptivas, mas aceitáveis (o recurso atualmente não está funcional).
|
||
|
|
Os serviços funcionam sem parâmetros (use os padrões).
|
||
|
|
Keyspaces padrão preservados: "config", "knowledge", "librarian".
|
||
|
|
Fila padrão: `persistent://tg/config/config`
|
||
|
|
|
||
|
|
**Gerenciamento de coleções:**
|
||
|
|
**Alteração disruptiva:** A tabela de coleções foi removida do keyspace librarian.
|
||
|
|
**Nenhuma migração de dados fornecida** - aceitável para esta fase.
|
||
|
|
A API de coleção externa não foi alterada (operações de listagem, atualização e exclusão).
|
||
|
|
O formato de metadados da coleção foi simplificado (os carimbos de data e hora foram removidos).
|
||
|
|
|
||
|
|
### Requisitos de teste
|
||
|
|
|
||
|
|
**Teste de parâmetros:**
|
||
|
|
1. Verificar se o parâmetro `--config-push-queue` funciona no serviço graph-embeddings.
|
||
|
|
2. Verificar se o parâmetro `--config-push-queue` funciona no serviço text-completion.
|
||
|
|
3. Verificar se o parâmetro `--config-push-queue` funciona no serviço de configuração.
|
||
|
|
4. Verificar se o parâmetro `--cassandra-keyspace` funciona para o serviço de configuração.
|
||
|
|
5. Verificar se o parâmetro `--cassandra-keyspace` funciona para o serviço cores.
|
||
|
|
6. Verificar se o parâmetro `--cassandra-keyspace` funciona para o serviço librarian.
|
||
|
|
7. Verificar se os serviços funcionam sem parâmetros (usa os padrões).
|
||
|
|
8. Verificar a implantação multi-inquilino com nomes de fila e keyspace personalizados.
|
||
|
|
|
||
|
|
**Teste de gerenciamento de coleções:**
|
||
|
|
9. Verificar a operação `list-collections` por meio do serviço de configuração.
|
||
|
|
10. Verificar se `update-collection` cria/atualiza na tabela de configuração.
|
||
|
|
11. Verificar se `delete-collection` remove da tabela de configuração.
|
||
|
|
12. Verificar se o push de configuração é acionado em atualizações de coleção.
|
||
|
|
13. Verificar se a filtragem de tags funciona com armazenamento baseado em configuração.
|
||
|
|
14. Verificar se as operações de coleção funcionam sem campos de carimbo de data e hora.
|
||
|
|
|
||
|
|
### Exemplo de implantação multi-inquilino
|
||
|
|
```bash
|
||
|
|
# Tenant: tg-dev
|
||
|
|
graph-embeddings \
|
||
|
|
-p pulsar+ssl://broker:6651 \
|
||
|
|
--pulsar-api-key <KEY> \
|
||
|
|
--config-push-queue persistent://tg-dev/config/config
|
||
|
|
|
||
|
|
config-service \
|
||
|
|
-p pulsar+ssl://broker:6651 \
|
||
|
|
--pulsar-api-key <KEY> \
|
||
|
|
--config-push-queue persistent://tg-dev/config/config \
|
||
|
|
--cassandra-keyspace tg_dev_config
|
||
|
|
```
|
||
|
|
|
||
|
|
## Análise de Impacto
|
||
|
|
|
||
|
|
### Serviços Afetados pela Alteração 1-2 (Renomeação de Parâmetro da CLI)
|
||
|
|
Todos os serviços que herdam de AsyncProcessor ou FlowProcessor:
|
||
|
|
config-service
|
||
|
|
cores-service
|
||
|
|
librarian-service
|
||
|
|
graph-embeddings
|
||
|
|
document-embeddings
|
||
|
|
text-completion-* (todos os provedores)
|
||
|
|
extract-* (todos os extractors)
|
||
|
|
query-* (todos os serviços de consulta)
|
||
|
|
retrieval-* (todos os serviços RAG)
|
||
|
|
storage-* (todos os serviços de armazenamento)
|
||
|
|
E mais 20 serviços
|
||
|
|
|
||
|
|
### Serviços Afetados pelas Alterações 3-6 (Keyspace do Cassandra)
|
||
|
|
config-service
|
||
|
|
cores-service
|
||
|
|
librarian-service
|
||
|
|
|
||
|
|
### Serviços Afetados pelas Alterações 7-11 (Gerenciamento de Coleções)
|
||
|
|
|
||
|
|
**Alterações Imediatas:**
|
||
|
|
librarian-service (collection_manager.py, service.py)
|
||
|
|
tables/library.py (remoção da tabela de coleções)
|
||
|
|
schema/services/collection.py (remoção do timestamp)
|
||
|
|
|
||
|
|
**Alterações Concluídas (Alteração 10):** ✅
|
||
|
|
Todos os serviços de armazenamento (11 no total) - migrados para o push de configuração para atualizações de coleções via `CollectionConfigHandler`
|
||
|
|
Esquema de gerenciamento de armazenamento removido de `storage.py`
|
||
|
|
|
||
|
|
## Considerações Futuras
|
||
|
|
|
||
|
|
### Modelo de Keyspace por Usuário
|
||
|
|
|
||
|
|
Alguns serviços usam **keyspaces por usuário** dinamicamente, onde cada usuário recebe seu próprio keyspace do Cassandra:
|
||
|
|
|
||
|
|
**Serviços com keyspaces por usuário:**
|
||
|
|
1. **Triples Query Service** (`trustgraph-flow/trustgraph/query/triples/cassandra/service.py:65`)
|
||
|
|
Usa `keyspace=query.user`
|
||
|
|
2. **Objects Query Service** (`trustgraph-flow/trustgraph/query/objects/cassandra/service.py:479`)
|
||
|
|
Usa `keyspace=self.sanitize_name(user)`
|
||
|
|
3. **KnowledgeGraph Direct Access** (`trustgraph-flow/trustgraph/direct/cassandra_kg.py:18`)
|
||
|
|
Parâmetro padrão `keyspace="trustgraph"`
|
||
|
|
|
||
|
|
**Status:** Estes **não são modificados** nesta especificação.
|
||
|
|
|
||
|
|
**Revisão Futura Necessária:**
|
||
|
|
Avaliar se o modelo de keyspace por usuário cria problemas de isolamento de locatários
|
||
|
|
Considerar se as implementações multi-locatário precisam de padrões de prefixo de keyspace (por exemplo, `tenant_a_user1`)
|
||
|
|
Revisar para possíveis colisões de ID de usuário entre locatários
|
||
|
|
Avaliar se um keyspace compartilhado único por locatário com isolamento de linha baseado em usuário é preferível
|
||
|
|
|
||
|
|
**Observação:** Isso não impede a implementação multi-locatário atual, mas deve ser revisado antes das implementações multi-locatário de produção.
|
||
|
|
|
||
|
|
## Fases de Implementação
|
||
|
|
|
||
|
|
### Fase 1: Correções de Parâmetros (Alterações 1-6)
|
||
|
|
Corrigir a nomenclatura do parâmetro `--config-push-queue`
|
||
|
|
Adicionar suporte ao parâmetro `--cassandra-keyspace`
|
||
|
|
**Resultado:** Configuração de fila e keyspace multi-locatário habilitada
|
||
|
|
|
||
|
|
### Fase 2: Migração do Gerenciamento de Coleções (Alterações 7-9, 11)
|
||
|
|
Migrar o armazenamento de coleções para o serviço de configuração
|
||
|
|
Remover a tabela de coleções do librarian
|
||
|
|
Atualizar o esquema de coleção (remover timestamps)
|
||
|
|
**Resultado:** Elimina tópicos de gerenciamento de armazenamento codificados, simplifica o librarian
|
||
|
|
|
||
|
|
### Fase 3: Atualizações do Serviço de Armazenamento (Alteração 10) ✅ CONCLUÍDA
|
||
|
|
Atualizados todos os serviços de armazenamento para usar o push de configuração para coleções via `CollectionConfigHandler`
|
||
|
|
Removida a infraestrutura de solicitação/resposta de gerenciamento de armazenamento
|
||
|
|
Removidas as definições de esquema legadas
|
||
|
|
**Resultado:** Gerenciamento de coleções baseado em configuração completo alcançado
|
||
|
|
|
||
|
|
## Referências
|
||
|
|
GitHub Issue: https://github.com/trustgraph-ai/trustgraph/issues/582
|
||
|
|
Arquivos Relacionados:
|
||
|
|
`trustgraph-base/trustgraph/base/async_processor.py`
|
||
|
|
`trustgraph-base/trustgraph/base/cassandra_config.py`
|
||
|
|
`trustgraph-base/trustgraph/schema/core/topic.py`
|
||
|
|
`trustgraph-base/trustgraph/schema/services/collection.py`
|
||
|
|
`trustgraph-flow/trustgraph/config/service/service.py`
|
||
|
|
`trustgraph-flow/trustgraph/cores/service.py`
|
||
|
|
`trustgraph-flow/trustgraph/librarian/service.py`
|
||
|
|
`trustgraph-flow/trustgraph/librarian/collection_manager.py`
|
||
|
|
`trustgraph-flow/trustgraph/tables/library.py`
|