{"id":32363536,"url":"https://github.com/neylsoncrepalde/kafka-exercise","last_synced_at":"2025-10-24T16:59:41.464Z","repository":{"id":48410201,"uuid":"355400394","full_name":"neylsoncrepalde/kafka-exercise","owner":"neylsoncrepalde","description":"An exercise running Kafka, Kafka Connect, PostgreSQL, Superset and AWS S3","archived":false,"fork":false,"pushed_at":"2021-04-29T03:58:10.000Z","size":431,"stargazers_count":18,"open_issues_count":1,"forks_count":13,"subscribers_count":2,"default_branch":"main","last_synced_at":"2023-03-08T03:16:50.386Z","etag":null,"topics":["aws","aws-s3","kafka","kafka-connect","postgresql","superset"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/neylsoncrepalde.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-04-07T03:27:11.000Z","updated_at":"2023-01-29T01:05:23.000Z","dependencies_parsed_at":"2022-08-27T04:50:13.471Z","dependency_job_id":null,"html_url":"https://github.com/neylsoncrepalde/kafka-exercise","commit_stats":null,"previous_names":[],"tags_count":null,"template":null,"template_full_name":null,"purl":"pkg:github/neylsoncrepalde/kafka-exercise","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neylsoncrepalde%2Fkafka-exercise","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neylsoncrepalde%2Fkafka-exercise/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neylsoncrepalde%2Fkafka-exercise/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neylsoncrepalde%2Fkafka-exercise/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neylsoncrepalde","download_url":"https://codeload.github.com/neylsoncrepalde/kafka-exercise/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neylsoncrepalde%2Fkafka-exercise/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280833013,"owners_count":26398971,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-24T02:00:06.418Z","response_time":73,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws","aws-s3","kafka","kafka-connect","postgresql","superset"],"created_at":"2025-10-24T16:59:30.621Z","updated_at":"2025-10-24T16:59:41.454Z","avatar_url":"https://github.com/neylsoncrepalde.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Kafka Exercise\n\n### Neylson Crepalde e Mariana Medeiros\n\n#### Abril de 2021\n\nExercício para praticar uma pipeline de Streaming de Dados com Kafka. Vamos implementar a seguinte arquitetura:\n\nIntegração do Kafka com uma database (postgresql) usando *kafka connect*, processamento de dados em streaming com *ksqlDB* e entrega em data lake com *kafka connect*. Para consultas simples, vamos utilizar o Superset (embora você possa escolher o cliente SQL de sua preferência). Todos os serviços que compõem o kafka, o superset e a database PostgreSQL que servirá de fonte serão implantadas com `docker-compose`.\n\n![Arquitetura](img/kafka_exercise_arch.png)\n\n---\n\n# Passo a passo para execução\n\n## 1 - Pré-requisitos\n\n- Docker\n- docker-compose\n- Uma conta AWS free tier\n\n## 2 - Configurar o arquivo .env_kafka_connect\n\nVocê deve criar um arquivo `.env_kafka_connect` para cadastrar as chaves de sua conta aws como variáveis de ambiente que serão injetadas dentro do container do kafka connect. O arquivo deve ser conforme o modelo:\n\n```\nAWS_ACCESS_KEY_ID=xxxxxxxxxxxxxxxxxxx\nAWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n```\n\n## 3 - Buildar a imagem do kafka-connect\nApós clonar o repositório, mude para a pasta `custom-kafka-connectors-image`, execute o seguinte comando:\n\n```bash\ngit clone https://github.com/neylsoncrepalde/kafka-exercise.git\ncd kafka-exercise/custom-kafka-connectors-image\ndocker build . -t connect-custom:1.0.0\n```\nUma nova imagem com o nome `connect-custom` e tag ` 1.0.0` será criada. Essa é a imagem que nosso serviço `connect` dentro do `docker-compose.yml` irá utilizar, com os conectores que precisaremos instalados.\n\n## 4 - Subir a plataforma Confluent no docker-compose + PostgreSQL\n\nNo arquivo `docker-compose.yml` estamos subindo toda a estrutura da plataforma Confluent junto com a database PostgreSQL que servirá de fonte. Para isso, vamos entrar na pasta e subir a estrutura.\n\n```bash\ncd ..\ndocker-compose up -d\n```\n\nEste código yml também cria uma rede externa na qual o superset será implantado para que todos os serviços consigam \"se enxergar\".\n\n## 5 - Subir o superset\n\nEm seguida vamos subir os serviços do superset. O arquivo `docker-compose-non-dev.yml` na pasta superset já disponibiliza os serviços da ferramenta na rede externa criada pelo outro arquivo.\n\n```bash\ncd superset\ndocker-compose -f docker-compose-non-dev.yml up -d\n ```\n\n\n## 6 - Executar o gerador de dados *fake*\n\nEm seguida, retorne à pasta root do projeto e execute o gerador de dados fake.\n\n```bash\ncd ..\npython make_fake_data.py\n```\n\nO módulo `make_fake_data.py` possui 4 argumentos que podem ser utilizados na linha de comando. Acrescente `--interval` para definir quantos segundos o simulador vai aguardar entre as simulações, `-n` para definir quantos casos serão simulados por vez, `--connection-string` ou `-cs` para definir uma string de conexão customizada (para reaproveitamento do módulo em outra database) e `--silent` caso não desejemos exibir os dados simulados na tela.\n\nAbaixo, a documentação do comando\n\n    usage: make_fake_data.py [-h] [--interval INTERVAL] [-n N]\n                         [--connection-string CONNECTION_STRING]\n                         [--silent [SILENT]]\n\n    Generate fake data...\n\n    optional arguments:\n    -h, --help            show this help message and exit\n    --interval INTERVAL   interval of generating fake data in seconds\n    -n N                  sample size\n    --connection-string CONNECTION_STRING, -cs CONNECTION_STRING\n                            Connection string to the database\n    --silent [SILENT]     print fake data\n\nSerá necessário executar o simulador apenas uma vez para criar a tabela na database.\n\n## 7 - Criar um tópico no Kafka\n\nVamos criar um tópico no kafka que irá armazenar os dados movidos da fonte.\n\n```bash\ndocker-compose exec broker kafka-topics --create \\\n    --bootstrap-server localhost:9092 \\\n    --partitions 1 \\\n    --replication-factor 1 \\\n    --topic psg-customers\n```\n\nO sufixo do nome do tópico deve possuir o mesmo nome da tabela cadastrado no arquivo `make_fake_data.py` caso seja necessário customizar.\n\n## 8 - Registrar os parâmetros de configuração do connector no kafka\n\nPara isso, vamos precisar de um arquivo no formato `json` contendo as configurações do conector que vamos registrar. O arquivo `connect_postgres.config` possui um exemplo de implementação. O conteúdo do arquivo está transcrito abaixo:\n\n```json\n{\n    \"name\": \"postg-connector\",\n    \"config\": {\n        \"connector.class\": \"io.confluent.connect.jdbc.JdbcSourceConnector\",\n        \"tasks.max\": 1,    \n        \"connection.url\": \"jdbc:postgresql://postgres:5432/postgres\",\n        \"connection.user\": \"postgres\",\n        \"connection.password\": \"SUA-SENHA\",\n        \"mode\": \"timestamp\",\n        \"timestamp.column.name\": \"dt_update\",\n        \"table.whitelist\": \"public.customers\",\n        \"topic.prefix\": \"psg-\",\n        \"validate.non.null\": \"false\",\n        \"poll.interval.ms\": 500\n    }\n}\n```\n\nCom o arquivo, fazemos uma chamada à API do Kafka para registrar os parâmetros:\n\n```bash\ncurl -X POST -H \"Content-Type: application/json\" \\\n    --data @connect_postgres.config http://localhost:8083/connectors\n```\n\nEste comando cria um conector que irá puxar todo o conteúdo da tabela mais todos os novos dados que forem inseridos. **Atenção**: O Kafka connect não puxa, por default, alterações feitas em registros já existentes. Puxa apenas novos registros. Para verificar se nossa configuração foi criada corretamente e o conector está ok, vamos exibir os logs.\n\n```bash\ndocker logs -f connect\n```\n\ne verifique se não há nenhuma mensagem de erro. \n\n## 9 - Iniciar um stream no ksqlDB\n\nPara iniciar o ksqlDB, fazemos\n\n```bash\ndocker-compose exec ksqldb-cli ksql http://ksqldb-server:8088\n```\n\nAntes de começar, vamos conferir se nosso tópico foi criado corretamente. Na *CLI* do ksqlDB, faça\n\n```\nksql\u003e show topics;\n```\n\n    Kafka Topic                 | Partitions | Partition Replicas \n    ---------------------------------------------------------------\n    default_ksql_processing_log | 1          | 1                  \n    psg-customers               | 1          | 1                  \n    ---------------------------------------------------------------\n\npara mostrar os tópicos criados. Para verificar se nosso conector está rodando corretamente, podemos fazer\n\n```\nksql\u003e show connectors;\n```\n\n    Connector Name  | Type   | Class                                         | Status                      \n    -------------------------------------------------------------------------------------------------------\n    psg-connector   | SOURCE | io.confluent.connect.jdbc.JdbcSourceConnector | RUNNING (1/1 tasks RUNNING) \n    -------------------------------------------------------------------------------------------------------\n\nO Status deve estar como RUNNING.\n\nOK! O Kafka agora está puxando dados da tabela e registrando no tópico `psg-customers`. Podemos conferir o fluxo de dados no tópico com\n\n```\nksql\u003e print psg-customers;\n```\n\nIsso vai exibir as mensagens como ficam registradas no tópico. Para consumir o dado de uma maneira mais interessante, podemos criar um STREAM:\n\n```\nksql\u003e create stream custstream WITH (kafka_topic='psg-customers', value_format='AVRO');\n```\n\nApós a mensagem de confirmação, podemos verificar o stream assim:\n\n```\nksql\u003e show streams;\n```\n\n    Stream Name         | Kafka Topic                 | Key Format | Value Format | Windowed \n    ------------------------------------------------------------------------------------------\n    CUSTSTREAM          | psg-customers               | KAFKA      | AVRO         | false    \n    KSQL_PROCESSING_LOG | default_ksql_processing_log | KAFKA      | JSON         | false    \n    ------------------------------------------------------------------------------------------\n\nPara fazer uma consulta rápida ao stream (apenas exibí-lo na tela), podemos fazer\n\n```\nksql\u003e select * from custstream emit changes;\n```\n\nO output dessa consulta não é dos melhores. Além de conter um número grande de colunas, dificultando a visualização, todas as colunas de data estão nos formatos INT ou BIGINT o que não é nada intuitivo para interpretação. Podemos fazer uma consulta mais enxuta e já corrigindo essas datas com o seguinte código:\n\n```\nksql\u003e select nome, telefone, email, \n\u003eDATETOSTRING(nascimento, 'yyyy-MM-dd') as dt_nascimento,\n\u003eTIMESTAMPTOSTRING(dt_update, 'yyyy-MM-dd HH:mm:ss.SSS', 'UTC') as dt_updt_conv\n\u003efrom custstream emit changes;\n```\n\nA consulta retorna a seguinte tabela:\n\n    +-------------------------+-------------------------+--------------------------+-------------------------+-------------------------+\n    |NOME                     |TELEFONE                 |EMAIL                     |DT_NASCIMENTO            |DT_UPDT_CONV             |\n    +-------------------------+-------------------------+--------------------------+-------------------------+-------------------------+\n    |Scott Johnson            |+1-475-559-2163x6531     |michelle01@example.org    |1970-01-01               |2021-04-12 23:26:03.655  |\n    |Amy Shannon              |902-547-6469             |michaelrogers@example.com |1969-12-31               |2021-04-12 23:26:04.211  |\n    |Julie Kane               |+1-817-150-3155          |austin36@example.net      |1970-01-01               |2021-04-12 23:26:04.758  |\n    |Sheri Fuller             |9634802570               |savannahduncan@example.net|1969-12-31               |2021-04-12 23:26:05.285  |\n\nBem mais interessante!\n\n## 10 - Criando uma tabela com processamento em tempo real\n\nPrimeiro, vamos criar um stream que filtra apenas as pessoas \"jovens\" (aqui definido como quem nasceu depois de 2000-01-01) e armazena esses dados no tópico `jovens`. O tópico será criado ao criar o stream.\n\n```\nksql\u003e create stream jovens WITH (kafka_topic='jovens', value_format='AVRO') AS\n\u003eselect nome, sexo, telefone, email, profissao,\n\u003eDATETOSTRING(nascimento, 'yyyy-MM-dd') as dt_nascimento,\n\u003eTIMESTAMPTOSTRING(dt_update, 'yyyy-MM-dd HH:mm:ss.SSS', 'UTC') as dt_updt\n\u003efrom custstream\n\u003eWHERE DATETOSTRING(nascimento, 'yyyy-MM-dd') \u003e= '2000-01-01'\n\u003eemit changes;\n```\n\nSe checarmos novamente as streams,\n\n```\nksql\u003e show streams;\n```\n\n    Stream Name         | Kafka Topic                 | Key Format | Value Format | Windowed \n    ------------------------------------------------------------------------------------------\n    JOVENS              | jovens                      | KAFKA      | AVRO         | false    \n    CUSTSTREAM          | psg-customers               | KAFKA      | AVRO         | false    \n    KSQL_PROCESSING_LOG | default_ksql_processing_log | KAFKA      | JSON         | false    \n    ------------------------------------------------------------------------------------------\n\nAgora, vamos criar um stream que fará a classificação das pessoas entre jovens e adultos e uma tabela que fará a contagem de jovens e adultos a cada 30 segundos.\n\n```\nksql\u003e create stream idadeclass WITH (kafka_topic='idadeclass', value_format='AVRO') AS\n\u003eselect nome, telefone, email, profissao,\n\u003eCASE\n\u003eWHEN DATETOSTRING(nascimento, 'yyyy-MM-dd') \u003e= '2000-01-01' THEN 'JOVEM'\n\u003eELSE 'ADULTO' END AS idadecat,\n\u003eTIMESTAMPTOSTRING(dt_update, 'yyyy-MM-dd HH:mm:ss.SSS', 'UTC') as dt_updt\n\u003efrom custstream\n\u003eemit changes;\n```\n\nE depois\n\n```\nksql\u003e create table idadecont WITH (kafka_topic='idadecont', value_format='AVRO') AS\n\u003eselect idadecat, count(idadecat) as contagem\n\u003efrom idadeclass\n\u003ewindow tumbling (size 30 seconds)\n\u003egroup by idadecat\n\u003eemit changes;\n```\n\nE teremos uma tabela contando cada caso de JOVEM e ADULTO para cada intervalo de 30 segundos.\n\n## 11 - Ingestão de tópicos no S3\n\nVamos agora configurar outro tipo de connector, um *sink connector* para entregar dados armazenados no tópico `jovens` no S3. Para isso, precisamos de um arquivo de configuração do conector similar ao que elaboramos antes, mas agora com outros parâmetros. Vamos nomeá-lo `connect_s3_sink_jovens.config`:\n\n```json\n{\n    \"name\": \"s3-jovens-sink\",\n    \"config\": {\n        \"connector.class\": \"io.confluent.connect.s3.S3SinkConnector\",\n        \"format.class\": \"io.confluent.connect.s3.format.parquet.ParquetFormat\",\n        \"flush.size\": 10,\n        \"schema.compatibility\": \"FULL\",\n        \"s3.bucket.name\": \"NOME_DO_BUCKET\",\n        \"s3.region\": \"REGIÃO_AWS\",\n        \"s3.object.tagging\": true,\n        \"s3.ssea.name\": \"AES256\",\n        \"topics.dir\": \"raw-data/kafka\",\n        \"storage.class\": \"io.confluent.connect.s3.storage.S3Storage\",\n        \"tasks.max\": 1,\n        \"topics\": \"jovens\"\n    }\n}\n```\n\nE vamos cadastrá-lo no cluster connect:\n\n```bash\ncurl -X POST -H \"Content-Type: application/json\" \\\n    --data @connect_s3_sink_jovens.config http://localhost:8083/connectors\n```\n\nVamos criar também uma configuração de conector para entregar dados do tópico `idadecount` para o S3 Vamos chamá-la de `connect_s3_sink_count.config`.\n\n```json\n{\n    \"name\": \"s3-contagem-sink\",\n    \"config\": {\n        \"connector.class\": \"io.confluent.connect.s3.S3SinkConnector\",\n        \"format.class\": \"io.confluent.connect.s3.format.json.JsonFormat\",\n        \"keys.format.class\": \"io.confluent.connect.s3.format.json.JsonFormat\",\n        \"schema.generator.class\": \"io.confluent.connect.storage.hive.schema.DefaultSchemaGenerator\",\n        \"flush.size\": 10,\n        \"schema.compatibility\": \"NONE\",\n        \"s3.bucket.name\": \"NOME_DO_BUCKET\",\n        \"s3.region\": \"REGIÃO_AWS\",\n        \"s3.object.tagging\": true,\n        \"s3.ssea.name\": \"AES256\",\n        \"topics.dir\": \"raw-data/kafka\",\n        \"storage.class\": \"io.confluent.connect.s3.storage.S3Storage\",\n        \"tasks.max\": 1,\n        \"topics\": \"idadecont\",\n        \"store.kafka.keys\": true\n    }\n}\n```\n\nE então, cadastrá-lo no cluster connect:\n\n```bash\ncurl -X POST -H \"Content-Type: application/json\" \\\n    --data @connect_s3_sink_count.config http://localhost:8083/connectors\n```\n\nPRONTO!! Agora os dados dos tópicos estão sendo entregues no bucket S3 definido. Vamos verificar no ksql se está tudo OK com os conectores:\n\n```\nkqsl\u003e show connectors;\n```\n\n---\n\n**Parabéns**!! Você acabou de concluir o seu pipeline de processamento de dados em tempo real usando a plataforma Confluent no docker-compose!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneylsoncrepalde%2Fkafka-exercise","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneylsoncrepalde%2Fkafka-exercise","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneylsoncrepalde%2Fkafka-exercise/lists"}