Browse Source

Optimize

imwald
Nuša Pukšič 1 month ago
parent
commit
acc2d15216
  1. 37
      src/Command/CacheLatestArticlesCommand.php
  2. 27
      src/Command/NostrRelayPoolStatsCommand.php
  3. 20
      src/Controller/DefaultController.php
  4. 24
      src/Service/NostrClient.php
  5. 95
      src/Service/NostrRelayPool.php

37
src/Command/CacheLatestArticlesCommand.php

@ -4,6 +4,8 @@ declare(strict_types=1); @@ -4,6 +4,8 @@ declare(strict_types=1);
namespace App\Command;
use App\Service\RedisCacheService;
use App\Util\NostrKeyUtil;
use FOS\ElasticaBundle\Finder\FinderInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
@ -23,13 +25,20 @@ class CacheLatestArticlesCommand extends Command @@ -23,13 +25,20 @@ class CacheLatestArticlesCommand extends Command
private FinderInterface $finder;
private CacheItemPoolInterface $articlesCache;
private ParameterBagInterface $params;
private RedisCacheService $redisCacheService;
public function __construct(FinderInterface $finder, CacheItemPoolInterface $articlesCache, ParameterBagInterface $params)
public function __construct(
FinderInterface $finder,
CacheItemPoolInterface $articlesCache,
ParameterBagInterface $params,
RedisCacheService $redisCacheService
)
{
parent::__construct();
$this->finder = $finder;
$this->articlesCache = $articlesCache;
$this->params = $params;
$this->redisCacheService = $redisCacheService;
}
protected function execute(InputInterface $input, OutputInterface $output): int
@ -70,10 +79,34 @@ class CacheLatestArticlesCommand extends Command @@ -70,10 +79,34 @@ class CacheLatestArticlesCommand extends Command
$articles = $this->finder->find($query);
// Pre-fetch and cache author metadata for all articles
$authorPubkeys = [];
foreach ($articles as $article) {
// Debug: check what type of object we have
if ($article instanceof \App\Entity\Article) {
$pubkey = $article->getPubkey();
if ($pubkey && NostrKeyUtil::isHexPubkey($pubkey)) {
$authorPubkeys[] = $pubkey;
}
} elseif (is_object($article)) {
// Elastica result object
if (isset($article->pubkey) && NostrKeyUtil::isHexPubkey($article->pubkey)) {
$authorPubkeys[] = $article->pubkey;
} elseif (isset($article->npub) && NostrKeyUtil::isNpub($article->npub)) {
$authorPubkeys[] = NostrKeyUtil::npubToHex($article->npub);
}
}
}
$authorPubkeys = array_unique($authorPubkeys);
$output->writeln('<comment>Pre-fetching metadata for ' . count($authorPubkeys) . ' authors...</comment>');
$authorsMetadata = $this->redisCacheService->getMultipleMetadata($authorPubkeys);
$output->writeln('<comment>Fetched ' . count($authorsMetadata) . ' author profiles.</comment>');
$cacheItem->set($articles);
$cacheItem->expiresAfter(3600); // Cache for 1 hour
$this->articlesCache->save($cacheItem);
$output->writeln('<info>Cached ' . count($articles) . ' articles.</info>');
$output->writeln('<info>Cached ' . count($articles) . ' articles with author metadata.</info>');
// } else {
// $output->writeln('<comment>Cache already exists for key: ' . $cacheKey . '</comment>');
// }

27
src/Command/NostrRelayPoolStatsCommand.php

@ -26,10 +26,35 @@ class NostrRelayPoolStatsCommand extends Command @@ -26,10 +26,35 @@ class NostrRelayPoolStatsCommand extends Command
$io = new SymfonyStyle($input, $output);
$stats = $this->relayPool->getStats();
$localRelay = $this->relayPool->getLocalRelay();
$defaultRelays = $this->relayPool->getDefaultRelays();
$io->title('Nostr Relay Pool Statistics');
$io->section('Overview');
$io->section('Configuration');
$io->table(
['Setting', 'Value'],
[
['Local Relay', $localRelay ?: '(not configured)'],
['Default Relays', count($defaultRelays)],
]
);
if (!empty($defaultRelays)) {
$io->section('Default Relay List (Priority Order)');
$rows = [];
foreach ($defaultRelays as $index => $relay) {
$isLocal = $localRelay && $relay === $localRelay;
$rows[] = [
$index + 1,
$relay,
$isLocal ? '✓ Local' : 'Public'
];
}
$io->table(['Priority', 'Relay URL', 'Type'], $rows);
}
$io->section('Connection Pool');
$io->table(
['Metric', 'Value'],
[

20
src/Controller/DefaultController.php

@ -61,14 +61,16 @@ class DefaultController extends AbstractController @@ -61,14 +61,16 @@ class DefaultController extends AbstractController
CacheItemPoolInterface $articlesCache
): Response
{
set_time_limit(300);
ini_set('max_execution_time', '300');
$env = $this->getParameter('kernel.environment');
// Reuse previous latest list cache key to show same set as old 'latest'
$cacheKey = 'latest_articles_list_' . $env;
$cacheItem = $articlesCache->getItem($cacheKey);
if (!$cacheItem->isHit()) {
// Fallback: run query now if command hasn't populated cache yet
set_time_limit(300);
ini_set('max_execution_time', '300');
$key = new Key();
$excludedPubkeys = [
$key->convertToHex('npub1etsrcjz24fqewg4zmjze7t5q8c6rcwde5zdtdt4v3t3dz2navecscjjz94'),
@ -81,8 +83,6 @@ class DefaultController extends AbstractController @@ -81,8 +83,6 @@ class DefaultController extends AbstractController
$key->convertToHex('npub14l5xklll5vxzrf6hfkv8m6n2gqevythn5pqc6ezluespah0e8ars4279ss'),
];
if (!$cacheItem->isHit()) {
// Fallback: run query now if command hasn't populated cache yet
$boolQuery = new BoolQuery();
$boolQuery->addMustNot(new Query\Terms('pubkey', $excludedPubkeys));
$query = new Query($boolQuery);
@ -99,14 +99,24 @@ class DefaultController extends AbstractController @@ -99,14 +99,24 @@ class DefaultController extends AbstractController
$articles = $cacheItem->get();
// Fetch author metadata - this is now much faster because
// metadata is pre-cached by the CacheLatestArticlesCommand
$authorPubkeys = [];
foreach ($articles as $article) {
if ($article instanceof \App\Entity\Article) {
$pubkey = $article->getPubkey();
if ($pubkey && NostrKeyUtil::isHexPubkey($pubkey)) {
$authorPubkeys[] = $pubkey;
}
} elseif (is_object($article)) {
// Elastica result object fallback
if (isset($article->pubkey) && NostrKeyUtil::isHexPubkey($article->pubkey)) {
$authorPubkeys[] = $article->pubkey;
} elseif (isset($article->npub) && NostrKeyUtil::isNpub($article->npub)) {
$authorPubkeys[] = NostrKeyUtil::npubToHex($article->npub);
}
}
}
$authorPubkeys = array_unique($authorPubkeys);
$authorsMetadata = $redisCacheService->getMultipleMetadata($authorPubkeys);

24
src/Service/NostrClient.php

@ -129,13 +129,13 @@ class NostrClient @@ -129,13 +129,13 @@ class NostrClient
*/
public function getPubkeyMetadata($pubkey): \stdClass
{
// Use relay pool for all relays including purplepag.es
$relayUrls = [
// Use relay pool for all relays including purplepag.es, with local relay prioritized
$relayUrls = $this->relayPool->ensureLocalRelayInList([
'wss://theforest.nostr1.com',
'wss://nostr.land',
'wss://relay.primal.net',
'wss://purplepag.es'
];
]);
$relaySet = $this->createRelaySet($relayUrls);
$this->logger->info('Getting metadata for pubkey ' . $pubkey );
@ -168,6 +168,9 @@ class NostrClient @@ -168,6 +168,9 @@ class NostrClient
if (empty($relays)) {
$key = new Key();
$relays = $this->getTopReputableRelaysForAuthor($key->convertPublicKeyToBech32($event->getPublicKey()), 5);
} else {
// Ensure local relay is included when publishing
$relays = $this->relayPool->ensureLocalRelayInList($relays);
}
// Use relay pool instead of creating new Relay instances
@ -206,8 +209,9 @@ class NostrClient @@ -206,8 +209,9 @@ class NostrClient
}
$requestMessage = new RequestMessage($subscriptionId, [$filter]);
// Create relay set from all reputable relays on record
$relaySet = $this->createRelaySet(self::REPUTABLE_RELAYS);
// Create relay set from all reputable relays on record, with local relay prioritized
$relayUrls = $this->relayPool->ensureLocalRelayInList(self::REPUTABLE_RELAYS);
$relaySet = $this->createRelaySet($relayUrls);
$request = new Request($relaySet, $requestMessage);
@ -281,6 +285,9 @@ class NostrClient @@ -281,6 +285,9 @@ class NostrClient
{
$this->logger->info('Getting event by ID', ['event_id' => $eventId, 'relays' => $relays]);
// Ensure local relay is included in the relay list
$relays = $this->relayPool->ensureLocalRelayInList($relays);
// Merge relays with reputable ones
$allRelays = array_unique(array_merge($relays, self::REPUTABLE_RELAYS));
// Loop over reputable relays and bail as soon as you get a valid event back
@ -341,6 +348,11 @@ class NostrClient @@ -341,6 +348,11 @@ class NostrClient
$this->logger->info('Getting events by IDs', ['event_ids' => $eventIds, 'relays' => $relays]);
// Ensure local relay is included
if (!empty($relays)) {
$relays = $this->relayPool->ensureLocalRelayInList($relays);
}
// Use provided relays or default if empty
$relaySet = empty($relays) ? $this->defaultRelaySet : $this->createRelaySet($relays);
@ -578,7 +590,7 @@ class NostrClient @@ -578,7 +590,7 @@ class NostrClient
$parts = explode(':', $coordinate, 3);
$pubkey = $parts[1];
// Get author's relays for better chances of finding zaps
// Get author's relays for better chances of finding zaps (includes local relay)
$authorRelays = $this->getTopReputableRelaysForAuthor($pubkey);
$relaySet = $this->createRelaySet($authorRelays);

95
src/Service/NostrRelayPool.php

@ -23,7 +23,10 @@ class NostrRelayPool @@ -23,7 +23,10 @@ class NostrRelayPool
private array $lastConnected = [];
/** @var array<string> */
private array $defaultRelays = [
private array $defaultRelays = [];
/** @var array<string> Default public relays to fall back to */
private const PUBLIC_RELAYS = [
'wss://theforest.nostr1.com',
'wss://nostr.land',
'wss://relay.primal.net',
@ -39,11 +42,39 @@ class NostrRelayPool @@ -39,11 +42,39 @@ class NostrRelayPool
private readonly string $nostrDefaultRelay,
array $defaultRelays = []
) {
$relayList = $defaultRelays ?: $this->defaultRelays;
if ($this->nostrDefaultRelay && !in_array($this->nostrDefaultRelay, $relayList, true)) {
array_unshift($relayList, $this->nostrDefaultRelay);
// Build relay list: prioritize local relay, then custom relays, then public relays
$relayList = [];
// Add local relay first if configured
if ($this->nostrDefaultRelay) {
$relayList[] = $this->nostrDefaultRelay;
$this->logger->info('NostrRelayPool: Local relay configured as primary', [
'relay' => $this->nostrDefaultRelay
]);
}
// Add custom relays (excluding local relay if already added)
if (!empty($defaultRelays)) {
foreach ($defaultRelays as $relay) {
if (!in_array($relay, $relayList, true)) {
$relayList[] = $relay;
}
}
} else {
// Use public relays as fallback
foreach (self::PUBLIC_RELAYS as $relay) {
if (!in_array($relay, $relayList, true)) {
$relayList[] = $relay;
}
}
}
$this->defaultRelays = $relayList;
$this->logger->info('NostrRelayPool initialized with relay priority', [
'relay_count' => count($this->defaultRelays),
'relays' => $this->defaultRelays
]);
}
/**
@ -88,7 +119,7 @@ class NostrRelayPool @@ -88,7 +119,7 @@ class NostrRelayPool
}
/**
* Get multiple relay connections, prioritizing default relay
* Get multiple relay connections, prioritizing local relay (primary default relay)
*
* @param array $relayUrls
* @return array<Relay>
@ -96,26 +127,26 @@ class NostrRelayPool @@ -96,26 +127,26 @@ class NostrRelayPool
public function getRelays(array $relayUrls): array
{
$relays = [];
$defaultRelay = $this->defaultRelays[0] ?? null;
$localRelay = $this->nostrDefaultRelay ? $this->normalizeRelayUrl($this->nostrDefaultRelay) : null;
$relayUrlsNormalized = array_map([$this, 'normalizeRelayUrl'], $relayUrls);
$defaultRelayNormalized = $defaultRelay ? $this->normalizeRelayUrl($defaultRelay) : null;
// Try default relay first if present in requested URLs
if ($defaultRelayNormalized && in_array($defaultRelayNormalized, $relayUrlsNormalized, true)) {
// Always try local relay first if configured, regardless of whether it's in the requested URLs
if ($localRelay) {
try {
$relays[] = $this->getRelay($defaultRelayNormalized);
$relays[] = $this->getRelay($localRelay);
$this->logger->debug('Using local relay (priority)', ['relay' => $localRelay]);
} catch (\Throwable $e) {
$this->logger->warning('Default relay unavailable, falling back to others', [
'relay' => $defaultRelayNormalized,
$this->logger->warning('Local relay unavailable, falling back to others', [
'relay' => $localRelay,
'error' => $e->getMessage()
]);
}
}
// Add other relays except the default
// Add other relays except the local relay
foreach ($relayUrlsNormalized as $url) {
if ($url === $defaultRelayNormalized) {
continue;
if ($url === $localRelay) {
continue; // Skip local relay as we already added it
}
try {
$relays[] = $this->getRelay($url);
@ -126,6 +157,7 @@ class NostrRelayPool @@ -126,6 +157,7 @@ class NostrRelayPool
]);
}
}
return $relays;
}
@ -340,4 +372,37 @@ class NostrRelayPool @@ -340,4 +372,37 @@ class NostrRelayPool
{
return $this->defaultRelays;
}
/**
* Get local relay URL if configured
*/
public function getLocalRelay(): ?string
{
return $this->nostrDefaultRelay ?: null;
}
/**
* Ensure local relay is included in a list of relay URLs
* Adds local relay at the beginning if configured and not already in the list
*/
public function ensureLocalRelayInList(array $relayUrls): array
{
if (!$this->nostrDefaultRelay) {
return $relayUrls;
}
$normalizedLocal = $this->normalizeRelayUrl($this->nostrDefaultRelay);
$normalizedUrls = array_map([$this, 'normalizeRelayUrl'], $relayUrls);
// If local relay not in list, add it at the beginning
if (!in_array($normalizedLocal, $normalizedUrls, true)) {
array_unshift($relayUrls, $this->nostrDefaultRelay);
$this->logger->debug('Added local relay to relay list', [
'local_relay' => $this->nostrDefaultRelay,
'total_relays' => count($relayUrls)
]);
}
return $relayUrls;
}
}

Loading…
Cancel
Save