А как быть с зависимыми данными?
Если транзакция №2 использует то, что навычисляла транзакция №1, а транзакция №1 откатывается?
Зависит от движка. Если это дисковый движок винил, то там изоляция. Транзакция 2 не видит, что навычисляла транзакция 1, пока транзакция 1 не завершена. Если же они меняют данные по одному ключу, то вторая может быть откачена, в зависимости от того, какие именно были изменения.
В случае движка memtx (в оперативной памяти) — тут транзакции выполняются от создания до начала коммита без переключения корутин. То есть увидеть данные друг друга в процессе выполнения не могут. В версии 2.6 теперь они могут прерываться, но добавили изоляцию и откаты как в виниле.
Но речь то о другом — пока вы не получили подтверждения вы не можете быть уверенными что данные точно сохранены. И транзакция всё это время ждет в незавершенном состоянии. Что не вяжется с производительностью.
Не вижу связи. Но я предполагаю, что вы не в курсе про корутины у нас, так что отсылаю к моему первому комментарию на этой странице.
То есть по достижению момента записи (момента постановки в очередь за запись) происходит переключение одно-потока на следующую транзакцию?
При этом в целом сервер однопоточен?
Да. Отправка транзакций на запись не блокирует главный поток. Он будет делать новые транзакции, пока уже отправленные пишутся на диск/сеть. Это возможно за счет наличия множества корутин в одном потоке.
А транзакция из потока при этому уже подтверждена или или еще не подтверждена?
Не понял вопроса. Пока транзакция не записана на диск, ее коммит не завершен, если в этом вопрос. В случае синхронной репликации коммит не будет завершен до репликации и получения подтверждений. Пока коммит не завершен, пользователь ответ не получит. Надеюсь, ответил на вопрос.
Если речь о надежной записи, то клиент должен ждать пока сервер окончательно запишет на диск, вытолкнет все буфера и кэши и только после этого сервер сообщит клиенту об успешном завершении его транзакции.
То есть подтверждение идет пачкой — значит клиенты получают информацию о коммитах всех своих транзакций тоже пачкой.
Да, пачкой. В одном потоке. Я написал в предыдущем ответе. У нас поток один главный, но в нем много корутин. Когда пачка транзакций закончит запись, все корутины, ждущие эти транзакции, по очереди отработают в главном потоке.
В чем же тогда однопоточность? Что сама обработка данных идет в один поток?
Да. База данных, доступная пользователю, и вся работа с ней, в одном потоке.
А параллельность во входящем сетевом соединении от клиентов всё же поддерживается Тарантулом?
Вопрос не понял. Читать из одного соединения параллельно — небезопасно в целом, как метод программирования вообще. Для чтения из сети и записи в нее у нас отдельный поток. Он читает сокеты, парсит запросы, отправляет их в главный поток, там они выполняются в корутинах, и потом сетевой поток пишет ответы обратно в сеть. Поток на все соединения один, но как показывает практика, этого более чем достаточно даже для тысяч соединений, в 100% CPU он практически не бывает. Это все возможно, если работа с памятью в нем организована эффективно, как в Тарантуле.
Разработчики Tarantool в своё время как про преимущество свой разработки говорили про однопоточность.
Тарантул не однопоточный, на самом деле. Вам, как пользователям, доступен один поток. Но их всегда как минимум три — для обработки транзакций, для работы с сетью, и для работы с диском. Еще потоки создаются на подключенные реплики. Так что блокировки всего процесса и тем более главного потока не бывает из-за того, что что-то куда-то долго пишется.
Что если, мол, нужно распараллеливать — то просто запускайте отдельные СУБД на каждом ядре. И шардингом распараллеливайте. Говорили, что причиной выбора однопоточности явилось упрощение обработки транзакций, что нет блокировок, все проходит быстро.
Все верно.
С другой стороны разработчики Тарантула как про преимущество своей разработки говорили про надежность хранения. В т.ч. и после сбоев.
Это не прям таки преимущество. Это норма. Без надежности хранения база не лучше будет, чем std::map или Lua-таблица.
С третьей стороны — про производительность высочайшую. Эти вещи уже противоречат.
Почему? То есть если база не теряет данные, то она не может быть производительной? Или потому что пользователю доступен только один поток выполнения? Ни то, ни другое, честно говоря, не вижу, как связано с невозможностью писать код ядра базы эффективно.
Не может быть одновременно и высочайшей производительности в однопоточном режиме с обязательной записью транзакций на диск чтобы подстраховаться от сбоев. Ниже подробнее напишу почему так.
Я наверное попрошу определить, что такое «высочайшая прозводительность». Я видимо не до конца понимаю.
Теперь добавляется синхронная репликация. Что тоже не увеличивает производительность.
Она не увеличивает, разумеется. Синхронные транзакции будут иметь значительно большую задержку, чем асинхронные. Из-за того, что их не только на диск надо писать, но еще и по сети слать и ждать ответы. Ничего не поделаешь.
А где же истина?
А где тут кто-то лгал или что-то утаивал? Цитату, если можно. Если это что-то в документации, то это баг документации — поправим. Там часто проскакивает всякое.
Ключевое здесь то, что она и не ухудшает прозводительность. То есть пользователи не платят за синхрон, если им не пользуются. Это никак не затрагивает обычные транзакции.
Почему так как я говорю? Вот почему:
m.habr.com/ru/company/selectel/blog/521168
Если мы начинаем писать на диск маленькими порциями с обязательным проталкиванием данных из кэша, с ожиданием реального завершения операции записи — то при записывании транзакции по одном — получаем крайне низкую скорость записи. Чуть ли не в 1000 раз меньше, чем при записи большими порциями.
Да, если писать их по одной, да еще и в том же потоке, что и транзакции выполняются, то это будет практически в ноль стоять. Но ни то, ни другое, не справедливо для Тарантула. Транзакции у нас по одной не пишутся. Даже если включите сброс на диск после каждой записи. Потому что у нас event loop в главном потоке. В одной итерации event loop-а обычно сразу много транзакций запрашивают запись на диск, из многих запросов пользователей. Пока итерация еще идет, эти запросы копятся. Потом в конце итерации они отправляются в другой поток одной пачкой, который запишет их на диск все разом. Причем пока он их пишет, там уже другая пачка в главном потоке формируется. И так далее.
Главный поток шлет пачки транзакций в поток записи в журнал и работает дальше. Запросы, создавшие эти транзакции, будут ждать. Журнальный поток будет писать. Все потоки операционной системы постоянно при деле.
Я тут еще уточню, что значит «запросы будут ждать». Возможно вас это смущает. Это разумеется не ждать вида while (!finished) sleep(1);. Запросы все выполняются в корутинах в главном потоке, так что когда запрос ждет коммита транзакции, его корутина просто перестает ставиться на исполнение, пока запись на диск/сеть не кончится. Остальные корутины работают, как ни в чем не бывало.
Записывать транзакции пачками мы не можем, так как обработка однопоточная.
Можем и делаем. Обработка однопоточная, но запись на диск идет в другом потоке.
Блокировок и версионирования нет. Значит, отдельные записи на диск будут мааааленькими, размером в одну транзакцию, это считанные байты зачастую.
Это как связано, не понял? Размер записей на диск и блокировки.
Не записывать на диск для завершения каждой транзакции мы не можем — так как иначе не получим гарантию надежного сохранения данных.
Верно. Поэтому пишем. Но я не вижу, как это запрещает писать на диск сразу много транзакций.
Асинхронная репликация тоже не давала таких гарантий.
Давала те же, в плане записи на диск. Проблема в том, что записи на диск, хоть 500 раз с fsync включенным, не дает гарантий против потери данных. Синхронная в дополнение дает еще и запись на реплики, что такие гарантии тоже не дает на 100% (весь кластер может уничтожить бомба, например), но увеличивает сохранность данных существенно.
Синхронная же репликация, как в случае с диском, даёт нам блокировку транзакции до тех пор, пока запись не окажется на другом сервере. Это плата за надежность.
Параллельное исполнение позволяет увеличить порции, которые передаются по сети и записываются на диск при завершении транзакции, что существенно увеличило бы скорость.
Они исполняются параллельно как раз. Пока одни транзакции ждут записи или репликации, другие уже могут работать дальше.
Я так подозреваю что вы хоть и выполняете транзакции в 1 поток, но при записи блокируете их пачками, в ожидании завершения записи? Разумеется в предположении, что включена синхронная запись на диск и синхронная репликация.
Почти. Я выше уже расписал, но тут еще раз продублирую. Транзакции пишутся и реплицируются пачками. Но они не блокируют другие транзакции. Другие могут дальше делать свои insert/select/replace и тд, пока не позовут commit. И уже тогда будут ждать в другом потоке, пока их на диск запишут и в сеть, если надо. Главный поток не блокируется никогда, и всегда делает следующие транзакции.
Зависит от движка. Если это дисковый движок винил, то там изоляция. Транзакция 2 не видит, что навычисляла транзакция 1, пока транзакция 1 не завершена. Если же они меняют данные по одному ключу, то вторая может быть откачена, в зависимости от того, какие именно были изменения.
В случае движка memtx (в оперативной памяти) — тут транзакции выполняются от создания до начала коммита без переключения корутин. То есть увидеть данные друг друга в процессе выполнения не могут. В версии 2.6 теперь они могут прерываться, но добавили изоляцию и откаты как в виниле.
Не вижу связи. Но я предполагаю, что вы не в курсе про корутины у нас, так что отсылаю к моему первому комментарию на этой странице.
При записи на диск в случае асинхронной репликации, и при успешной репликации + записи коммита на диск в случае синхронной.
Да.
Да.
Да. Отправка транзакций на запись не блокирует главный поток. Он будет делать новые транзакции, пока уже отправленные пишутся на диск/сеть. Это возможно за счет наличия множества корутин в одном потоке.
Не понял вопроса. Пока транзакция не записана на диск, ее коммит не завершен, если в этом вопрос. В случае синхронной репликации коммит не будет завершен до репликации и получения подтверждений. Пока коммит не завершен, пользователь ответ не получит. Надеюсь, ответил на вопрос.
Да, пачкой. В одном потоке. Я написал в предыдущем ответе. У нас поток один главный, но в нем много корутин. Когда пачка транзакций закончит запись, все корутины, ждущие эти транзакции, по очереди отработают в главном потоке.
Да. База данных, доступная пользователю, и вся работа с ней, в одном потоке.
Вопрос не понял. Читать из одного соединения параллельно — небезопасно в целом, как метод программирования вообще. Для чтения из сети и записи в нее у нас отдельный поток. Он читает сокеты, парсит запросы, отправляет их в главный поток, там они выполняются в корутинах, и потом сетевой поток пишет ответы обратно в сеть. Поток на все соединения один, но как показывает практика, этого более чем достаточно даже для тысяч соединений, в 100% CPU он практически не бывает. Это все возможно, если работа с памятью в нем организована эффективно, как в Тарантуле.
Тарантул не однопоточный, на самом деле. Вам, как пользователям, доступен один поток. Но их всегда как минимум три — для обработки транзакций, для работы с сетью, и для работы с диском. Еще потоки создаются на подключенные реплики. Так что блокировки всего процесса и тем более главного потока не бывает из-за того, что что-то куда-то долго пишется.
Все верно.
Это не прям таки преимущество. Это норма. Без надежности хранения база не лучше будет, чем std::map или Lua-таблица.
Почему? То есть если база не теряет данные, то она не может быть производительной? Или потому что пользователю доступен только один поток выполнения? Ни то, ни другое, честно говоря, не вижу, как связано с невозможностью писать код ядра базы эффективно.
Я наверное попрошу определить, что такое «высочайшая прозводительность». Я видимо не до конца понимаю.
Она не увеличивает, разумеется. Синхронные транзакции будут иметь значительно большую задержку, чем асинхронные. Из-за того, что их не только на диск надо писать, но еще и по сети слать и ждать ответы. Ничего не поделаешь.
А где тут кто-то лгал или что-то утаивал? Цитату, если можно. Если это что-то в документации, то это баг документации — поправим. Там часто проскакивает всякое.
Ключевое здесь то, что она и не ухудшает прозводительность. То есть пользователи не платят за синхрон, если им не пользуются. Это никак не затрагивает обычные транзакции.
Да, если писать их по одной, да еще и в том же потоке, что и транзакции выполняются, то это будет практически в ноль стоять. Но ни то, ни другое, не справедливо для Тарантула. Транзакции у нас по одной не пишутся. Даже если включите сброс на диск после каждой записи. Потому что у нас event loop в главном потоке. В одной итерации event loop-а обычно сразу много транзакций запрашивают запись на диск, из многих запросов пользователей. Пока итерация еще идет, эти запросы копятся. Потом в конце итерации они отправляются в другой поток одной пачкой, который запишет их на диск все разом. Причем пока он их пишет, там уже другая пачка в главном потоке формируется. И так далее.
Главный поток шлет пачки транзакций в поток записи в журнал и работает дальше. Запросы, создавшие эти транзакции, будут ждать. Журнальный поток будет писать. Все потоки операционной системы постоянно при деле.
Я тут еще уточню, что значит «запросы будут ждать». Возможно вас это смущает. Это разумеется не ждать вида while (!finished) sleep(1);. Запросы все выполняются в корутинах в главном потоке, так что когда запрос ждет коммита транзакции, его корутина просто перестает ставиться на исполнение, пока запись на диск/сеть не кончится. Остальные корутины работают, как ни в чем не бывало.
Можем и делаем. Обработка однопоточная, но запись на диск идет в другом потоке.
Это как связано, не понял? Размер записей на диск и блокировки.
Верно. Поэтому пишем. Но я не вижу, как это запрещает писать на диск сразу много транзакций.
Давала те же, в плане записи на диск. Проблема в том, что записи на диск, хоть 500 раз с fsync включенным, не дает гарантий против потери данных. Синхронная в дополнение дает еще и запись на реплики, что такие гарантии тоже не дает на 100% (весь кластер может уничтожить бомба, например), но увеличивает сохранность данных существенно.
Они исполняются параллельно как раз. Пока одни транзакции ждут записи или репликации, другие уже могут работать дальше.
Почти. Я выше уже расписал, но тут еще раз продублирую. Транзакции пишутся и реплицируются пачками. Но они не блокируют другие транзакции. Другие могут дальше делать свои insert/select/replace и тд, пока не позовут commit. И уже тогда будут ждать в другом потоке, пока их на диск запишут и в сеть, если надо. Главный поток не блокируется никогда, и всегда делает следующие транзакции.
И есть начатый патч: github.com/tarantool/vshard/commit/3346a02b7ab51a73bc39f2b5e0e94e06a2ce2ff3 Но то, что сейчас реализовано в мастере, пока всех устраивает, и потому этот патч не особо продвигается. Блокировка там будет, но не на все время переноса, а только в самом конце.