«phpClub» — архив тем ("тредов"), посвящённых изучению PHP и веб-технологий.
someApprentice 2020/02/23 10:32:59  №1612400 1
https://github.com/someApprentice/Crypter

Написал часть кода с шифрованием и использованием библиотеки openpgp.js. Приложение получилось нагруженным, и на дешифровку нескольких сообщений сразу (например при подхвате связки из 20 сообщений с бэкенда) уходит от 4 до 8 секунд. Это не очень userfriendly, и я принимаю решение создать секретные чаты, как в телеграме — только в них сообщения будут шифроваться, и они будут создаваться по запросу пользователя.

Изначально я хотел шифровать приватный ключ хэшом (SHA-512) от пароля и сохранять его в локальном хранилище под ключом shadow, и затем дешифровать им приватный ключ каждый раз при инициализации приложения, а ключ сохранять в памяти. Но выяснилось, что дешифрованый ключ переведённый в ASCII-armor имеет другую структуру/контент в отличие от зашифрованного. Поэтому, я принял решение шифровать ключ простым паролем и просто сохранить дешифрованный ключ в IndexedDB при залогинивании.

Код проверять не нужно, так как обо всех ошибках я, скорее всего, знаю, и я собираюсь отрефкторить фронтенд код. Но то что мне хотелось бы знать, и мы хотели оставить это на последнее, это оптимизация.

Я никогда с этим не сталкивался и это будет для меня совершенно новый опыт. Поэтому, я снова опишу схему БД, со всеми комментариями и мыслями, чтобы проще было сориентироваться.

User:
uuid,
email,
name,
hash,
private_key,
public_key,
revocation_certificate,
last_seen

// Нужно добавить ещё urlname, которое будет уникальным идентификатором пользователя по URL запросу (прим. @alice).

// Может быть ещё нужно добавить поле online: BOOLEAN


Conference:
uuid,
updated, // время когда конференция последний раз обновилась (появилось новое сообщение)

// Нужно добавить поле type: ENUM('private', 'public', 'secret')

// Нужно добавить поле last_message, чтобы в интерфейсе можно было вывести его, а так же чтобы можно было отсортировать их по последнему сообщению, а не по колонке update. Колонку update можно будет удалить.


// Содержит в себе ссылки на конференции для каждого пользователя.
// Она нужна чтобы у каждого пользователя была своя "копия" конференции. Или можно сказать своё представление конференции, т.к. она содержит специфичную для каждого пользователя информацию (такую как количество непрочитанных сообщений или количество сообщений в общем конкретно для этого пользователя)
// Изначально идея создания этой таблицы была, то что пользователь может удалить отдельно для себя эту конференцию, а у его собеседника она останется. (В популярных мессенджерах это так и работает — можно удалить конференцию либо только для себя, либо и для собеседника тоже, т.е. конференция и все сообщения в ней удаляться вообще.)
Conference_Reference:
uuid,
user,
conference,
participant, // Ссылка на собеседника, в случае приватной конференции, для публичных это поле остаётся пустым. Нужна чтобы получить конференцию по собеседнику за 1 запрос и является денормализацией (см. ниже таблицу Participants)
unread // Количество непрочитанных сообщений у данного пользователя
count // Количество сообщений в общем для данного пользователя. (Ведь сообщения, так же как и конференции, можно удалять как и только для себя, так и вообще)

// Таблица всех получателей для определённой конференции. Изначально создавалась для публичных конференций где количество получателей больше 2-ух, а для приватных, она осталась как представление конференции из всего лишь 2-ух собеседников. Для приватных конференций можно опустить.
Participant:
uuid,
conference,
user


Message:
uuid,
author,
readed
date,
type: ENUM('text/plain', 'audio/ogg', 'video/mp4') // Либо текстовое, либо голосовое, либо видео сообщение
content: TEXT // Содержит либо простой текст для текстового сообщения, либо URL на медиа-файл для голосового/видео сообщения
consumed: BOOLEAN // Определяет было ли прослушано/просмотрено аудио/видео сообщение
edited: BOOLEAN // Было ли отредактировано сообщение
readed_at: TIMESTAMP WITH TIMEZONE // Определяет когда сообщение было прочитано и нужно чтобы получить все прочитанные сообщения в момент когда клиент был в офлайне, чтобы обновить их локальном хранилище

// Нужно добавить поле edited_at


// Тоже самое что Conference_Reference (т.е. имеет ту же идею под собой)
Message_Reference:
uuid,
conference, // Нужно перенести в таблицу Message
user,
message


Message_Attachment:
uuid,
type: ENUM('image', 'audio', 'video', 'file', 'forwarding', 'reply'), // Либо изображение, аудио, видео, файл, пересылка сообщения, либо ответ на сообщение
message,
src // Содержит либо URL на файл для изображений/аудио/видео/файлов, либо uuid сообщения для пересылки/ответа


Какие запросы нужно выполнить:

1. Получить последние 20 Конференций для конкретного пользователя, вместе с последним Сообщением и вместе с Получателем если есть (в случае приватной конференции), и вместе с Получателями в случае с публичной конференции:

SELECT
conference.uuid,
conference.updated
conference_reference.unread,
conference_reference.count,
participant.uuid AS participant_uuid,
participant.name AS participant_name,
participant.urlname AS participant_urlname,
participant.public_key AS participant_public_key,
participant.last_seen AS participant_last_seen,
m.uuid AS last_message_uuid,
m.author AS last_message_author,
m.readed AS last_message_readed,
m.date AS last_message_date,
m.type AS last_message_type,
m.content AS last_message_content,
m.consumed AS last_message_consumed,
m.edited AS last_message_edited,
m.readed_at AS last_message_readed_at,
m.updated_at AS last_message_updated_at,
ps.uuid AS participants_uuid,
ps.name AS participants_name,
ps.urlname AS participants_urlname,
ps_public_key AS participants_public_key
ps.last_seen AS participants_last_seen
FROM conference
INNER JOIN conference_reference ON conference.uuid = conference_reference.conference
INNER JOIN user AS participant ON conference_reference.participant = participant.uuid
INNER JOIN message AS m ON conference.last_message = m.uuid
INNER JOIN participant AS pu ON pu.conference = conference.uuid
INNER JOIN user AS ps ON pu.user = ps.uuid
WHERE conference_reference.user = :uuid
ORDER BY conference.updated DESC;

Такой запрос вернёт количество строк равный количеству Конференций помноженных на количество Получателей. pic-1. Как сделать лимит на первые 20 конференций, а не на первые 20 строк (при условии что используется Doctrine)?


2. Получить Конференцию по Получателю, вместе с получателем.

Примерно такой же запрос, только выборка будет WHERE conference_reference.participant = :uuid.

3. Получить последнее 20 Сообщений для определённой конференции для конкретного пользователя, вместе с конференцией и автором.

Doctrine и DQL конечно же упрощают написание таких больших запросов как я написал выше, поэтому я напишу его:

SELECT m
FROM Crypter\Entity\Message m
INNER JOIN Crypter\Entity\MessageReference mr WITH m.uuid = mr.message
WHERE m.conference = :conference AND mr.user = :user
ORDER BY m.date DESC

4. Получить первые 20 непрочитанных Сообщений для определённой конференции для конкретного пользователя, вместе с конференцией и автором.

Примерно такой же запрос, только выборка будет помимо прочего ещё и по полю readed и author:

...AND m.readed = FALSE AND m.author != :user.

5. Получить первые 20 Сообщений начиная с данного таймстампа (получить новые сообщения), для определённой конференции для конкретного пользователя вместе с конференцией и автором

Примерно такой же запрос как и в 3-ем запросе только добавляется ещё и выборка по дате: ...AND m.date > :date

6. Получить последние 20 Сообщений начиная с данного таймстампа (получить старые сообщения)... -//-

Такой же запрос только наоборот: AND m.date < :date

7. Получить все прочитанные Сообщения с определённого момента.

Такой запрос нужно чтобы обновить локально сохранённые сообщения (в IndexedDB), которые были прочитаны пока клиент был в офлайне.

SELECT m
FROM Crypter\Entity\Message m
JOIN Crypter\Entity\MessageReference mr WITH m.uuid = mr.message
WHERE mr.user = :user AND readedAt > :date
ORDER BY m.date DESC


Думаю, что нормализованная структура определена и осталось её оптимизировать. Пожалуйста, научите меня этому.
Ответы: >>1612440 >>1612570
Аноним 2020/02/23 12:34:30  №1612570 2
>>1612400

Для мессенджера советую в README добавить скриншоты/гифки, описание основных фич (что тут есть сложного и крутого), будет хорошее демо для собеседований. Перечислить использованные технологии и библиотеки. И подключенных ботов. README сухой и краткий и не очевидно, что за ним сложный интересный проект. А может получиться хорошая реклама твоих навыков, которая выделяет тебя среди других разработчиков.

> Приложение получилось нагруженным, и на дешифровку нескольких сообщений сразу (например при подхвате связки из 20 сообщений с бэкенда) уходит от 4 до 8 секунд.

Это можно пофиксить на уровне UI:

- показываем сначала блоки-плейсхолдеры вместо сообщений
- по мере расшифровки заменяем их на текст. Создается ощущение, что все надежно зашифровано и ничего не тормозит.
- в первую очередь расшифровываем сообщения, видимые на экране, снизу вверх. Не расшифровываем то, что далеко от видимой области.
- можно кешировать в оперативной памяти последние N (например, 5000) расшифрованных сообщений для быстрого показа, когда пользователь переключается между несколькими контактами

Наконец, можно рассмотреть вариант переписать/подключить декодер на WebAssembly (заодно освоишь эту технологию), или asm.js (фаерфоксовая старая версия для выполнения машинного кода). NaCl не буду рекомендовать, так как он x86-only. Должно ускориться.

Имхо, компромисс, выбранный Телеграм - неудачный. Шифровать надо 100% сообщений. Но, конечно, это тебе решать.

> Нужно добавить ещё urlname

Это можно назвать login или slug.

> Может быть ещё нужно добавить поле online: BOOLEAN

А можно сделать отдельную таблицу online_users (uuid, last_seen). Плюс: при обновлении статуса пользователя (пришел/ушел) мы не трогаем тяжелую таблицу users, а только эту. Она будет меньше users, так как обычно онлайн только часть пользователей и так как тут мало колонок. Кто знает, может она целиком в RAM поместится. Просто даю как идею, не гарантирую, что это будет лучше.

> updated, // время когда конференция последний раз обновилась (появилось новое сообщение)

Может, last_event_time? Хотя, updated тоже норм.

> participant, // Ссылка на собеседника, в случае приватной конференции, для публичных это поле остаётся пустым.

Не лучше ли назвать partner/other_party?

> date,

Может, лучше писать send_time?

И еще по схеме БД - а не слишком ли много метаданных в открытом виде? Может, есть смысл хранить метаданные сообщения как JSON и шифровать, и хранить шифротекст в единственной колонке metadata ?

> 1. Получить последние 20 Конференций для конкретного пользователя, вместе с последним Сообщением и вместе с Получателем если есть

5 джойнов выглядит чуть страшновато. Это, наверно, кандидат на кеширование, хотя без нагрузочного теста я не скажу, быстро или медленно оно будет работать.

> Получить последние 20 Конференций для конкретного пользователя, вместе с последним Сообщением и вместе с Получателем если есть (в случае приватной конференции), и вместе с Получателями в случае с публичной конференции:

Не очень понятно, зачем тут выбирать Получателей? Мы же выбираем только сообщения, адресованные данному пользователю? Я предполагаю, что если пользователь - участник конференции, то все сообщения в ней (не написанные им) адресованы ему. Или это на случай, если пользователь состоит в чате, но как-то забанен в нем? На случай игнора отправителя?

> Такой запрос вернёт количество строк равный количеству Конференций помноженных на количество Получателей. pic-1. Как сделать лимит на первые 20 конференций, а не на первые 20 строк (при условии что используется Doctrine)?

По моему, в Доктрине это есть. Там LIMIT применяется к основной сущности запроса, но это делается за счет оборачивания запроса в какую-то сложную конструкцию. Попробуй проверить, вроде:

$qb->select('a', 'b')->from('a')->leftJoin('a.b', 'b')->setMaxResults(10);

И посмотреть, какой запрос он сформирует. Это по идее должно вернуть 10 сущностей a независимо от числа приджойненных b.

Так, по идее - группировка нужна, наверно. Либо выборка в 2 запроса: сначала список конференций, потом список последних сообщений в них.

Увы, Доктрина - не самый быстрый способ делать запросы.

> Думаю, что нормализованная структура определена и осталось её оптимизировать. Пожалуйста, научите меня этому.

- нужно освоить команду EXPLAIN и прочитать мануал по тому, как понимать ее результаты (слова для поиска: postgres analyze query plan)
- прогнать запросы через эту команду и понять, эффективно они работают или нет (я смогу подсказать что-то). В идеале мы хотим: выборка из одной таблицы с LIMIT по индексу, который оптимизирует WHERE и ORDER.
- для тестирования было бы здорово сделать генератор, который нагенерирует произвольное число пользователей, конференций, сообщений. По ним прогонять запросы по одному и параллельно для измерения реальной производительности. Для Доктрины такой генератор делается, например, на основе Faker и https://github.com/thephpleague/factory-muffin. Хорошо оптимизированный запрос выполняется тысячами в секунду.

На тестах может выясниться что-то интересное, например, могут всплыть какие-то блокировки, мешающие параллельному выполнению запросов. Так их предсказать трудно, я не смогу.

- БД работает хорошо только пока индексы помещаются в память. Размер индексов можно как-то увидеть, не помню как. А если таблица помещается в память - то вообще все чудесно. Хайлоад обычно предполагает, что все влезает в память, кроме может быть редко используемых данных. Помещаются данные в память или нет - очень важный фактор, влияющий на производительность.
- скорее всего, может понадобиться больше денормализации. Денормализовать удобнее всего то, что никогда не меняется (вроде uuid получателя сообщения). Так как иначе надо будет думать об обновлении денормализованных полей.
- может понадобиться кеширование, если денормализация и индексы не помогут. Смотри на это как на последнюю надежду, так как при кешировании появляется проблема сброса/обновления кеша и она очень сложна.
- возможно, вместо запросов с джойнами быстрее будет сделать несколько отдельных запросов.

Индексы для начинающих: https://ruhighload.com/%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81%D1%8B+%D0%B2+mysql

Если есть конкретные вопросы, уточняй.
Ответы: >>1613334
someApprentice 2020/02/24 06:35:23  №1613334 3
>>1612570
>Для мессенджера советую в README добавить скриншоты/гифки, описание основных фич (что тут есть сложного и крутого), будет хорошее демо для собеседований. Перечислить использованные технологии и библиотеки. И подключенных ботов. README сухой и краткий и не очевидно, что за ним сложный интересный проект. А может получиться хорошая реклама твоих навыков, которая выделяет тебя среди других разработчиков.

Я хочу свой стартап закончить. Если не получиться, то оформлю README, в качестве запасного плана.

Спасибо за эти слова, я часто сомневаюсь что моих навыки выделяются среди других разработчиков. Очень важно было это услышать.

>Имхо, компромисс, выбранный Телеграм - неудачный. Шифровать надо 100% сообщений. Но, конечно, это тебе решать.
Мне нужно подумать об этом.

Приведите пожалуйста аргументы почему нужно шифровать 100% сообщений?

В противовес, здесь есть недостаток - нельзя сделать поиск по сообщениям.

Это касается и метаданных, о которых вы сделали замечание ниже. Если зашифровать дату отправки сообщения, то нельзя будет отсортировать последние или первые 20 сообщений.

И поделюсь сырыми мыслями которые крутятся у меня сейчас на этот счет:

Может быть Телеграм как шифровальный мессенджер неудачен, с этим я согласен, но как мессенджер сам по себе, нужно признать, чрезвычайно удобен. Им пользуются все, и такая популярность достигнута благодаря тому, что его юзабилити привычны пользователю. Что-то не знакомое отталкивает. Нужно повторить подобный трюк и сделать это лучше - вместо самопальных алгоритмов шифрования, который использует телеграм, я уже использую проверенный OpenPGP. Осталось только повторить интерфейс.

Я вижу как предложенный вами способ может сработать. Действительно, на расшифровку одного единственного сообщения, в независимости от его размера, уходят доли секунды. Проблемы начинаются когда сообщений несколько, и их дешифровать одновременно - начинаются перегрузы в памяти и приложение начинает работать медленней (и даже фризить если не использовать Web Worker). И расшифровка сообщений по одному может решить проблему. К тому же, вы предложили как можно вывести сообщения плавно и это может выглядеть симпатично. Я проведу тесты. Но если дешифровка будет проходить всё равно медленно или это будет выглядеть некрасиво, или будут технические проблемы в реализации, придётся отказаться от этого решения.

Пока вопрос:

>- показываем сначала блоки-плейсхолдеры вместо сообщений
Как рассчитать размер плейсхолдера? Сообщения могут быть разного размера, и после дешифровки, если размер плейсхолдера статичный, то будет выглядеть немного "дёргано".


Критический, всё же, момент то что нельзя сделать поиск по сообщениям.


>> Нужно добавить ещё urlname
>
>Это можно назвать login или slug.
Или username. Я только что посмотрел в api телеграма, и это называется так https://core.telegram.org/bots/api#user


>> participant, // Ссылка на собеседника, в случае приватной конференции, для публичных это поле остаётся пустым.
>
>Не лучше ли назвать partner/other_party?
А можно назвать ещё проще и лаконичней - party? Это может подразумевать под собой как раз другую сторону:

https://translate.google.com/?source=osdd#auto/ru/party

>Noun
>③ a person or people forming one side in an agreement or dispute.

>соучастник partner, accomplice, accessory, accessary, party, associate
>участник party, participant, member, partner, participator, partaker
>сторона side, party, hand, part, way, aspect


>> date,
>
>Может, лучше писать send_time?
>
>И еще по схеме БД - а не слишком ли много метаданных в открытом виде? Может, есть смысл хранить метаданные сообщения как JSON и шифровать, и хранить шифротекст в единственной колонке metadata ?

>Может, лучше писать send_time?
А почему нельзя назвать просто и лаконично date? Разве на очевидно что это дата сообщения?

>И еще по схеме БД - а не слишком ли много метаданных в открытом виде? Может, есть смысл хранить метаданные сообщения как JSON и шифровать, и хранить шифротекст в единственной колонке metadata ?
Какие метаданные, помимо даты, вы хотели бы чтобы шифровались?

Что касается даты, как я писал выше, если её зашифровать, то нельзя будет сделать выборку по первым или последним сообщениям.


>> Получить последние 20 Конференций для конкретного пользователя, вместе с последним Сообщением и вместе с Получателем если есть (в случае приватной конференции), и вместе с Получателями в случае с публичной конференции:
>
>Не очень понятно, зачем тут выбирать Получателей? Мы же выбираем только сообщения, адресованные данному пользователю? Я предполагаю, что если пользователь - участник конференции, то все сообщения в ней (не написанные им) адресованы ему. Или это на случай, если пользователь состоит в чате, но как-то забанен в нем? На случай игнора отправителя?
Да, я как раз думал об этом, что получателей не обязательно получать, потому что единственный случай, когда нужно получить их, это когда пользователь сам захочешь посмотреть кто состоит в беседе (публичной конференции).

Это как раз решает проблему с кешированием выше и проблему с сортировкой ниже.


По поводу оптимизации:

Спасибо, что указали в каком направлении нужно двигаться, думаю я понял что нужно делать.

Задавать какие-то конкретные вопросы тяжело, и я буду задавать их и делать отчеты по мере прогресса.

Сейчас у меня есть такие вопросы:

Как тестируются параллельное выполнение запросов?
Если написать скрипт, например на PHP, который будет обращаться к БД, он будет выполнять одну команду за другой, императивно. Нужно что-то, что будет выполнять запросы параллельно. Как это сделать?

>- БД работает хорошо только пока индексы помещаются в память. Размер индексов можно как-то увидеть, не помню как. А если таблица помещается в память - то вообще все чудесно. Хайлоад обычно предполагает, что все влезает в память, кроме может быть редко используемых данных. Помещаются данные в память или нет - очень важный фактор, влияющий на производительность.
Тяжело представить что таблица сообщений или конференций поместиться в память, потому что количество записей, в теории, будет катастрофически велико. Как с этим выйдет на практике?

И ещё я сейчас поймал себя на мысли, что ничего не знаю о том когда данные хранятся в RAM, я всегда думал что записи хранятся на диске. Я погуглил этот вопрос, и создаётся впечатление что PostgreSQL решает автоматически когда хранить данные в RAM а когда на диске, и предпочтительнее в RAM. Это верно?


>- для тестирования было бы здорово сделать генератор, который нагенерирует произвольное число пользователей, конференций, сообщений. По ним прогонять запросы по одному и параллельно для измерения реальной производительности.
А у меня есть подобный генератор, который заполняет базу тремя пользователями (Alice, Bob и Tester) и заполняет по сотне сообщений между ними: (☣ Warning! Code with a smell! ☣) https://github.com/someApprentice/Crypter/blob/master/api/src/Command/PopulateCommand.php

Следовало бы увеличить количество пользователей до сотен и количество сообщений до тысяч?

Будет ли лучше если я вместо выполнения HTTP-запросов создам объекты с помощью Faker и factory-muffin, и выполню все запросы к БД напрямую?
Ответы: >>1619949
Аноним 2020/03/01 13:56:53  №1619949 4
>>1619249

Скорее всего опенсервер не при чем.

>>1613334

> Приведите пожалуйста аргументы почему нужно шифровать 100% сообщений?

- в Телеграме те, кто использует секретные чаты, выделяются из толпы и привлекают внимание
- проще шифровать все по умолчанию, чем отправить что-то не то, а потом вспомнить, что шифрование выключено

Если делать шифрование отключаемым, может делать по дефолту его включенным, а далее отключать по желанию?

Насчет поиска и сортировки - интересный аргумент. Ну вот тебе и задачка на проектирование, подумай, реально ли реализовать 1) сортировку 2) поиск при полном шифровании сообщений? Что касается сортировки, то тут точно можно присваивать сообщениям какие-то номера вместо времени. Поиск обычно (без шифрования) делается построением индекса, такого вида:

{ слово -> [список id всех сообщений, где оно есть и позиция в сообщениях ] }

Как минимум реально сделать поиск, если у нас приложение поддерживает локальную БД со всеми сообщениями.

> но как мессенджер сам по себе, нужно признать, чрезвычайно удобен

Значит, у него хорошо продуманный интерфейс и процессы взаимодействия с пользователями.

> Осталось только повторить интерфейс.

Хорошая идея, взять за основу продуманный интерфейс.

> Как рассчитать размер плейсхолдера?

Никак, на то оно и шифрование. Но если мы будет заменять блоки снизу вверх, фиксируя позицию самого нижнего блока, то ничего дергаться не будет (кроме плейсхолдеров, которые никто не читает). Неудобно, когда дергается то, на что человек смотрит.

Можно попробовать другой вариант: внизу уже расшифрованные сообщения, а сверху над ними один плейсхолдер, который поднимается вверх по мере расшифровки. Можно расшифровывать сообщения и заменять плейсхолдеры пачками, используя кеш расшифрованных сообщений.

> А можно назвать ещё проще и лаконичней - party?

Можно, хотя мне показалось, что partner наиболее очевидный вариант.

> А почему нельзя назвать просто и лаконично date? Разве на очевидно что это дата сообщения?

Можно и так, мне показалось, что может быть, со словом send будет еще более очевидно.

> Какие метаданные, помимо даты, вы хотели бы чтобы шифровались?

В идеале - все, что можно.

> Как тестируются параллельное выполнение запросов?

Проще всего сделать скрипт, который шлет запросы без перерыва, и запустить через bash-скрипт несколько его копий. До штук 50-100 скриптов эта схема будет работать, потом могут быть большие накладные расходы на постоянные переключения контекста.

> Тяжело представить что таблица сообщений или конференций поместиться в память, потому что количество записей, в теории, будет катастрофически велико. Как с этим выйдет на практике?

Надо, чтобы хотя бы индексы влезали, а лучше вся таблица. Иначе будет возникать нагрузка на дисковую подсистему, хотя тут SSD и выручает, но он не сравнится по скорости с памятью. Либо писать какие-то свои решения на Си/Раст/Го, которые будут более тесно хранить данные в памяти, но это очень сложно. Если речь о бизнесе, то купить память будет дешевле.

> Я погуглил этот вопрос, и создаётся впечатление что PostgreSQL решает автоматически когда хранить данные в RAM а когда на диске, и предпочтительнее в RAM. Это верно?

В MySQL это просто задается опциями в конфиге. Там есть "пул" (InnoDB Pool) в памяти, в котором кешируются "страницы" - куски таблиц и индексов. Администратор задает размер пула. В Postgres - не знаю, возможно, что она полагается на системный кеш страниц (все современные ОС используют невыделенную память как кеш страниц с диска, то есть при чтении кладут туда страницы и при повторном обращении оттуда же берут: https://habr.com/ru/company/smart_soft/blog/228937/ ). В этом случае объем кеша определяется количеством свободной памяти.

> Следовало бы увеличить количество пользователей до сотен и количество сообщений до тысяч?

Было бы хорошо, так как на сотнях тысяч и миллионах записей могут выскочить какие-то эффекты, которых не было на маленьком кол-ве.

> Будет ли лучше если я вместо выполнения HTTP-запросов создам объекты с помощью Faker и factory-muffin, и выполню все запросы к БД напрямую?

Не знаю, надо попробовать. Быстрее всего будет делать большие INSERT напрямую, но может, и через Доктрину будет приемлемая скорость.
Ответы: >>1629813
someApprentice 2020/03/10 23:52:06  №1629813 5
>>1619949
>> Приведите пожалуйста аргументы почему нужно шифровать 100% сообщений?
>
>- в Телеграме те, кто использует секретные чаты, выделяются из толпы и привлекают внимание
>- проще шифровать все по умолчанию, чем отправить что-то не то, а потом вспомнить, что шифрование выключено
>
>Если делать шифрование отключаемым, может делать по дефолту его включенным, а далее отключать по желанию?
>
>Насчет поиска и сортировки - интересный аргумент.
У меня не будет шанса объяснить пользователям что это не приложение тормозит, а происходит дешифровка.
Ответы: >>1632501
Аноним 2020/03/13 19:39:32  №1632501 6
Ладно, раз уж создали новый тред, давайте в него перейдем >>1630065 (OP) , хотя там творится какой-то флуд.

>>1631944

Это делается директивой DocumentRoot. Если открыть ее описание, то видно, где ее можно размещать: https://httpd.apache.org/docs/2.4/mod/core.html

> Context:\tserver config

(щелкнув по ссылке)

> server config
> This means that the directive may be used in the server configuration files (e.g., httpd.conf), but not within any <VirtualHost> or <Directory> containers. It is not allowed in .htaccess files at all.

То есть нужно править конфиг сервера, на хостинге это делается как-то через панель управления. Если ты поднял Апач у себя, то просто редактируется файл в /etc/apache2/ (иногда на каждый хост делается отдельный файл) из-под админа.

>>1629813

> У меня не будет шанса объяснить пользователям что это не приложение тормозит, а происходит дешифровка.

Ну ок. Вообще, есть еще вариант попробовать поменять алгоритмы шифрования. Асимметричное шифрование медленное. Потому часто (например, в SSL (и в HTTPS)) с помощью асимметричного шифрования шифруют только ключ, который используется в быстром алгоритме симметричного шифрования. А сообщения уже шифруют симметричным ключом.

То есть, можно сделать так:

1) создатель беседы генерирует для нее ключ
2) шифрует его публичными ключами собеседников (включая свой публичный ключ) и сохраняет полученные копии на сервер
3) шифрует сообщения в беседе симметричным шифрованием с этим ключом
4) все собеседники скачивают с сервера свою копию зашифрованного ключа и расшифровывают эту копию своим приватным, после чего расшифровывают сообщения

В такой схеме не нужно генерировать отдельную копию сообщения каждому собеседнику. Возможно, тут есть какие-то слабости, и стоит внимательно все продумать. Можно для надежности раз в N часов/дней генерировать новый ключ беседы.

Можно почитать по этой теме про протокол Диффи-Хеллмана - там похожая ситуация.

Если тебе интересно поломать над этим голову, можешь попробовать сделать. Если нет - можно, как ты предлагаешь, просто сделать шифрование включаемым по желанию.
Ответы: >>1639469
someApprentice 2020/03/21 16:34:14  №1639469 7
>>1632501 →
>Ну ок. Вообще, есть еще вариант попробовать поменять алгоритмы шифрования. Асимметричное шифрование медленное. Потому часто (например, в SSL (и в HTTPS)) с помощью асимметричного шифрования шифруют только ключ, который используется в быстром алгоритме симметричного шифрования. А сообщения уже шифруют симметричным ключом.
Мне нравится эта идея. Можно секретные чаты сделать медленные с асимметричным шифрованием, а простые с симметричным. Нужно изучить как это можно сделать, и сначала сделать хотя бы секретные чаты, иначе разработка может затянуться на целую вечность!
Ответы: >>1645510
Аноним 2020/03/29 13:24:33  №1645510 8
>>1639469

Проще все сделать с симметричным для начала. Но ключ от симметричного шифра открыто не хранить, а хранить, зашифрованным ключом или паролем пользователя.

>>1639812

> Я оставил пояснения почти перед каждым свойством и методом. Это неправильный подход?

Совсем очевидные комментарии, не несущие новой информации, не нужны, такие не нужны:

// Складываем b и c
$a = $b + $c;
// Получить число работников
function getEMployeeCount()

Все, что не так очевидно, допустимо.

> Выше анон назвал мои сокращения мерзкими и предложил использовать полные названия.

Лучше полные названия. Не надо экономить на месте в коде, неудобно разбираться в чужих сокращениях, это тратит время.

> Однако опять же: почему в начальном массиве не задаются два дополнительных свойства, отражающие статус руководителя (true | false) и принадлежность к департаменту?

Можешь их добавить. Тогда логично сначала создать пустые отделы, а в массиве указать, для какого отдела сотрудник.

> Другими словами, как конструктор из общей массы объектов сможет выцепить сотрудников, которые относятся к определенному департаменту, если из "опознавательных знаков" у них есть только название должности и ранг?

Он и не должен этим заниматься. Конструктор вообще не имеет отношения к этому массиву. Этим должен заниматься код, разбирающий массив, создающий объекты сотрудников, и передающий их в нужные отделы. Лучше у отдела сделать метод "добавить сотрудника" и туда передавать созданных сотрудников.

Ответы: >>1647817 >>1651539
Аноним 2020/04/01 05:23:16  №1647817 9
>>1645510
>>1645512

Огромное спасибо за просмотр кода и ценные советы по его улучшению! Однако есть затруднения.

> В сотрудниках лучше использовать абстрактные методы вроде getBasePay() для определения базовых ставок.

Я поправил код, удалив поля в абстрактном классе Employee, которые получают разные значения в каждом субклассе и определив ряд дополнительных методов.
Kак иначе? Ведь если я объявлю те поля в абстрактном классе и забуду задать им значения в наследниках, то они автоматом примут значение 0 и код сработает, но даст некорректный результат.

Однако тут я столкнулся с дилеммой.

Вариант 1: https://ideone.com/OVnT1P
Новые методы возвращают свойства не объявленные в данном классе. Сами те методы не являются абстрактными. Таким образом мне не нужно их имплементировать в каждом наследнике - код становится короче.
Однако не нарушает ли данное решение принципа, который ты упомянул в учебнике: предки не должны знать ничего о своих наследниках?

Вариант 2: https://ideone.com/SLNNfE
Новые методы являются абстрактными. Соответственно, абсолютно идентичный код копируется в каждом наследнике, зато принцип вроде как соблюдается.

Что лучше?

> В конструкторе Департамента не надо создавать сотрудников. Надо принимать просто объекты сотрудников. Этим должен заниматься код, разбирающий массив, создающий объекты сотрудников, и передающий их в нужные отделы.

Меня все-таки интересует возможное решение с тем форматом массива, который ты предложил здесь: >>1637453
То есть без указания принадлежности к отделу и статуса начальника:

$employees = [
// count, profession, rank
[3, Employee::JOB_MANAGER, 1],
[5, Employee::JOB_ENGINEER, 2],
...
];

Я разбираю входящий массив при помощи функции, которая не относится ни к одному классу? Создаю объекты и складываю в другой массив? Или все-таки сразу же распределяю по департаментам?
В любом случае я не очень понимаю, как то осуществить в условиях отсутствия каких-либо указаний на принадлежность к отделу и статуса начальника в стартовом массиве.
То есть, вот я сложил все получившиеся объекты в единый массив: https://ideone.com/ikKea2
Но как их теперь автоматизированно разбросать по отделам? Меня очень интересует твой вариант.

Тот формат начального массива, который я составил, вероятно, выглядит витеевато, однако в нем я поместил все указания, без которых я затрудняюсь следовать дальше. Вот он с функцией разбора: https://ideone.com/P8mQ6j
Ответы: >>1651539 >>1652416
Аноним 2020/04/06 05:09:07  №1651539 10
hikarunogo.png (738, 759x760)
760x759
>>1645510
>>1645512
>>1647817

Bump решением первой части вектора с учетом замечаний:
https://ideone.com/aXqwbZ
ОП, my dear, посмотри пожалуйста, когда найдешь время.
Решил все-таки использовать абстрактные методы, чтобы не нарушать тот принцип, который ты упоминал в учебнике.
По-прежнему очень интересно, как можно составить решение с твоим форматом входящего массива - никак в толк не возьму. Pасскажи, пожалуйста.

Также есть общие соображения по поводу дальнейшего антикризисного решения. Как насчет определения метода в Company, который создает копию департаментов, производит над ними манипуляции, после чего использует их для создания нового объекта данного класса и печатает репорт?
Такая практика вообще имеет место быть, когда метод определенного класса создает объект того же самого класса? Это нормально?
Ответы: >>1656443
Аноним 2020/04/07 07:28:45  №1652416 11
>>1647817
сука с пикчи в голос
Аноним 2020/04/12 11:45:32  №1656443 12
>>1652126

Я думаю, тебе нужно сделать свой контроллер для этого, который проверит логин/пароль и выдаст нужный токен. Если ты используешь токены, то у тебя stateless аутентификация (без сессий и кук) и выдачу токенов ты организуешь сам. GuardAuthenticator лишь проверяет токены, он в такой ситуации не имеет дела с логинами/паролями.

>>1651539

В функции printReport() стоило ставить перенос строки после точки, чтобы не было таких длинных строк. А так, код выглядит верно.

Вместо констант вроде Employee::MANAGER можно было использовать Manager::class - это встроенная константа, которая возвращает имя класса.

Для антикризисных мер стоит разобраться с клонированием объектов и магическим методом __clone(). Сделать несколько копий компании и дальше над ними проводить эксперименты. Придется также добавить методы для поиска работников по условиям, увольнения, изменения базовых ставок. Если запутаешься, то погугли в архивах тредов эту задачу и посмотри, как другие решали антикризисные меры.